]> git.neil.brown.name Git - edlib.git/blob - doc-email.c
email: user windows-1251 as default charset.
[edlib.git] / doc-email.c
1 /*
2  * Copyright Neil Brown ©2016-2021 <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 from sets of three documents.
9  * In each set:
10  * - The first is the original email, overlayed with 'crop' to select
11  *   one section, then overlayed with handlers for the transfer-encoding
12  *   and (optionally) charset.  There will be one for the headers
13  *   and either one for the body, or one for each part of the body
14  *   is multipart.  All nested multiparts have their parts linearized.
15  * - The second is a scratch text document which can contain a transformed
16  *   copy of the content when the tranformation is too complex for an
17  *   overlay layer.  This includes HTML and PDF which can be converted
18  *   to text, but the conversion is complex, and the headers, which need to be
19  *   re-ordered as well as filtered and decoded.  For images, this document has
20  *   trivial content but specifier a rendering function that displays the image.
21  * - The final section is a 'spacer' which has fixed content and displays
22  *   as a summary line for the part (e.g. MIME-type, file name) and
23  *   provides active buttons for acting on the content (save, external viewer
24  *   etc).
25  *
26  * The middle part has attributes set on the document which can be
27  * accessed form the spacer using "multipart-prev:"
28  * - email:path identify the part in the nexted multipart struture
29  *   e.g. "header", "body", "body,multipart/mixed:0,mulitpart/alternate:1"
30  * - email:actions a ':' separated list of buttons. "hide:save:view"
31  * - email:content-type the MIME type of the content. "image/png"
32  *
33  */
34
35 #define _GNU_SOURCE /* for asprintf */
36 #include <unistd.h>
37 #include <stdlib.h>
38 #include <fcntl.h>
39 #include <string.h>
40 #include <wctype.h>
41 #include <ctype.h>
42 #include <stdio.h>
43 #include "core.h"
44 #include "misc.h"
45
46 static inline bool is_orig(int p)
47 {
48         return p >= 0 && p % 3 == 0;
49 }
50
51 static inline bool is_transformed(int p)
52 {
53         return p >= 0 && p % 3 == 1;
54 }
55
56 static inline bool is_spacer(int p)
57 {
58         return p >= 0 && p % 3 == 2;
59 }
60
61 static inline int to_orig(int p)
62 {
63         if (p < 0)
64                 return p;
65         return p - p % 3;
66 }
67
68 struct email_info {
69         struct pane     *email safe;
70         struct pane     *spacer safe;
71 };
72
73 static bool handle_content(struct pane *p safe, char *type, char *xfer,
74                            struct mark *start safe, struct mark *end safe,
75                            struct pane *mp safe, struct pane *spacer safe,
76                            char *path safe);
77
78 static bool cond_append(struct buf *b safe, char *txt safe, char *tag safe,
79                         int offset, int *cp safe)
80 {
81         char *tagf = "active-tag:email-";
82         int prelen = 1 + strlen(tagf) + strlen(tag) + 1 + 1;
83         int postlen = 1 + 3;
84         int len = prelen + strlen(txt) + postlen;
85         if (offset != NO_NUMERIC && offset >= 0 && offset <= b->len + len)
86                 return False;
87         buf_concat(b, "<");
88         buf_concat(b, tagf);
89         buf_concat(b, tag);
90         buf_concat(b, ">[");
91         if (*cp == 0)
92                 return False;
93         if (*cp > 0)
94                 *cp -= 1;
95         buf_concat(b, txt);
96         buf_concat(b, "]</>");
97         return True;
98 }
99
100 static bool is_attr(char *a safe, char *attrs safe)
101 {
102         int l = strlen(a);
103         if (strncmp(a, attrs, l) != 0)
104                 return False;
105         if (attrs[l] == ':' || attrs[l] == '\0')
106                 return True;
107         return False;
108 }
109
110 DEF_CMD(email_spacer)
111 {
112         struct buf b;
113         int visible = 1;
114         struct mark *m = ci->mark;
115         struct mark *pm = ci->mark2;
116         int o = ci->num;
117         int cp = -1;
118         char *attr;
119         int ret;
120         bool ok = True;
121
122         if (!m)
123                 return Enoarg;
124         if (pm) {
125                 /* Count the number of chars before the cursor.
126                  * This tells us which button to highlight.
127                  */
128                 cp = 0;
129                 pm = mark_dup(pm);
130                 while (pm->seq > m->seq && !mark_same(pm, m)) {
131                         doc_prev(ci->focus, pm);
132                         cp += 1;
133                 }
134                 mark_free(pm);
135         }
136
137         buf_init(&b);
138         buf_concat(&b, "<fg:red>");
139
140         attr = pane_mark_attr(ci->home, m, "multipart-prev:email:path");
141         if (attr) {
142                 buf_concat(&b, attr);
143                 buf_concat(&b, " ");
144         }
145
146         attr = pane_mark_attr(ci->focus, m, "email:visible");
147         if (attr && strcmp(attr, "none") == 0)
148                 visible = 0;
149         attr = pane_mark_attr(ci->home, m, "multipart-prev:email:actions");
150         if (!attr)
151                 attr = "hide";
152
153         while (ok && attr && *attr) {
154                 char *a = strchr(attr, ':');
155                 if (a)
156                         *a = 0;
157                 if (is_attr("hide", attr))
158                         ok = cond_append(&b, visible ? "HIDE" : "SHOW", attr,
159                                          o, &cp);
160                 else
161                         ok = cond_append(&b, attr, attr, o, &cp);
162                 if (ok)
163                         doc_next(ci->focus, m);
164                 attr = a;
165                 if (attr)
166                         *attr++ = ':';
167         }
168         /* end of line, only display if we haven't reached
169          * the cursor or offset
170          *
171          * if cp < 0, we aren't looking for a cursor, so don't stop.
172          * if cp > 0, we haven't reached cursor yet, so don't stop
173          * if cp == 0, this is cursor pos, so stop.
174          */
175         if (ok && cp != 0 && ((o < 0 || o == NO_NUMERIC))) {
176                 wint_t wch;
177                 buf_concat(&b, "</>");
178                 attr = pane_mark_attr(ci->focus, m,
179                                       "multipart-prev:email:content-type");
180                 if (attr) {
181                         buf_concat(&b, " ");
182                         buf_concat(&b, attr);
183                 }
184                 attr = pane_mark_attr(ci->focus, m,
185                                       "multipart-prev:email:charset");
186                 if (attr) {
187                         buf_concat(&b, " ");
188                         buf_concat(&b, attr);
189                 }
190                 attr = pane_mark_attr(ci->focus, m,
191                                       "multipart-prev:email:filename");
192                 if (attr) {
193                         buf_concat(&b, " file=");
194                         buf_concat(&b, attr);
195                 }
196                 buf_concat(&b, "\n");
197                 while ((wch = doc_next(ci->focus, m)) &&
198                        wch != '\n' && wch != WEOF)
199                         ;
200         }
201
202         ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL,
203                         buf_final(&b));
204         free(b.b);
205         return ret;
206 }
207
208 static int get_part(struct pane *p safe, struct mark *m safe)
209 {
210         char *a = pane_mark_attr(p, m, "multipart:part-num");
211
212         if (!a)
213                 return Efail;
214         return atoi(a);
215 }
216
217 DEF_CMD(email_image)
218 {
219         char *c = NULL;
220         int p;
221         int ret;
222
223         if (!ci->mark)
224                 return Enoarg;
225         p = get_part(ci->home, ci->mark);
226         doc_next(ci->focus, ci->mark);
227         asprintf(&c, "<image:comm:doc:multipart-%d-doc:get-bytes>\n", to_orig(p));
228         ret = comm_call(ci->comm2, "callback:render", ci->focus,
229                         0, NULL, c);
230         free(c);
231         return ret;
232 }
233
234 DEF_CMD(email_select)
235 {
236         /* If mark is on a button, press it... */
237         struct mark *m = ci->mark;
238         char *a, *e, *cmd = NULL;
239         wint_t ch;
240         int p;
241         int b;
242
243         if (!m)
244                 return Enoarg;
245         p = get_part(ci->home, m);
246         if (!is_spacer(p))
247                 return Efallthrough;
248         ch = doc_following(ci->home, m);
249         if (ch == WEOF || !isdigit(ch))
250                 return 1;
251         b = ch - '0';
252         a = pane_mark_attr(ci->focus, m, "multipart-prev:email:actions");
253         if (!a)
254                 a = "hide";
255         while (b > 0 && a) {
256                 a = strchr(a, ':');
257                 if (a)
258                         a += 1;
259                 b -= 1;
260         }
261         if (!a)
262                 return 1;
263         e = strchr(a, ':');
264         if (!e)
265                 e = a + strlen(a);
266         asprintf(&cmd, "email:select:%.*s", (int)(e-a), a);
267         if (!cmd)
268                 return Efail;
269         return call(cmd, ci->focus, 0, m);
270 }
271
272 DEF_CMD(email_select_hide)
273 {
274         int vis = 1;
275         char *a;
276         struct mark *m = ci->mark;
277
278         if (!m)
279                 return Enoarg;
280
281         a = pane_mark_attr(ci->focus, m, "email:visible");
282         if (a && strcmp(a, "none") == 0)
283                 vis = 0;
284         call("doc:set-attr", ci->focus, 1, m, "email:visible", 0, NULL,
285              vis ? "none" : "preferred");
286         return 1;
287 }
288
289 static struct map *email_view_map safe;
290
291 DEF_LOOKUP_CMD(email_view_handle, email_view_map);
292
293 static char tspecials[] = "()<>@,;:\\\"/[]?=";
294
295 static int lws(char c) {
296         return c == ' '  || c == '\t' || c == '\r' || c == '\n';
297 }
298
299 static char *get_822_token(char **hdrp safe, int *len safe)
300 {
301         /* A "token" is one of:
302          * - Quoted string ""
303          * - single char from tspecials (except '(' or '"')
304          * - string of non-LWS, and non-tspecials
305          *
306          * (comments) are skipped.
307          * Start is returned, hdrp is moved, len is reported.
308          */
309         char *hdr = *hdrp;
310         char *start;
311         *len = 0;
312         if (!hdr)
313                 return NULL;
314         while (1) {
315                 while (lws(*hdr))
316                         hdr++;
317                 if (*hdr == '(') {
318                         while (*hdr && *hdr != ')')
319                                 hdr++;
320                         continue;
321                 }
322                 if (*hdr == '"') {
323                         start = ++hdr;
324                         while (*hdr && *hdr != '"')
325                                 hdr++;
326                         *len = hdr - start;
327                         hdr++;
328                         *hdrp = hdr;
329                         return start;
330                 }
331                 if (!*hdr) {
332                         *hdrp = NULL;
333                         return NULL;
334                 }
335                 if (strchr(tspecials, *hdr)) {
336                         start = hdr;
337                         hdr++;
338                         *len = 1;
339                         *hdrp = hdr;
340                         return start;
341                 }
342                 start = hdr;
343                 while (*hdr && !lws(*hdr) && !strchr(tspecials, *hdr))
344                         hdr++;
345                 *len = hdr - start;
346                 *hdrp = hdr;
347                 return start;
348         }
349 }
350
351 static char *get_822_attr(char *shdr safe, char *attr safe)
352 {
353         /* If 'hdr' contains "$attr=...", return "..."
354          * with "quotes" stripped.  Return value can be used
355          * until the next call, when it will be free.
356          */
357         int len, alen;
358         char *hdr = shdr;
359         char *h;
360         static char *last = NULL;
361
362         free(last);
363         last = NULL;
364
365         alen = strlen(attr);
366         while (hdr) {
367                 while ((h = get_822_token(&hdr, &len)) != NULL &&
368                        (len != alen || strncasecmp(h, attr, alen) != 0))
369                         ;
370                 h = get_822_token(&hdr, &len);
371                 if (!h || len != 1 || *h != '=')
372                         continue;
373                 h = get_822_token(&hdr, &len);
374                 if (!h)
375                         continue;
376                 last = strndup(h, len);
377                 return last;
378         }
379         return NULL;
380 }
381
382 static char *get_822_word(char *hdr safe)
383 {
384         /* Get the first word from header, in static
385          * space (freed on next call)
386          */
387         static char *last = NULL;
388         int len;
389         char *h;
390
391         free(last);
392         last = NULL;
393         h = get_822_token(&hdr, &len);
394         if (!h)
395                 return h;
396         last = strndup(h, len);
397         return last;
398 }
399
400 static bool tok_matches(char *tok, int len, char *match safe)
401 {
402         if (!tok)
403                 return False;
404         if (len != (int)strlen(match))
405                 return False;
406         return strncasecmp(tok, match, len) == 0;
407 }
408
409 static bool handle_text(struct pane *p safe, char *type, char *xfer,
410                         struct mark *start safe, struct mark *end safe,
411                         struct pane *mp safe, struct pane *spacer safe,
412                         char *path)
413 {
414         struct pane *h, *transformed = NULL;
415         int need_charset = 0;
416         char *charset = NULL;
417         char *major, *minor = NULL;
418         int majlen, minlen;
419         char *ctype = NULL;
420         char *fname = NULL;
421
422         h = call_ret(pane, "attach-crop", p, 0, start, NULL, 0, end);
423         if (!h)
424                 return False;
425
426         if (xfer) {
427                 int xlen;
428                 xfer = get_822_token(&xfer, &xlen);
429                 if (xfer && xlen == 16 &&
430                     strncasecmp(xfer, "quoted-printable", 16) == 0) {
431                         struct pane *hx = call_ret(pane,
432                                                    "attach-quoted_printable",
433                                                    h);
434                         if (hx) {
435                                 h = hx;
436                                 need_charset = 1;
437                         }
438                 }
439                 if (xfer && xlen == 6 &&
440                     strncasecmp(xfer, "base64", 6) == 0) {
441                         struct pane *hx = call_ret(pane, "attach-base64", h);
442                         if (hx) {
443                                 h = hx;
444                                 need_charset = 1;
445                         }
446                 }
447         }
448         if (type && need_charset &&
449             (charset = get_822_attr(type, "charset")) != NULL) {
450                 char *c = NULL, *cp;
451                 struct pane *hx = NULL;
452                 charset = strsave(h, charset);
453                 asprintf(&c, "attach-charset-%s", charset);
454                 for (cp = c; cp && *cp; cp++)
455                         if (isupper(*cp))
456                                 *cp = tolower(*cp);
457                 if (c)
458                         hx = call_ret(pane, c, h);
459                 free(c);
460                 if (!hx)
461                         /* windows-1251 is safer than utf-8 as the latter
462                          * rejects some byte sequences, and iso-8859-* has
463                          * lots of control characters.
464                          */
465                         hx = call_ret(pane, "attach-charset-windows-1251", h);
466                 if (hx)
467                         h = hx;
468         }
469         if (type && (fname = get_822_attr(type, "name")))
470                 fname = strsave(h, fname);
471         major = get_822_token(&type, &majlen);
472         if (major) {
473                 minor = get_822_token(&type, &minlen);
474                 if (minor && tok_matches(minor, minlen, "/"))
475                         minor = get_822_token(&type, &minlen);
476                 else
477                         minor = NULL;
478         }
479         if (minor)
480                 asprintf(&ctype, "%1.*s/%1.*s", majlen, major, minlen, minor);
481         else
482                 asprintf(&ctype, "%1.*s", majlen, major);
483         if (ctype && strcmp(ctype, "text/html") == 0)
484                 transformed = call_ret(pane, "html-to-text", h);
485         if (ctype && strcmp(ctype, "application/pdf") == 0)
486                 transformed = call_ret(pane, "pdf-to-text", h);
487         if (ctype && strcmp(ctype, "application/octet-stream") == 0 &&
488             fname && (strstr(fname, ".pdf") == NULL ||
489                       strstr(fname, ".PDF") == NULL))
490                 transformed = call_ret(pane, "pdf-to-text", h);
491         if (ctype && strncmp(ctype, "image/", 6) == 0) {
492                 struct mark *m;
493                 transformed = call_ret(pane, "doc:from-text", h,
494                                        0, NULL, NULL, 0, NULL, "\n");
495                 if (transformed) {
496                         m = vmark_new(transformed, MARK_UNGROUPED, NULL);
497                         call("doc:set-ref", transformed, 1, m);
498                         call("doc:set-attr", transformed, 1, m, "markup:func", 0,
499                              NULL, "doc:email:render-image");
500                         mark_free(m);
501                 }
502         }
503         if (transformed) {
504                 attr_set_str(&transformed->attrs, "email:is_transformed", "yes");
505                 attr_set_str(&transformed->attrs, "email:preferred", "transformed");
506         } else {
507                 transformed = call_ret(pane, "doc:from-text", h,
508                                        0, NULL, NULL, 0, NULL, "\n");
509                 if (transformed)
510                         attr_set_str(&transformed->attrs, "email:preferred",
511                                      "orig");
512         }
513         if (!transformed) {
514                 pane_close(h);
515                 return False;
516         }
517         call("doc:set:autoclose", transformed, 1);
518         if (ctype) {
519                 int i;
520                 for (i = 0; ctype[i]; i++)
521                         if (isupper(ctype[i]))
522                                 ctype[i] = tolower(ctype[i]);
523                 attr_set_str(&transformed->attrs, "email:content-type", ctype);
524                 free(ctype);
525         } else
526                 attr_set_str(&h->attrs, "email:content-type", "text/plain");
527         attr_set_str(&transformed->attrs, "email:actions", "hide:save");
528         attr_set_str(&transformed->attrs, "email:path", path);
529         attr_set_str(&transformed->attrs, "email:which", "transformed");
530         if (charset)
531                 attr_set_str(&transformed->attrs, "email:charset", charset);
532         if (fname)
533                 attr_set_str(&transformed->attrs, "email:filename", fname);
534         attr_set_str(&h->attrs, "email:which", "orig");
535
536         home_call(mp, "multipart-add", h);
537         home_call(mp, "multipart-add", transformed);
538         home_call(mp, "multipart-add", spacer);
539         return True;
540 }
541
542 /* Find a multipart boundary between start and end, moving
543  * 'start' to after the boundary, and 'pos' to just before it.
544  * Return 0 if a non-terminal boundary is found
545  * Return 1 if a terminal boundary is found (trailing --)
546  * Return -1 if nothing is found.
547  */
548 #define is_lws(c) ({int __c2 = c; __c2 == ' ' || __c2 == '\t' || is_eol(__c2); })
549 static int find_boundary(struct pane *p safe,
550                          struct mark *start safe, struct mark *end safe,
551                          struct mark *pos,
552                          char *boundary safe)
553 {
554         char *patn = NULL;
555         int ret;
556         int len = strlen(boundary);
557
558         asprintf(&patn, "^--(?%d:%s)(--)?[ \\t\\r]*$", len, boundary);
559         ret = call("text-search", p, 0, start, patn, 0, end);
560         if (ret <= 0)
561                 return -1;
562         ret -= 1;
563         if (pos) {
564                 int cnt = ret;
565                 mark_to_mark(pos, start);
566                 while (cnt > 0 && doc_prev(p, pos) != WEOF)
567                         cnt -= 1;
568                 /* Previous char is CRLF, and must be swallowed */
569                 if (doc_prior(p, pos) == '\n')
570                         doc_prev(p, pos);
571                 if (doc_prior(p, pos) == '\r')
572                         doc_prev(p, pos);
573         }
574         while (is_lws(doc_prior(p, start))) {
575                 len -= 1;
576                 doc_prev(p, start);
577         }
578         while (is_lws(doc_following(p, start)))
579                 doc_next(p, start);
580         if (ret == 2 + len)
581                 return 0;
582         if (ret == 2 + len + 2)
583                 return 1;
584         return -1;
585 }
586
587 static bool handle_multipart(struct pane *p safe, char *type safe,
588                              struct mark *start safe, struct mark *end safe,
589                              struct pane *mp safe, struct pane *spacer safe,
590                              char *path safe)
591 {
592         char *boundary = get_822_attr(type, "boundary");
593         int found_end = 0;
594         struct mark *pos, *part_end;
595         char *tok;
596         int len;
597         int partnum = 0;
598         char *newpath;
599
600         if (!boundary)
601                 /* FIXME need a way to say "just display the text" */
602                 return True;
603
604         found_end = find_boundary (p, start, end, NULL, boundary);
605         if (found_end != 0)
606                 return True;
607         tok = get_822_token(&type, &len);
608         if (tok) {
609                 tok = get_822_token(&type, &len);
610                 if (tok && tok[0] == '/')
611                         tok = get_822_token(&type, &len);
612         }
613         boundary = strdup(boundary);
614         pos = mark_dup(start);
615         part_end = mark_dup(pos);
616         while (found_end == 0 &&
617                (found_end = find_boundary(p, pos, end, part_end,
618                                           boundary)) >= 0) {
619                 struct pane *hdr = call_ret(pane, "attach-rfc822header", p,
620                                             0, start, NULL,
621                                             0, part_end);
622                 char *ptype, *pxfer;
623
624                 if (!hdr)
625                         break;
626                 call("get-header", hdr, 0, NULL, "content-type",
627                      0, NULL, "cmd");
628                 call("get-header", hdr, 0, NULL, "content-transfer-encoding",
629                      0, NULL, "cmd");
630                 ptype = attr_find(hdr->attrs, "rfc822-content-type");
631                 pxfer = attr_find(hdr->attrs,
632                                   "rfc822-content-transfer-encoding");
633
634                 pane_close(hdr);
635
636                 newpath = NULL;
637                 asprintf(&newpath, "%s%s%1.*s:%d", path, path[0] ? ",":"",
638                          len, tok, partnum);
639                 partnum++;
640
641                 handle_content(p, ptype, pxfer, start, part_end, mp, spacer,
642                                newpath ?:"");
643                 free(newpath);
644                 mark_to_mark(start, pos);
645         }
646         mark_to_mark(start, pos);
647         mark_free(pos);
648         mark_free(part_end);
649         free(boundary);
650         return True;
651 }
652
653 static bool handle_content(struct pane *p safe, char *type, char *xfer,
654                            struct mark *start safe, struct mark *end safe,
655                            struct pane *mp safe, struct pane *spacer safe,
656                            char *path safe)
657 {
658         char *hdr;
659         char *major, *minor = NULL;
660         int mjlen, mnlen;
661
662         if (!type)
663                 type = "text/plain";
664         hdr = type;
665
666         major = get_822_token(&hdr, &mjlen);
667         if (major) {
668                 minor = get_822_token(&hdr, &mnlen);
669                 if (minor && minor[0] == '/')
670                         minor = get_822_token(&hdr, &mnlen);
671         }
672         if (major == NULL ||
673             tok_matches(major, mjlen, "text"))
674                 return handle_text(p, type, xfer, start, end,
675                                    mp, spacer, path);
676
677         if (tok_matches(major, mjlen, "multipart"))
678                 return handle_multipart(p, type, start, end, mp, spacer, path);
679
680         /* default to plain text until we get a better default */
681         return handle_text(p, type, xfer, start, end, mp, spacer, path);
682 }
683
684 DEF_CMD(open_email)
685 {
686         int fd;
687         struct email_info *ei;
688         struct mark *start, *end;
689         struct pane *p, *h2;
690         char *mime;
691         char *xfer = NULL, *type = NULL;
692         struct pane *hdrdoc;
693         struct mark *point;
694
695         if (ci->str == NULL ||
696             strncmp(ci->str, "email:", 6) != 0)
697                 return Efallthrough;
698         fd = open(ci->str+6, O_RDONLY);
699         p = call_ret(pane, "doc:open", ci->focus, fd, NULL, ci->str + 6, 1);
700         if (!p)
701                 return Efallthrough;
702         start = vmark_new(p, MARK_UNGROUPED, NULL);
703         if (!start)
704                 return Efallthrough;
705         end = mark_dup(start);
706         call("doc:set-ref", p, 0, end);
707
708         alloc(ei, pane);
709         ei->email = p;
710         h2 = call_ret(pane, "attach-rfc822header", p, 0, start, NULL, 0, end);
711         if (!h2)
712                 goto out;
713         attr_set_str(&h2->attrs, "email:which", "orig");
714         p = call_ret(pane, "doc:from-text", p, 0, NULL, NULL, 0, NULL,
715                      "0123456789\n");
716         if (!p) {
717                 pane_close(h2);
718                 goto out;
719         }
720         attr_set_str(&p->attrs, "email:which", "spacer");
721         ei->spacer = p;
722         point = vmark_new(p, MARK_POINT, NULL);
723         call("doc:set-ref", p, 1, point);
724         call("doc:set-attr", p, 1, point, "markup:func", 0,
725              NULL, "doc:email:render-spacer");
726         mark_free(point);
727
728         hdrdoc = call_ret(pane, "attach-doc-text", ci->focus);
729         if (!hdrdoc)
730                 goto out;
731         call("doc:set:autoclose", hdrdoc, 1);
732         point = vmark_new(hdrdoc, MARK_POINT, NULL);
733         if (!point)
734                 goto out;
735
736         /* copy some headers to the header temp document */
737         home_call(h2, "get-header", hdrdoc, 0, point, "From");
738         home_call(h2, "get-header", hdrdoc, 0, point, "Date");
739         home_call(h2, "get-header", hdrdoc, 0, point, "Subject", 0, NULL, "text");
740         home_call(h2, "get-header", hdrdoc, 0, point, "To", 0, NULL, "list");
741         home_call(h2, "get-header", hdrdoc, 0, point, "Cc", 0, NULL, "list");
742
743         /* copy some headers into attributes for later analysis */
744         call("get-header", h2, 0, NULL, "MIME-Version", 0, NULL, "cmd");
745         call("get-header", h2, 0, NULL, "content-type", 0, NULL, "cmd");
746         call("get-header", h2, 0, NULL, "content-transfer-encoding",
747              0, NULL, "cmd");
748         mime = attr_find(h2->attrs, "rfc822-mime-version");
749         if (mime)
750                 mime = get_822_word(mime);
751         if (mime && strcmp(mime, "1.0") == 0) {
752                 type = attr_find(h2->attrs, "rfc822-content-type");
753                 xfer = attr_find(h2->attrs, "rfc822-content-transfer-encoding");
754         }
755
756         p = call_ret(pane, "attach-doc-multipart", ci->home);
757         if (!p)
758                 goto out;
759         call("doc:set:autoclose", p, 1);
760         attr_set_str(&hdrdoc->attrs, "email:actions", "hide");
761         attr_set_str(&hdrdoc->attrs, "email:which", "transformed");
762         attr_set_str(&hdrdoc->attrs, "email:content-type", "text/rfc822-headers");
763         attr_set_str(&hdrdoc->attrs, "email:path", "headers");
764         attr_set_str(&hdrdoc->attrs, "email:is_transformed", "yes");
765         home_call(p, "multipart-add", h2);
766         home_call(p, "multipart-add", hdrdoc);
767         home_call(p, "multipart-add", ei->spacer);
768
769         if (!handle_content(ei->email, type, xfer, start, end,
770                             p, ei->spacer, "body"))
771                 goto out;
772
773         mark_free(start);
774         mark_free(end);
775         attr_set_str(&p->attrs, "render-default", "text");
776         attr_set_str(&p->attrs, "filename", ci->str+6);
777         attr_set_str(&p->attrs, "doc-type", "email");
778         return comm_call(ci->comm2, "callback:attach", p);
779
780 out:
781         mark_free(start);
782         mark_free(end);
783         free(ei);
784         // FIXME free stuff
785         return Efail;
786 }
787
788 struct email_view {
789         int     parts;
790         char    *invis safe;
791 };
792
793 DEF_CMD(email_view_free)
794 {
795         struct email_view *evi = ci->home->data;
796
797         free(evi->invis);
798         unalloc(evi, pane);
799         return 1;
800 }
801
802 static int count_buttons(struct pane *p safe, struct mark *m safe)
803 {
804         int cnt = 0;
805         char *attr = pane_mark_attr(p, m, "multipart-prev:email:actions");
806         if (!attr)
807                 attr = "hide";
808         while (attr) {
809                 cnt += 1;
810                 attr = strchr(attr, ':');
811                 if (attr)
812                         attr++;
813         }
814         return cnt;
815 }
816
817 DEF_CMD(email_step)
818 {
819         struct pane *p = ci->home;
820         struct email_view *evi = p->data;
821         wint_t ret;
822         int n = -1;
823
824         if (!ci->mark)
825                 return Enoarg;
826         if (ci->num) {
827                 ret = home_call(p->parent, ci->key, ci->focus,
828                                 ci->num, ci->mark, evi->invis,
829                                 ci->num2);
830                 n = get_part(p->parent, ci->mark);
831                 if (ci->num2 && is_spacer(n)) {
832                         /* Moving in a spacer, If after valid buttons,
833                          * move to end
834                          */
835                         wint_t c;
836                         unsigned int buttons;
837                         buttons = count_buttons(p, ci->mark);
838                         while ((c = doc_following(p->parent, ci->mark)) != WEOF
839                                && iswdigit(c) && (c - '0') >= buttons)
840                                         doc_next(p->parent, ci->mark);
841                 }
842         } else {
843                 ret = home_call(p->parent, ci->key, ci->focus,
844                                 ci->num, ci->mark, evi->invis, ci->num2);
845                 n = get_part(p->parent, ci->mark);
846                 if (is_spacer(n) && ci->num2 &&
847                     ret != CHAR_RET(WEOF) && iswdigit(ret & 0x1fffff)) {
848                         /* Just stepped back over the 9 at the end of a spacer,
849                          * Maybe step further if there aren't 10 buttons.
850                          */
851                         unsigned int buttons = count_buttons(p, ci->mark);
852                         wint_t c = ret & 0x1fffff;
853
854                         while (c != WEOF && iswdigit(c) && c - '0' >= buttons)
855                                 c = doc_prev(p->parent, ci->mark);
856                         ret = CHAR_RET(c);
857                 }
858         }
859         return ret;
860 }
861
862 DEF_CMD(email_content)
863 {
864         /* Call the multipart doc:content telling in
865          * what is invisible, marking all spacers as invisible
866          */
867         struct pane *p = ci->home;
868         struct email_view *evi = p->data;
869         char *invis2 = strsave(p, evi->invis);
870         int i;
871
872         for (i = 0; invis2 && invis2[i]; i++)
873                 if (is_spacer(i))
874                         invis2[i] = 'i';
875         return home_call(p->parent, ci->key, p,
876                          ci->num, ci->mark, invis2,
877                          ci->num2, ci->mark2, ci->str2,
878                          ci->x, ci->y, ci->comm2);
879 }
880
881 DEF_CMD(email_set_ref)
882 {
883         struct pane *p = ci->home;
884         struct email_view *evi = p->data;
885
886         if (!ci->mark)
887                 return Enoarg;
888         home_call(p->parent, ci->key, ci->focus, ci->num, ci->mark, evi->invis);
889         return 1;
890 }
891
892 DEF_CMD(email_view_get_attr)
893 {
894         int p;
895         char *v;
896         struct email_view *evi = ci->home->data;
897
898         if (!ci->str || !ci->mark)
899                 return Enoarg;
900         if (strcmp(ci->str, "email:visible") == 0) {
901                 p = get_part(ci->home->parent, ci->mark);
902                 /* only parts can be invisible, not separators */
903                 p = to_orig(p);
904                 if (p < 0 || p >= evi->parts)
905                         v = "none";
906                 else if (evi->invis[p] != 'i')
907                         v = "orig";
908                 else if (evi->invis[p+1] != 'i')
909                         v = "transformed";
910                 else
911                         v = "none";
912
913                 return comm_call(ci->comm2, "callback", ci->focus, 0, ci->mark,
914                                  v, 0, NULL, ci->str);
915         }
916         return Efallthrough;
917 }
918
919 DEF_CMD(email_view_set_attr)
920 {
921         int p;
922         struct email_view *evi = ci->home->data;
923
924         if (!ci->str || !ci->mark)
925                 return Enoarg;
926         if (strcmp(ci->str, "email:visible") == 0) {
927                 struct mark *m1, *m2;
928                 const char *w;
929
930                 p = get_part(ci->home->parent, ci->mark);
931                 /* only parts can be invisible, not separators */
932                 p = to_orig(p);
933                 if (p < 0 || p >= evi->parts)
934                         return Efail;
935
936                 m1 = mark_dup(ci->mark);
937                 while (get_part(ci->home->parent, m1) > p &&
938                        home_call(ci->home->parent, "doc:step-part",
939                                  ci->focus, -1, m1) > 0)
940                         ;
941
942                 w = ci->str2;
943                 if (w && strcmp(w, "preferred") == 0) {
944                         w = pane_mark_attr(ci->focus, m1,
945                                            "multipart-next:email:preferred");
946                         if (!w)
947                                 w = "orig";
948                 } else if (w && (strcmp(w, "orig") == 0 ||
949                                  strcmp(w, "transformed") == 0)) {
950                         call("doc:set-attr", ci->focus, 1, m1,
951                              "multipart-next:email:preferred", 0, NULL, w);
952                 }
953                 evi->invis[p] = 'i';
954                 evi->invis[p+1] = 'i';
955                 if (w && strcmp(w, "orig") == 0)
956                         evi->invis[p] = 'v';
957                 if (w && strcmp(w, "transformed") == 0)
958                         evi->invis[p+1] = 'v';
959
960                 /* Tell viewers that visibility has changed */
961                 mark_step(m1, 0);
962                 m2 = mark_dup(m1);
963                 home_call(ci->home->parent, "doc:step-part", ci->focus,
964                           1, m2);
965                 home_call(ci->home->parent, "doc:step-part", ci->focus,
966                           1, m2);
967                 call("view:changed", ci->focus, 0, m1, NULL, 0, m2);
968                 call("Notify:clip", ci->focus, 0, m1, NULL, 0, m2);
969                 mark_free(m1);
970                 mark_free(m2);
971
972                 return 1;
973         }
974         return Efallthrough;
975 }
976
977 DEF_CMD(attach_email_view)
978 {
979         struct pane *p;
980         struct email_view *evi;
981         struct mark *m;
982         int n;
983
984         m = vmark_new(ci->focus, MARK_UNGROUPED, NULL);
985         if (!m)
986                 return Efail;
987         call("doc:set-ref", ci->focus, 0, m);
988         n = get_part(ci->focus, m);
989         mark_free(m);
990         if (n <= 0 || n > 1000 )
991                 return Einval;
992
993         alloc(evi, pane);
994         evi->parts = n;
995         evi->invis = calloc(n+1, sizeof(char));
996         memset(evi->invis, 'v', n);
997         p = pane_register(ci->focus, 0, &email_view_handle.c, evi);
998         if (!p) {
999                 free(evi);
1000                 return Efail;
1001         }
1002         attr_set_str(&p->attrs, "render-hide-CR", "yes");
1003         return comm_call(ci->comm2, "callback:attach", p);
1004 }
1005
1006 static void email_init_map(void)
1007 {
1008         email_view_map = key_alloc();
1009         key_add(email_view_map, "Free", &email_view_free);
1010         key_add(email_view_map, "doc:step", &email_step);
1011         key_add(email_view_map, "doc:content", &email_content);
1012         key_add(email_view_map, "doc:set-ref", &email_set_ref);
1013         key_add(email_view_map, "doc:set-attr", &email_view_set_attr);
1014         key_add(email_view_map, "doc:get-attr", &email_view_get_attr);
1015         key_add(email_view_map, "doc:email:render-spacer", &email_spacer);
1016         key_add(email_view_map, "doc:email:render-image", &email_image);
1017         key_add(email_view_map, "doc:email:select", &email_select);
1018         key_add(email_view_map, "email:select:hide", &email_select_hide);
1019 }
1020
1021 void edlib_init(struct pane *ed safe)
1022 {
1023         email_init_map();
1024         call_comm("global-set-command", ed, &open_email, 0, NULL,
1025                   "open-doc-email");
1026         call_comm("global-set-command", ed, &attach_email_view, 0, NULL,
1027                   "attach-email-view");
1028
1029         call("global-load-module", ed, 0, NULL, "lib-html-to-text");
1030         call("global-load-module", ed, 0, NULL, "lib-pdf-to-text");
1031 }