]> git.neil.brown.name Git - edlib.git/blob - doc-email.c
email: use text-search for find_boundary()
[edlib.git] / doc-email.c
1 /*
2  * Copyright Neil Brown ©2016-2020 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * doc-email: Present an email message as its intended content, with
6  * part recognition and decoding etc.
7  *
8  * A multipart document is created where every other part is a "spacer"
9  * where buttons can be placed to control visibility of the previous part,
10  * or to act on it in some other way.
11  * The first part is the headers which are copied to a temp text document.
12  * Subsequent non-spacer parts are cropped sections of the email, possibly
13  * with filters overlayed to handle the transfer encoding.
14  * Alternately, they might be temp documents simplar to the headers
15  * storing e.g. transformed HTML or an image.
16  */
17
18 #define _GNU_SOURCE /* for asprintf */
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <stdio.h>
25 #include "core.h"
26 #include "misc.h"
27
28 struct email_info {
29         struct pane     *email safe;
30         struct pane     *spacer safe;
31 };
32
33 static bool handle_content(struct pane *p safe, char *type, char *xfer,
34                            struct mark *start safe, struct mark *end safe,
35                            struct pane *mp safe, struct pane *spacer safe,
36                            char *path safe);
37
38 static bool cond_append(struct buf *b safe, char *txt safe, char *tag safe,
39                         int offset, struct mark *m safe, int *cp safe)
40 {
41         char *tagf = "active-tag:email-";
42         int prelen = 1 + strlen(tagf) + strlen(tag) + 1 + 1;
43         int postlen = 1 + 3;
44         int len = prelen + strlen(txt) + postlen;
45         if (offset != NO_NUMERIC && offset >= 0 && offset <= b->len + len)
46                 return False;
47         buf_concat(b, "<");
48         buf_concat(b, tagf);
49         buf_concat(b, tag);
50         buf_concat(b, ">[");
51         if (*cp == 0)
52                 return False;
53         if (*cp > 0)
54                 *cp -= 1;
55         buf_concat(b, txt);
56         buf_concat(b, "]</>");
57         return True;
58 }
59
60 static bool is_attr(char *a safe, char *attrs safe)
61 {
62         int l = strlen(a);
63         if (strncmp(a, attrs, l) != 0)
64                 return False;
65         if (attrs[l] == ':' || attrs[l] == '\0')
66                 return True;
67         return False;
68 }
69
70 DEF_CMD(email_spacer)
71 {
72         struct buf b;
73         int visible = 1;
74         struct mark *m = ci->mark;
75         struct mark *pm = ci->mark2;
76         int o = ci->num;
77         int cp = -1;
78         char *attr;
79         int ret;
80         bool ok = True;
81
82         if (!m)
83                 return Enoarg;
84         if (pm) {
85                 /* Count the number of chars before the cursor.
86                  * This tells us which button to highlight.
87                  */
88                 cp = 0;
89                 pm = mark_dup(pm);
90                 while (pm->seq > m->seq && !mark_same(pm, m)) {
91                         doc_prev(ci->focus, pm);
92                         cp += 1;
93                 }
94                 mark_free(pm);
95         }
96
97         buf_init(&b);
98         buf_concat(&b, "<fg:red>");
99
100         attr = pane_mark_attr(ci->home, m, "multipart-prev:email:path");
101         if (attr) {
102                 buf_concat(&b, attr);
103                 buf_concat(&b, " ");
104         }
105
106         attr = pane_mark_attr(ci->focus, m, "email:visible");
107         if (attr && *attr == '0')
108                 visible = 0;
109         attr = pane_mark_attr(ci->home, m, "multipart-prev:email:actions");
110         if (!attr)
111                 attr = "hide";
112
113         while (ok && attr && *attr) {
114                 if (is_attr("hide", attr))
115                         ok = cond_append(&b, visible ? "HIDE" : "SHOW", "1",
116                                          o, m, &cp);
117                 else if (is_attr("save", attr))
118                         ok = cond_append(&b, "Save", "2", o, m, &cp);
119                 else if (is_attr("open", attr))
120                         ok = cond_append(&b, "Open", "3", o, m, &cp);
121                 attr = strchr(attr, ':');
122                 if (attr)
123                         attr += 1;
124         }
125         /* end of line, only display if we haven't reached
126          * the cursor or offset
127          *
128          * if cp < 0, we aren't looking for a cursor, so don't stop.
129          * if cp > 0, we haven't reached cursor yet, so don't stop
130          * if cp == 0, this is cursor pos, so stop.
131          */
132         if (ok && cp != 0 && ((o < 0 || o == NO_NUMERIC))) {
133                 wint_t wch;
134                 buf_concat(&b, "</>");
135                 attr = pane_mark_attr(ci->focus, m,
136                                       "multipart-prev:email:content-type");
137                 if (attr) {
138                         buf_concat(&b, " ");
139                         buf_concat(&b, attr);
140                 }
141                 buf_concat(&b, "\n");
142                 while ((wch = doc_next(ci->focus, m)) &&
143                        wch != '\n' && wch != WEOF)
144                         ;
145         }
146
147         ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL,
148                         buf_final(&b));
149         free(b.b);
150         return ret;
151 }
152
153 DEF_CMD(email_select)
154 {
155         /* If mark is on a button, press it... */
156         struct mark *m = ci->mark;
157         char *a;
158         int r = 0;
159
160         if (!m)
161                 return Enoarg;
162         a = pane_mark_attr(ci->focus, m, "markup:func");
163         if (!a || strcmp(a, "doc:email:render-spacer") != 0)
164                 return Efallthrough;
165         a = pane_mark_attr(ci->focus, m, "multipart-prev:email:actions");
166         if (!a)
167                 a = "hide";
168         while (r > 0 && a) {
169                 a = strchr(a, ':');
170                 if (a)
171                         a += 1;
172                 r -= 1;
173         }
174         if (a && is_attr("hide", a)) {
175                 int vis = 1;
176                 a = pane_mark_attr(ci->focus, m, "email:visible");
177                 if (a && *a == '0')
178                         vis = 0;
179                 call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
180                      vis ? "0" : "1");
181         }
182         return 1;
183 }
184
185 static struct map *email_view_map safe;
186
187 DEF_LOOKUP_CMD(email_view_handle, email_view_map);
188
189 static char tspecials[] = "()<>@,;:\\\"/[]?=";
190
191 static int lws(char c) {
192         return c == ' '  || c == '\t' || c == '\r' || c == '\n';
193 }
194
195 static char *get_822_token(char **hdrp safe, int *len safe)
196 {
197         /* A "token" is one of:
198          * Quoted string ""
199          * single char from tspecials
200          * string on of LWS, and none tspecials
201          *
202          * (comments) are skipped.
203          * Start is returned, hdrp is moved, len is reported.
204          */
205         char *hdr = *hdrp;
206         char *start;
207         *len = 0;
208         if (!hdr)
209                 return NULL;
210         while (1) {
211                 while (lws(*hdr))
212                         hdr++;
213                 if (*hdr == '(') {
214                         while (*hdr && *hdr != ')')
215                                 hdr++;
216                         continue;
217                 }
218                 if (*hdr == '"') {
219                         start = ++hdr;
220                         while (*hdr && *hdr != '"')
221                                 hdr++;
222                         *len = hdr - start;
223                         hdr++;
224                         *hdrp = hdr;
225                         return start;
226                 }
227                 if (!*hdr) {
228                         *hdrp = NULL;
229                         return NULL;
230                 }
231                 if (strchr(tspecials, *hdr)) {
232                         start = hdr;
233                         hdr++;
234                         *len = 1;
235                         *hdrp = hdr;
236                         return start;
237                 }
238                 start = hdr;
239                 while (*hdr && !lws(*hdr) && !strchr(tspecials, *hdr))
240                         hdr++;
241                 *len = hdr - start;
242                 *hdrp = hdr;
243                 return start;
244         }
245 }
246
247 static char *get_822_attr(char *shdr safe, char *attr safe)
248 {
249         /* If 'hdr' contains "$attr=...", return "..."
250          * with "quotes" stripped
251          */
252         int len, alen;
253         char *hdr = shdr;
254         char *h;
255         static char *last = NULL;
256
257         free(last);
258         last = NULL;
259
260         alen = strlen(attr);
261         while (hdr) {
262                 while ((h = get_822_token(&hdr, &len)) != NULL &&
263                        (len != alen || strncasecmp(h, attr, alen) != 0))
264                         ;
265                 h = get_822_token(&hdr, &len);
266                 if (!h || len != 1 || *h != '=')
267                         continue;
268                 h = get_822_token(&hdr, &len);
269                 if (!h)
270                         continue;
271                 last = strndup(h, len);
272                 return last;
273         }
274         return NULL;
275 }
276
277 static char *get_822_word(char *hdr safe)
278 {
279         /* Get the first word from header, is static space */
280         static char *last = NULL;
281         int len;
282         char *h;
283
284         free(last);
285         last = NULL;
286         h = get_822_token(&hdr, &len);
287         if (!h)
288                 return h;
289         last = strndup(h, len);
290         return last;
291 }
292
293 static bool tok_matches(char *tok, int len, char *match safe)
294 {
295         if (!tok)
296                 return False;
297         if (len != (int)strlen(match))
298                 return False;
299         return strncasecmp(tok, match, len) == 0;
300 }
301
302 static bool handle_text(struct pane *p safe, char *type, char *xfer,
303                         struct mark *start safe, struct mark *end safe,
304                         struct pane *mp safe, struct pane *spacer safe,
305                         char *path)
306 {
307         struct pane *h;
308         int need_charset = 0;
309         char *charset;
310         char *major, *minor = NULL;
311         int majlen, minlen;
312         char *ctype = NULL;
313
314         h = call_ret(pane, "attach-crop", p, 0, start, NULL, 0, end);
315         if (!h)
316                 return False;
317
318         if (xfer) {
319                 int xlen;
320                 xfer = get_822_token(&xfer, &xlen);
321                 if (xfer && xlen == 16 &&
322                     strncasecmp(xfer, "quoted-printable", 16) == 0) {
323                         struct pane *hx = call_ret(pane,
324                                                    "attach-quoted_printable",
325                                                    h);
326                         if (hx) {
327                                 h = hx;
328                                 need_charset = 1;
329                         }
330                 }
331                 if (xfer && xlen == 6 &&
332                     strncasecmp(xfer, "base64", 6) == 0) {
333                         struct pane *hx = call_ret(pane, "attach-base64", h);
334                         if (hx) {
335                                 h = hx;
336                                 need_charset = 1;
337                         }
338                 }
339         }
340         if (type && need_charset &&
341             (charset = get_822_attr(type, "charset")) != NULL &&
342             strcasecmp(charset, "utf-8") == 0) {
343                 struct pane *hx = call_ret(pane, "attach-utf8", h);
344                 if (hx)
345                         h = hx;
346         }
347         major = get_822_token(&type, &majlen);
348         if (major && tok_matches(major, majlen, "text"))
349                 attr_set_str(&h->attrs, "email:actions", "hide:save");
350         else
351                 attr_set_str(&h->attrs, "email:actions", "hide:open");
352         if (major) {
353                 minor = get_822_token(&type, &minlen);
354                 if (minor && tok_matches(minor, minlen, "/"))
355                         minor = get_822_token(&type, &minlen);
356                 else
357                         minor = NULL;
358         }
359         if (minor)
360                 asprintf(&ctype, "%1.*s/%1.*s", majlen, major, minlen, minor);
361         else
362                 asprintf(&ctype, "%1.*s", majlen, major);
363         if (ctype && strcmp(ctype, "text/html") == 0) {
364                 struct pane *html;
365                 html = call_ret(pane, "html-to-text", h);
366                 if (html) {
367                         pane_close(h);
368                         h = html;
369                 }
370         }
371         if (ctype) {
372                 int i;
373                 for (i = 0; ctype[i]; i++)
374                         if (isupper(ctype[i]))
375                                 ctype[i] = tolower(ctype[i]);
376                 attr_set_str(&h->attrs, "email:content-type", ctype);
377                 free(ctype);
378         }
379         attr_set_str(&h->attrs, "email:path", path);
380
381         home_call(mp, "multipart-add", h);
382         home_call(mp, "multipart-add", spacer);
383         return True;
384 }
385
386 /* Find a multipart boundary between start and end, moving
387  * 'start' to after the boundary, and 'pos' to just before it.
388  * Return 0 if a non-terminal boundary is found
389  * Return 1 if a terminal boundary is found (trailing --)
390  * Return -1 if nothing is found.
391  */
392 #define is_lws(c) ({int __c2 = c; __c2 == ' ' || __c2 == '\t' || is_eol(__c2); })
393 static int find_boundary(struct pane *p safe,
394                          struct mark *start safe, struct mark *end safe,
395                          struct mark *pos,
396                          char *boundary safe)
397 {
398         char *patn = NULL;
399         int ret;
400         int len = strlen(boundary);
401
402         asprintf(&patn, "^--(?%d:%s)(--)?[ \\t\\r]*$", len, boundary);
403         ret = call("text-search", p, 0, start, patn, 0, end);
404         if (ret <= 0)
405                 return -1;
406         ret -= 1;
407         if (pos) {
408                 int cnt = ret;
409                 mark_to_mark(pos, start);
410                 while (cnt > 0 && doc_prev(p, pos) != WEOF)
411                         cnt -= 1;
412                 /* Previous char is CRLF, and must be swallowed */
413                 if (doc_prior(p, pos) == '\n')
414                         doc_prev(p, pos);
415                 if (doc_prior(p, pos) == '\r')
416                         doc_prev(p, pos);
417         }
418         while (is_lws(doc_prior(p, start))) {
419                 len -= 1;
420                 doc_prev(p, start);
421         }
422         while (is_lws(doc_following(p, start)))
423                 doc_next(p, start);
424         if (ret == 2 + len)
425                 return 0;
426         if (ret == 2 + len + 2)
427                 return 1;
428         return -1;
429 }
430
431 static bool handle_multipart(struct pane *p safe, char *type safe,
432                              struct mark *start safe, struct mark *end safe,
433                              struct pane *mp safe, struct pane *spacer safe,
434                              char *path safe)
435 {
436         char *boundary = get_822_attr(type, "boundary");
437         int found_end = 0;
438         struct mark *pos, *part_end;
439         char *tok;
440         int len;
441         int partnum = 0;
442         char *newpath;
443
444         if (!boundary)
445                 /* FIXME need a way to say "just display the text" */
446                 return True;
447
448         found_end = find_boundary (p, start, end, NULL, boundary);
449         if (found_end != 0)
450                 return True;
451         tok = get_822_token(&type, &len);
452         if (tok) {
453                 tok = get_822_token(&type, &len);
454                 if (tok && tok[0] == '/')
455                         tok = get_822_token(&type, &len);
456         }
457         boundary = strdup(boundary);
458         pos = mark_dup(start);
459         part_end = mark_dup(pos);
460         while (found_end == 0 &&
461                (found_end = find_boundary(p, pos, end, part_end,
462                                           boundary)) >= 0) {
463                 struct pane *hdr = call_ret(pane, "attach-rfc822header", p,
464                                             0, start, NULL,
465                                             0, part_end);
466                 char *ptype, *pxfer;
467
468                 if (!hdr)
469                         break;
470                 call("get-header", hdr, 0, NULL, "content-type",
471                      0, NULL, "cmd");
472                 call("get-header", hdr, 0, NULL, "content-transfer-encoding",
473                      0, NULL, "cmd");
474                 ptype = attr_find(hdr->attrs, "rfc822-content-type");
475                 pxfer = attr_find(hdr->attrs,
476                                   "rfc822-content-transfer-encoding");
477
478                 pane_close(hdr);
479
480                 newpath = NULL;
481                 asprintf(&newpath, "%s%s%1.*s:%d", path, path[0] ? ",":"",
482                          len, tok, partnum);
483                 partnum++;
484
485                 handle_content(p, ptype, pxfer, start, part_end, mp, spacer,
486                                newpath ?:"");
487                 free(newpath);
488                 mark_to_mark(start, pos);
489         }
490         mark_to_mark(start, pos);
491         mark_free(pos);
492         mark_free(part_end);
493         free(boundary);
494         return True;
495 }
496
497 static bool handle_content(struct pane *p safe, char *type, char *xfer,
498                            struct mark *start safe, struct mark *end safe,
499                            struct pane *mp safe, struct pane *spacer safe,
500                            char *path safe)
501 {
502         char *hdr;
503         char *major, *minor = NULL;
504         int mjlen, mnlen;
505
506         if (!type)
507                 type = "text/plain";
508         hdr = type;
509
510         major = get_822_token(&hdr, &mjlen);
511         if (major) {
512                 minor = get_822_token(&hdr, &mnlen);
513                 if (minor && minor[0] == '/')
514                         minor = get_822_token(&hdr, &mnlen);
515         }
516         if (major == NULL ||
517             tok_matches(major, mjlen, "text"))
518                 return handle_text(p, type, xfer, start, end,
519                                    mp, spacer, path);
520
521         if (tok_matches(major, mjlen, "multipart"))
522                 return handle_multipart(p, type, start, end, mp, spacer, path);
523
524         /* default to plain text until we get a better default */
525         return handle_text(p, type, xfer, start, end, mp, spacer, path);
526 }
527
528 DEF_CMD(open_email)
529 {
530         int fd;
531         struct email_info *ei;
532         struct mark *start, *end;
533         struct pane *p, *h2;
534         char *mime;
535         char *xfer = NULL, *type = NULL;
536         struct pane *hdrdoc;
537         struct mark *point;
538
539         if (ci->str == NULL ||
540             strncmp(ci->str, "email:", 6) != 0)
541                 return Efallthrough;
542         fd = open(ci->str+6, O_RDONLY);
543         p = call_ret(pane, "doc:open", ci->focus, fd, NULL, ci->str + 6, 1);
544         if (!p)
545                 return Efallthrough;
546         start = vmark_new(p, MARK_UNGROUPED, NULL);
547         if (!start)
548                 return Efallthrough;
549         end = mark_dup(start);
550         call("doc:set-ref", p, 0, end);
551
552         alloc(ei, pane);
553         ei->email = p;
554         h2 = call_ret(pane, "attach-rfc822header", p, 0, start, NULL, 0, end);
555         if (!h2)
556                 goto out;
557         p = call_ret(pane, "doc:from-text", p, 0, NULL, NULL, 0, NULL,
558                      "0123456789\n");
559         if (!p) {
560                 pane_close(h2);
561                 goto out;
562         }
563         ei->spacer = p;
564         point = vmark_new(p, MARK_POINT, NULL);
565         call("doc:set-ref", p, 1, point);
566         call("doc:set-attr", p, 1, point, "markup:func", 0,
567              NULL, "doc:email:render-spacer");
568         mark_free(point);
569
570         hdrdoc = call_ret(pane, "attach-doc-text", ci->focus);
571         if (!hdrdoc)
572                 goto out;
573         call("doc:set:autoclose", hdrdoc, 1);
574         point = vmark_new(hdrdoc, MARK_POINT, NULL);
575         if (!point)
576                 goto out;
577
578         /* copy some headers to the header temp document */
579         home_call(h2, "get-header", hdrdoc, 0, point, "From");
580         home_call(h2, "get-header", hdrdoc, 0, point, "Date");
581         home_call(h2, "get-header", hdrdoc, 0, point, "Subject", 0, NULL, "text");
582         home_call(h2, "get-header", hdrdoc, 0, point, "To", 0, NULL, "list");
583         home_call(h2, "get-header", hdrdoc, 0, point, "Cc", 0, NULL, "list");
584
585         /* copy some headers into attributes for later analysis */
586         call("get-header", h2, 0, NULL, "MIME-Version", 0, NULL, "cmd");
587         call("get-header", h2, 0, NULL, "content-type", 0, NULL, "cmd");
588         call("get-header", h2, 0, NULL, "content-transfer-encoding",
589              0, NULL, "cmd");
590         mime = attr_find(h2->attrs, "rfc822-mime-version");
591         if (mime)
592                 mime = get_822_word(mime);
593         if (mime && strcmp(mime, "1.0") == 0) {
594                 type = attr_find(h2->attrs, "rfc822-content-type");
595                 xfer = attr_find(h2->attrs, "rfc822-content-transfer-encoding");
596         }
597         pane_close(h2);
598
599         p = call_ret(pane, "attach-doc-multipart", ci->home);
600         if (!p)
601                 goto out;
602         call("doc:set:autoclose", p, 1);
603         attr_set_str(&hdrdoc->attrs, "email:actions", "hide");
604         home_call(p, "multipart-add", hdrdoc);
605         home_call(p, "multipart-add", ei->spacer);
606         call("doc:set:autoclose", hdrdoc, 1);
607
608         attr_set_str(&hdrdoc->attrs, "email:path", "headers");
609
610         if (!handle_content(ei->email, type, xfer, start, end,
611                             p, ei->spacer, "body"))
612                 goto out;
613
614         mark_free(start);
615         mark_free(end);
616         attr_set_str(&p->attrs, "render-default", "text");
617         attr_set_str(&p->attrs, "filename", ci->str+6);
618         attr_set_str(&p->attrs, "doc-type", "email");
619         return comm_call(ci->comm2, "callback:attach", p);
620
621 out:
622         mark_free(start);
623         mark_free(end);
624         free(ei);
625         // FIXME free stuff
626         return Efail;
627 }
628
629 struct email_view {
630         int     parts;
631         char    *invis safe;
632 };
633
634 DEF_CMD(email_view_free)
635 {
636         struct email_view *evi = ci->home->data;
637
638         free(evi->invis);
639         unalloc(evi, pane);
640         return 1;
641 }
642
643 static int get_part(struct pane *p safe, struct mark *m safe)
644 {
645         char *a = pane_mark_attr(p, m, "multipart:part-num");
646
647         if (!a)
648                 return Efail;
649         return atoi(a);
650 }
651
652 static int count_buttons(struct pane *p safe, struct mark *m safe)
653 {
654         int cnt = 0;
655         char *attr = pane_mark_attr(p, m, "multipart-prev:email:actions");
656         if (!attr)
657                 attr = "hide";
658         while (attr) {
659                 cnt += 1;
660                 attr = strchr(attr, ':');
661                 if (attr)
662                         attr++;
663         }
664         return cnt;
665 }
666
667 DEF_CMD(email_step)
668 {
669         struct pane *p = ci->home;
670         struct email_view *evi = p->data;
671         wint_t ret;
672         int n = -1;
673
674         if (!ci->mark)
675                 return Enoarg;
676         if (ci->num) {
677                 ret = home_call(p->parent, ci->key, ci->focus,
678                                 ci->num, ci->mark, evi->invis,
679                                 ci->num2);
680                 n = get_part(p->parent, ci->mark);
681                 if (ci->num2 && n > 0 && (n & 1)) {
682                         /* Moving in a spacer, If after valid buttons,
683                          * move to end
684                          */
685                         wint_t c;
686                         unsigned int buttons;
687                         buttons = count_buttons(p, ci->mark);
688                         while (isdigit(c = doc_following(p->parent, ci->mark)) &&
689                                (c - '0') >= buttons)
690                                         doc_next(p->parent, ci->mark);
691                 }
692         } else {
693                 ret = home_call(p->parent, ci->key, ci->focus,
694                                 ci->num, ci->mark, evi->invis, 1);
695                 n = get_part(p->parent, ci->mark);
696                 if ((n & 1) && ci->num2 && isdigit(ret & 0xfffff)) {
697                         /* Just stepped back over the 9 at the end of a spacer,
698                          * Maybe step further if there aren't 10 buttons.
699                          */
700                         unsigned int buttons = count_buttons(p, ci->mark);
701                         wint_t c = ret & 0xfffff;
702
703                         while (isdigit(c) && c - '0' >= buttons)
704                                 c = doc_prev(p->parent, ci->mark);
705                         ret = CHAR_RET(c);
706                 }
707         }
708         return ret;
709 }
710
711 DEF_CMD(email_set_ref)
712 {
713         struct pane *p = ci->home;
714         struct email_view *evi = p->data;
715
716         if (!ci->mark)
717                 return Enoarg;
718         home_call(p->parent, ci->key, ci->focus, ci->num, ci->mark, evi->invis);
719         return 1;
720 }
721
722 DEF_CMD(email_view_get_attr)
723 {
724         int p, v;
725         struct email_view *evi = ci->home->data;
726
727         if (!ci->str || !ci->mark)
728                 return Enoarg;
729         if (strcmp(ci->str, "email:visible") == 0) {
730                 p = get_part(ci->home->parent, ci->mark);
731                 /* only parts can be invisible, not separators */
732                 p &= ~1;
733                 v = (p >= 0 && p < evi->parts) ? evi->invis[p] != 'i' : 0;
734
735                 return comm_call(ci->comm2, "callback", ci->focus, 0, ci->mark,
736                                  v ? "1":"0", 0, NULL, ci->str);
737         }
738         return Efallthrough;
739 }
740
741 DEF_CMD(email_view_set_attr)
742 {
743         int p, v;
744         struct email_view *evi = ci->home->data;
745
746         if (!ci->str || !ci->mark)
747                 return Enoarg;
748         if (strcmp(ci->str, "email:visible") == 0) {
749                 struct mark *m1, *m2;
750
751                 p = get_part(ci->home->parent, ci->mark);
752                 /* only parts can be invisible, not separators */
753                 p &= ~1;
754                 v = ci->str2 && atoi(ci->str2) >= 1;
755                 if (p >= 0 && p < evi->parts)
756                         evi->invis[p] = v ? 'v' : 'i';
757
758                 /* Tell viewers that visibility has changed */
759                 m1 = mark_dup(ci->mark);
760                 home_call(ci->home->parent, "doc:step-part", ci->focus,
761                           0, m1);
762                 if (get_part(ci->home->parent, m1) != p)
763                         home_call(ci->home->parent, "doc:step-part",
764                                   ci->focus, -1, m1);
765
766                 mark_step(m1, 0);
767                 m2 = mark_dup(m1);
768                 home_call(ci->home->parent, "doc:step-part", ci->focus,
769                           1, m2);
770                 call("view:changed", ci->focus, 0, m1, NULL, 0, m2);
771                 call("Notify:clip", ci->focus, 0, m1, NULL, 0, m2);
772                 mark_free(m1);
773                 mark_free(m2);
774
775                 return 1;
776         }
777         return Efallthrough;
778 }
779
780 DEF_CMD(attach_email_view)
781 {
782         struct pane *p;
783         struct email_view *evi;
784         struct mark *m;
785         int n;
786
787         m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
788         if (!m)
789                 return Efail;
790         call("doc:set-ref", ci->focus, 0, m);
791         n = get_part(ci->focus, m);
792         mark_free(m);
793         if (n <= 0 || n > 1000 )
794                 return Einval;
795
796         alloc(evi, pane);
797         evi->parts = n;
798         evi->invis = calloc(n+1, sizeof(char));
799         memset(evi->invis, 'v', n);
800         p = pane_register(ci->focus, 0, &email_view_handle.c, evi);
801         if (!p) {
802                 free(evi);
803                 return Efail;
804         }
805         return comm_call(ci->comm2, "callback:attach", p);
806 }
807
808 static void email_init_map(void)
809 {
810         email_view_map = key_alloc();
811         key_add(email_view_map, "Free", &email_view_free);
812         key_add(email_view_map, "doc:step", &email_step);
813         key_add(email_view_map, "doc:set-ref", &email_set_ref);
814         key_add(email_view_map, "doc:set-attr", &email_view_set_attr);
815         key_add(email_view_map, "doc:get-attr", &email_view_get_attr);
816         key_add(email_view_map, "doc:email:render-spacer", &email_spacer);
817         key_add(email_view_map, "doc:email:select", &email_select);
818 }
819
820 void edlib_init(struct pane *ed safe)
821 {
822         email_init_map();
823         call_comm("global-set-command", ed, &open_email, 0, NULL,
824                   "open-doc-email");
825         call_comm("global-set-command", ed, &attach_email_view, 0, NULL,
826                   "attach-email-view");
827
828         call("global-load-module", ed, 0, NULL, "lib-html-to-text");
829 }