WANT_AMALGAMATION will henceforth work through main.c
[s-mailx.git] / send.c
blobe422d8e690d6fcbdcaaf76b9b6c7030921faabad
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mail to mail folders and displays.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
39 #undef n_FILE
40 #define n_FILE send
42 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 enum pipeflags {
47 PIPE_NULL, /* No pipe- mimetype handler */
48 PIPE_COMM, /* Normal command */
49 PIPE_ASYNC, /* Normal command, run asynchronous */
50 PIPE_TEXT, /* @ special command to force treatment as text */
51 PIPE_MSG /* Display message (returned as command string) */
54 enum parseflags {
55 PARSE_DEFAULT = 0,
56 PARSE_DECRYPT = 01,
57 PARSE_PARTS = 02
60 static sigjmp_buf _send_pipejmp;
62 /* */
63 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
64 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
65 enum parseflags pf, int level);
66 static void parse822(struct message *zmp, struct mimepart *ip,
67 enum parseflags pf, int level);
68 #ifdef HAVE_SSL
69 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
70 enum parseflags pf, int level);
71 #endif
72 static void _parsemultipart(struct message *zmp,
73 struct mimepart *ip, enum parseflags pf, int level);
74 static void __newpart(struct mimepart *ip, struct mimepart **np,
75 off_t offs, int *part);
76 static void __endpart(struct mimepart **np, off_t xoffs, long lines);
78 /* Going for user display, print Part: info string */
79 static void _print_part_info(FILE *obuf, struct mimepart const *mpp,
80 struct ignoretab *doign, int level,
81 struct quoteflt *qf, ui64_t *stats);
83 /* Query possible pipe command for MIME part */
84 static enum pipeflags _pipecmd(char const **result, struct mimepart const *mpp);
86 /* Create a pipe; if mpp is not NULL, place some NAILENV_* environment
87 * variables accordingly */
88 static FILE * _pipefile(char const *pipecomm, struct mimepart const *mpp,
89 FILE **qbuf, bool_t quote, bool_t async);
91 /* Call mime_write() as approbiate and adjust statistics */
92 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
93 enum conversion convert, enum sendaction action,
94 struct quoteflt *qf, ui64_t *stats, struct str *rest);
96 /* SIGPIPE handler */
97 static void _send_onpipe(int signo);
99 /* Send one part */
100 static int sendpart(struct message *zmp, struct mimepart *ip,
101 FILE *obuf, struct ignoretab *doign,
102 struct quoteflt *qf, enum sendaction action,
103 ui64_t *stats, int level);
105 /* Get a file for an attachment */
106 static FILE * newfile(struct mimepart *ip, int *ispipe);
108 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
109 struct quoteflt *qf, ui64_t *stats);
111 /* Output a reasonable looking status field */
112 static void statusput(const struct message *mp, FILE *obuf,
113 struct quoteflt *qf, ui64_t *stats);
114 static void xstatusput(const struct message *mp, FILE *obuf,
115 struct quoteflt *qf, ui64_t *stats);
117 static void put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats);
119 static struct mimepart *
120 parsemsg(struct message *mp, enum parseflags pf)
122 struct mimepart *ip;
123 NYD_ENTER;
125 ip = csalloc(1, sizeof *ip);
126 ip->m_flag = mp->m_flag;
127 ip->m_have = mp->m_have;
128 ip->m_block = mp->m_block;
129 ip->m_offset = mp->m_offset;
130 ip->m_size = mp->m_size;
131 ip->m_xsize = mp->m_xsize;
132 ip->m_lines = mp->m_lines;
133 ip->m_xlines = mp->m_lines;
134 if (parsepart(mp, ip, pf, 0) != OKAY)
135 ip = NULL;
136 NYD_LEAVE;
137 return ip;
140 static enum okay
141 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
142 int level)
144 char *cp_b, *cp;
145 enum okay rv = STOP;
146 NYD_ENTER;
148 ip->m_ct_type = hfield1("content-type", (struct message*)ip);
149 if (ip->m_ct_type != NULL) {
150 ip->m_ct_type_plain = cp_b = savestr(ip->m_ct_type);
151 if ((cp = strchr(cp_b, ';')) != NULL)
152 *cp = '\0';
153 cp = cp_b + strlen(cp_b);
154 while (cp > cp_b && blankchar(cp[-1]))
155 --cp;
156 *cp = '\0';
157 } else if (ip->m_parent != NULL &&
158 ip->m_parent->m_mimecontent == MIME_DIGEST)
159 ip->m_ct_type_plain = "message/rfc822";
160 else
161 ip->m_ct_type_plain = "text/plain";
162 ip->m_ct_type_usr_ovwr = NULL;
164 if (ip->m_ct_type != NULL)
165 ip->m_charset = mime_param_get("charset", ip->m_ct_type);
166 if (ip->m_charset == NULL)
167 ip->m_charset = charset_get_7bit();
169 if ((ip->m_ct_enc = hfield1("content-transfer-encoding",
170 (struct message*)ip)) == NULL)
171 ip->m_ct_enc = mime_enc_from_conversion(CONV_7BIT);
172 ip->m_mime_enc = mime_enc_from_ctehead(ip->m_ct_enc);
174 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
175 (ip->m_filename = mime_param_get("filename", cp)) == NULL) &&
176 ip->m_ct_type != NULL)
177 ip->m_filename = mime_param_get("name", ip->m_ct_type);
179 ip->m_mimecontent = mime_type_mimepart_content(ip);
181 if (pf & PARSE_PARTS) {
182 if (level > 9999) { /* TODO MAGIC */
183 n_err(_("MIME content too deeply nested\n"));
184 goto jleave;
186 switch (ip->m_mimecontent) {
187 case MIME_PKCS7:
188 if (pf & PARSE_DECRYPT) {
189 #ifdef HAVE_SSL
190 parsepkcs7(zmp, ip, pf, level);
191 break;
192 #else
193 n_err(_("No SSL support compiled in\n"));
194 goto jleave;
195 #endif
197 /* FALLTHRU */
198 default:
199 break;
200 case MIME_MULTI:
201 case MIME_ALTERNATIVE:
202 case MIME_RELATED: /* TODO /related yet handled like /alternative */
203 case MIME_DIGEST:
204 _parsemultipart(zmp, ip, pf, level);
205 break;
206 case MIME_822:
207 parse822(zmp, ip, pf, level);
208 break;
211 rv = OKAY;
212 jleave:
213 NYD_LEAVE;
214 return rv;
217 static void
218 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
219 int level)
221 int c, lastc = '\n';
222 size_t cnt;
223 FILE *ibuf;
224 off_t offs;
225 struct mimepart *np;
226 long lines;
227 NYD_ENTER;
229 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
230 goto jleave;
232 cnt = ip->m_size;
233 lines = ip->m_lines;
234 while (cnt && ((c = getc(ibuf)) != EOF)) {
235 --cnt;
236 if (c == '\n') {
237 --lines;
238 if (lastc == '\n')
239 break;
241 lastc = c;
243 offs = ftell(ibuf);
245 np = csalloc(1, sizeof *np);
246 np->m_flag = MNOFROM;
247 np->m_have = HAVE_HEADER | HAVE_BODY;
248 np->m_block = mailx_blockof(offs);
249 np->m_offset = mailx_offsetof(offs);
250 np->m_size = np->m_xsize = cnt;
251 np->m_lines = np->m_xlines = lines;
252 np->m_partstring = ip->m_partstring;
253 np->m_parent = ip;
254 ip->m_multipart = np;
256 if (ok_blook(rfc822_body_from_)) {
257 substdate((struct message*)np);
258 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
261 parsepart(zmp, np, pf, level + 1);
262 jleave:
263 NYD_LEAVE;
266 #ifdef HAVE_SSL
267 static void
268 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
269 int level)
271 struct message m, *xmp;
272 struct mimepart *np;
273 char *to, *cc;
274 NYD_ENTER;
276 memcpy(&m, ip, sizeof m);
277 to = hfield1("to", zmp);
278 cc = hfield1("cc", zmp);
280 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
281 np = csalloc(1, sizeof *np);
282 np->m_flag = xmp->m_flag;
283 np->m_have = xmp->m_have;
284 np->m_block = xmp->m_block;
285 np->m_offset = xmp->m_offset;
286 np->m_size = xmp->m_size;
287 np->m_xsize = xmp->m_xsize;
288 np->m_lines = xmp->m_lines;
289 np->m_xlines = xmp->m_xlines;
290 np->m_partstring = ip->m_partstring;
292 if (parsepart(zmp, np, pf, level + 1) == OKAY) {
293 np->m_parent = ip;
294 ip->m_multipart = np;
297 NYD_LEAVE;
299 #endif
301 static void
302 _parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
303 int level)
305 /* TODO Instead of the recursive multiple run parse we have today,
306 * TODO the send/MIME layer rewrite must create a "tree" of parts with
307 * TODO a single-pass parse, then address each part directly as
308 * TODO necessary; since boundaries start with -- and the content
309 * TODO rather forms a stack this is pretty cheap indeed! */
310 struct mimepart *np = NULL;
311 char *boundary, *line = NULL;
312 size_t linesize = 0, linelen, cnt, boundlen;
313 FILE *ibuf;
314 off_t offs;
315 int part = 0;
316 long lines = 0;
317 NYD_ENTER;
319 if ((boundary = mime_param_boundary_get(ip->m_ct_type, &linelen)) == NULL)
320 goto jleave;
322 boundlen = linelen;
323 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
324 goto jleave;
326 cnt = ip->m_size;
327 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
328 if (line[0] == '\n')
329 break;
330 offs = ftell(ibuf);
332 __newpart(ip, &np, offs, NULL);
333 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
334 /* XXX linelen includes LF */
335 if (!((lines > 0 || part == 0) && linelen > boundlen &&
336 !strncmp(line, boundary, boundlen))) {
337 ++lines;
338 continue;
341 /* Subpart boundary? */
342 if (line[boundlen] == '\n') {
343 offs = ftell(ibuf);
344 if (part > 0) {
345 __endpart(&np, offs - boundlen - 2, lines);
346 __newpart(ip, &np, offs - boundlen - 2, NULL);
348 __endpart(&np, offs, 2);
349 __newpart(ip, &np, offs, &part);
350 lines = 0;
351 continue;
354 /* Final boundary? Be aware of cases where there is no separating
355 * newline in between boundaries, as has been seen in a message with
356 * "Content-Type: multipart/appledouble;" */
357 if (linelen < boundlen + 2)
358 continue;
359 linelen -= boundlen + 2;
360 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
361 (linelen > 0 && line[boundlen + 2] != '\n'))
362 continue;
363 offs = ftell(ibuf);
364 if (part != 0) {
365 __endpart(&np, offs - boundlen - 4, lines);
366 __newpart(ip, &np, offs - boundlen - 4, NULL);
368 __endpart(&np, offs + cnt, 2);
369 break;
371 if (np) {
372 offs = ftell(ibuf);
373 __endpart(&np, offs, lines);
376 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
377 if (np->m_mimecontent != MIME_DISCARD)
378 parsepart(zmp, np, pf, level + 1);
379 free(line);
380 jleave:
381 NYD_LEAVE;
384 static void
385 __newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
387 struct mimepart *pp;
388 size_t sz;
389 NYD_ENTER;
391 *np = csalloc(1, sizeof **np);
392 (*np)->m_flag = MNOFROM;
393 (*np)->m_have = HAVE_HEADER | HAVE_BODY;
394 (*np)->m_block = mailx_blockof(offs);
395 (*np)->m_offset = mailx_offsetof(offs);
397 if (part) {
398 ++(*part);
399 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
400 sz += 20;
401 (*np)->m_partstring = salloc(sz);
402 if (ip->m_partstring)
403 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
404 else
405 snprintf((*np)->m_partstring, sz, "%u", *part);
406 } else
407 (*np)->m_mimecontent = MIME_DISCARD;
408 (*np)->m_parent = ip;
410 if (ip->m_multipart) {
411 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
413 pp->m_nextpart = *np;
414 } else
415 ip->m_multipart = *np;
416 NYD_LEAVE;
419 static void
420 __endpart(struct mimepart **np, off_t xoffs, long lines)
422 off_t offs;
423 NYD_ENTER;
425 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
426 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
427 (*np)->m_lines = (*np)->m_xlines = lines;
428 *np = NULL;
429 NYD_LEAVE;
432 static void
433 _print_part_info(FILE *obuf, struct mimepart const *mpp, /* TODO strtofmt.. */
434 struct ignoretab *doign, int level, struct quoteflt *qf, ui64_t *stats)
436 char buf[64];
437 struct str ti = {NULL, 0}, to;
438 struct str const *cpre, *csuf;
439 char const *cp;
440 NYD2_ENTER;
442 #ifdef HAVE_COLOUR
443 cpre = colour_get(COLOURSPEC_PARTINFO);
444 csuf = colour_get(COLOURSPEC_RESET);
445 #else
446 cpre = csuf = NULL;
447 #endif
449 /* Take care of "99.99", i.e., 5 */
450 if ((cp = mpp->m_partstring) == NULL || cp[0] == '\0')
451 cp = "?";
452 if (level || (cp[0] != '1' && cp[1] == '\0'))
453 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
454 if (cpre != NULL)
455 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
456 _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
457 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
459 to.l = snprintf(buf, sizeof buf, " %" PRIuZ "/%" PRIuZ " ",
460 (uiz_t)mpp->m_lines, (uiz_t)mpp->m_size);
461 _out(buf, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
463 if ((cp = mpp->m_ct_type_usr_ovwr) != NULL)
464 _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
465 else
466 cp = mpp->m_ct_type_plain;
467 if ((to.l = strlen(cp)) > 30 && is_asccaseprefix(cp, "application/")) {
468 size_t const al = sizeof("appl../") -1, fl = sizeof("application/") -1;
469 size_t i = to.l - fl;
470 char *x = salloc(al + i +1);
472 memcpy(x, "appl../", al);
473 memcpy(x + al, cp + fl, i +1);
474 cp = x;
475 to.l = al + i;
477 _out(cp, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
479 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_ct_enc) != NULL) {
480 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
481 if (to.l > 25 && !asccasecmp(cp, "quoted-printable"))
482 cp = "qu.-pr.";
483 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
486 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_charset) != NULL) {
487 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
488 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
491 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
492 if (csuf != NULL)
493 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
494 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
496 if (is_ign("content-disposition", 19, doign) && mpp->m_filename != NULL &&
497 *mpp->m_filename != '\0') {
498 makeprint(n_str_add_cp(&ti, mpp->m_filename), &to);
499 free(ti.s);
500 to.l = delctrl(to.s, to.l);
502 if (cpre != NULL)
503 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
504 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
505 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
506 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
507 if (csuf != NULL)
508 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
509 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
511 free(to.s);
513 NYD2_LEAVE;
516 static enum pipeflags
517 _pipecmd(char const **result, struct mimepart const *mpp)
519 enum pipeflags rv;
520 char const *cp;
521 NYD2_ENTER;
523 *result = NULL;
525 /* Do we have any handler for this part? */
526 if ((cp = mime_type_mimepart_handler(mpp)) == NULL)
527 rv = PIPE_NULL;
528 else if (cp == MIME_TYPE_HANDLER_TEXT)
529 rv = PIPE_TEXT;
530 else if (
531 #ifdef HAVE_FILTER_HTML_TAGSOUP
532 cp == MIME_TYPE_HANDLER_HTML ||
533 #endif
534 *cp != '@') {
535 *result = cp;
536 rv = PIPE_COMM;
537 } else if (*++cp == '\0') {
538 /* Treat as plain text */
539 rv = PIPE_TEXT;
540 } else if (!(pstate & PS_MSGLIST_DIRECT)) {
541 /* Viewing multiple messages in one go, don't block system */
542 *result = _("[Directly address message only to display this]\n");
543 rv = PIPE_MSG;
544 } else {
545 /* Viewing a single message only */
546 /* TODO send/MIME layer rewrite: when we have a single-pass parser
547 * TODO then the parsing phase and the send phase will be separated;
548 * TODO that allows us to ask a user *before* we start the send, i.e.,
549 * TODO *before* a pager pipe is setup */
550 if (*cp == '&')
551 /* Asynchronous command, normal command line */
552 *result = ++cp, rv = PIPE_ASYNC;
553 else
554 *result = cp, rv = PIPE_COMM;
556 NYD2_LEAVE;
557 return rv;
560 static FILE *
561 _pipefile(char const *pipecomm, struct mimepart const *mpp, FILE **qbuf,
562 bool_t quote, bool_t async)
564 struct str s;
565 char const *env_addon[8], *cp, *sh;
566 FILE *rbuf;
567 NYD_ENTER;
569 rbuf = *qbuf;
571 if (quote) {
572 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER,
573 0600)) == NULL) {
574 n_perr(_("tmpfile"), 0);
575 *qbuf = rbuf;
577 async = FAL0;
580 #ifdef HAVE_FILTER_HTML_TAGSOUP
581 if (pipecomm == MIME_TYPE_HANDLER_HTML) {
582 union {int (*ptf)(void); char const *sh;} u;
583 u.ptf = &htmlflt_process_main;
584 rbuf = Popen((char*)-1, "W", u.sh, NULL, fileno(*qbuf));
585 pipecomm = "Builtin HTML tagsoup filter";
586 goto jafter_tagsoup_hack;
588 #endif
590 /* NAIL_FILENAME */
591 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
592 cp = "";
593 env_addon[0] = str_concat_csvl(&s, NAILENV_FILENAME, "=", cp, NULL)->s;
595 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
596 * TODO a file wherever he wants! *Do* create a zero-size temporary file
597 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
598 * TODO the pipe returns? Like this we *can* verify path/name issues! */
599 #undef _X
600 #define _X MIN(NAME_MAX / 4, 16)
601 s.s = getrandstring(_X);
602 if (mpp == NULL)
603 cp = s.s;
604 else if (*cp == '\0') {
605 size_t i;
607 if ( (((cp = mpp->m_ct_type_usr_ovwr) == NULL || *cp == '\0') &&
608 ((cp = mpp->m_ct_type_plain) == NULL || *cp == '\0')) ||
609 ((sh = strrchr(cp, '/')) == NULL || *++sh == '\0') ||
610 (i = strlen(sh)) > _X)
611 cp = s.s;
612 else {
613 LCTA(_X >= 13);
615 s.s[0] = '-';
616 cp = s.s = savecat(sh, s.s);
617 s.s[i + _X - 4] = '.';
620 #undef _X
621 env_addon[1] = str_concat_csvl(&s, NAILENV_FILENAME_GENERATED, "=", cp,
622 NULL)->s;
624 /* NAIL_CONTENT{,_EVIDENCE} */
625 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
626 cp = "";
627 env_addon[2] = str_concat_csvl(&s, NAILENV_CONTENT, "=", cp, NULL)->s;
629 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
630 cp = mpp->m_ct_type_usr_ovwr;
631 env_addon[3] = str_concat_csvl(&s, NAILENV_CONTENT_EVIDENCE, "=", cp,
632 NULL)->s;
634 env_addon[4] = str_concat_csvl(&s, NAILENV_TMPDIR, "=", tempdir, NULL)->s;
635 env_addon[5] = str_concat_csvl(&s, "TMPDIR", "=", tempdir, NULL)->s;
637 env_addon[6] = NULL;
639 if ((sh = ok_vlook(SHELL)) == NULL)
640 sh = XSHELL;
642 rbuf = Popen(pipecomm, "W", sh, env_addon, (async ? -1 : fileno(*qbuf)));
643 #ifdef HAVE_FILTER_HTML_TAGSOUP
644 jafter_tagsoup_hack:
645 #endif
646 if (rbuf == NULL)
647 n_err(_("Cannot run MIME type handler \"%s\": %s\n"),
648 pipecomm, strerror(errno));
649 else {
650 fflush(*qbuf);
651 if (*qbuf != stdout)
652 fflush(stdout);
654 NYD_LEAVE;
655 return rbuf;
658 SINLINE ssize_t
659 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
660 sendaction action, struct quoteflt *qf, ui64_t *stats, struct str *rest)
662 ssize_t sz = 0, n;
663 int flags;
664 NYD_ENTER;
666 #if 0
667 Well ... it turns out to not work like that since of course a valid
668 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
669 after an empty line has been seen, which cannot be detected that easily
670 right here!
671 ifdef HAVE_DEBUG /* TODO assert legacy */
672 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
673 * TODO other input situations handle RFC 4155 OR, if newly generated,
674 * TODO enforce quoted-printable if there is From_, as "required" by
675 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
676 * TODO if it may happen in this path, we should just treat decryption
677 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
678 if (action == SEND_MBOX || action == SEND_DECRYPT)
679 assert(!is_head(buf, len, TRU1));
680 #else
681 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
682 is_head(buf, len, TRU1)) {
683 putc('>', fp);
684 ++sz;
686 #endif
688 flags = ((int)action & _TD_EOF);
689 action &= ~_TD_EOF;
690 n = mime_write(buf, len, fp,
691 action == SEND_MBOX ? CONV_NONE : convert,
692 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
693 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
694 ? TD_ISPR | TD_ICONV
695 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
696 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
697 qf, rest);
698 if (n < 0)
699 sz = n;
700 else if (n > 0) {
701 sz += n;
702 if (stats != NULL)
703 *stats += sz;
705 NYD_LEAVE;
706 return sz;
709 static void
710 _send_onpipe(int signo)
712 NYD_X; /* Signal handler */
713 UNUSED(signo);
714 siglongjmp(_send_pipejmp, 1);
717 static sigjmp_buf __sendp_actjmp; /* TODO someday.. */
718 static int __sendp_sig; /* TODO someday.. */
719 static sighandler_type __sendp_opipe;
720 static void
721 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
723 NYD_X; /* Signal handler */
724 __sendp_sig = sig;
725 siglongjmp(__sendp_actjmp, 1);
728 static sigjmp_buf __sndalter_actjmp; /* TODO someday.. */
729 static int __sndalter_sig; /* TODO someday.. */
730 static void
731 __sndalter_onsig(int sig) /* TODO someday, we won't need it no more */
733 NYD_X; /* Signal handler */
734 __sndalter_sig = sig;
735 siglongjmp(__sndalter_actjmp, 1);
738 static int
739 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
740 struct ignoretab *doign, struct quoteflt *qf,
741 enum sendaction volatile action, ui64_t * volatile stats, int level)
743 int volatile ispipe, rv = 0;
744 struct str rest;
745 char *line = NULL, *cp, *cp2, *start;
746 char const *pipecomm = NULL;
747 size_t linesize = 0, linelen, cnt;
748 int dostat, infld = 0, ignoring = 1, isenc, c;
749 struct mimepart *volatile np;
750 FILE * volatile ibuf = NULL, * volatile pbuf = obuf, * volatile qbuf = obuf,
751 *origobuf = obuf;
752 enum conversion volatile convert;
753 sighandler_type volatile oldpipe = SIG_DFL;
754 long lineno = 0;
755 NYD_ENTER;
757 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
758 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
759 goto jskip;
761 dostat = 0;
762 if (level == 0) {
763 if (doign != NULL) {
764 if (!is_ign("status", 6, doign))
765 dostat |= 1;
766 if (!is_ign("x-status", 8, doign))
767 dostat |= 2;
768 } else
769 dostat = 3;
771 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
772 rv = -1;
773 goto jleave;
775 cnt = ip->m_size;
777 if (ip->m_mimecontent == MIME_DISCARD)
778 goto jskip;
780 if (!(ip->m_flag & MNOFROM))
781 while (cnt && (c = getc(ibuf)) != EOF) {
782 cnt--;
783 if (c == '\n')
784 break;
786 isenc = 0;
787 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
788 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
789 action == SEND_TOSRCH)
790 ? CONV_FROMHDR : CONV_NONE;
792 /* Work the headers */
793 quoteflt_reset(qf, obuf);
794 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
795 ++lineno;
796 if (line[0] == '\n') {
797 /* If line is blank, we've reached end of headers, so force out
798 * status: field and note that we are no longer in header fields */
799 if (dostat & 1)
800 statusput(zmp, obuf, qf, stats);
801 if (dostat & 2)
802 xstatusput(zmp, obuf, qf, stats);
803 if (doign != allignore)
804 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
805 break;
808 isenc &= ~1;
809 if (infld && blankchar(line[0])) {
810 /* If this line is a continuation (SP / HT) of a previous header
811 * field, determine if the start of the line is a MIME encoded word */
812 if (isenc & 2) {
813 for (cp = line; blankchar(*cp); ++cp);
814 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
815 cp[0] == '=' && cp[1] == '?')
816 isenc |= 1;
818 } else {
819 /* Pick up the header field if we have one */
820 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
822 cp2 = cp;
823 while (spacechar(*cp))
824 ++cp;
825 if (cp[0] != ':' && level == 0 && lineno == 1) {
826 /* Not a header line, force out status: This happens in uucp style
827 * mail where there are no headers at all */
828 if (dostat & 1)
829 statusput(zmp, obuf, qf, stats);
830 if (dostat & 2)
831 xstatusput(zmp, obuf, qf, stats);
832 if (doign != allignore)
833 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
834 break;
837 /* If it is an ignored field and we care about such things, skip it.
838 * Misuse dostat also for another bit xxx use a bitenum + for more */
839 if (ok_blook(keep_content_length))
840 dostat |= 1 << 2;
841 c = *cp2;
842 *cp2 = 0; /* temporarily null terminate */
843 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
844 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
845 (!asccasecmp(line, "content-length") ||
846 !asccasecmp(line, "lines"))))
847 ignoring = 1;
848 else if (!asccasecmp(line, "status")) {
849 /* If field is "status," go compute and print real Status: field */
850 if (dostat & 1) {
851 statusput(zmp, obuf, qf, stats);
852 dostat &= ~1;
853 ignoring = 1;
855 } else if (!asccasecmp(line, "x-status")) {
856 /* If field is "status," go compute and print real Status: field */
857 if (dostat & 2) {
858 xstatusput(zmp, obuf, qf, stats);
859 dostat &= ~2;
860 ignoring = 1;
862 } else {
863 ignoring = 0;
864 /* For colourization we need the complete line, so save it */
865 /* XXX This is all temporary (colour belongs into backend), so
866 * XXX use pipecomm as a temporary storage in the meanwhile */
867 #ifdef HAVE_COLOUR
868 if (colour_table != NULL)
869 pipecomm = savestrbuf(line, PTR2SIZE(cp2 - line));
870 #endif
872 *cp2 = c;
873 dostat &= ~(1 << 2);
874 infld = 1;
877 /* Determine if the end of the line is a MIME encoded word */
878 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
879 * TODO with header follow lines, and it should be up to the backend
880 * TODO what happens and what not, i.e., it doesn't matter wether it's
881 * TODO a MIME-encoded word or not, as long as a single separating space
882 * TODO remains in between lines (the MIME stuff will correctly remove
883 * TODO whitespace in between multiple adjacent encoded words) */
884 isenc &= ~2;
885 if (cnt && (c = getc(ibuf)) != EOF) {
886 if (blankchar(c)) {
887 cp = line + linelen - 1;
888 if (linelen > 0 && *cp == '\n')
889 --cp;
890 while (cp >= line && whitechar(*cp))
891 --cp;
892 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
893 isenc |= 2;
895 ungetc(c, ibuf);
898 if (!ignoring) {
899 size_t len = linelen;
900 start = line;
901 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
902 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
903 action == SEND_TOSRCH) {
904 /* Strip blank characters if two MIME-encoded words follow on
905 * continuing lines */
906 if (isenc & 1)
907 while (len > 0 && blankchar(*start)) {
908 ++start;
909 --len;
911 if (isenc & 2)
912 if (len > 0 && start[len - 1] == '\n')
913 --len;
914 while (len > 0 && blankchar(start[len - 1]))
915 --len;
917 #ifdef HAVE_COLOUR
919 bool_t colour_stripped = FAL0;
920 if (pipecomm != NULL) {
921 colour_put_header(obuf, pipecomm);
922 if (len > 0 && start[len - 1] == '\n') {
923 colour_stripped = TRU1;
924 --len;
927 #endif
928 _out(start, len, obuf, convert, action, qf, stats, NULL);
929 #ifdef HAVE_COLOUR
930 if (pipecomm != NULL) {
931 colour_reset(obuf); /* XXX reset after \n!! */
932 if (colour_stripped)
933 putc('\n', obuf);
936 #endif
937 if (ferror(obuf)) {
938 free(line);
939 rv = -1;
940 goto jleave;
944 quoteflt_flush(qf);
945 free(line);
946 line = NULL;
947 pipecomm = NULL;
949 jskip:
950 switch (ip->m_mimecontent) {
951 case MIME_822:
952 switch (action) {
953 case SEND_TODISP:
954 case SEND_TODISP_ALL:
955 case SEND_QUOTE:
956 case SEND_QUOTE_ALL:
957 if (ok_blook(rfc822_body_from_)) {
958 if (qf->qf_pfix_len > 0) {
959 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
960 qf->qf_pfix_len, obuf);
961 if (i == qf->qf_pfix_len && stats != NULL)
962 *stats += i;
964 put_from_(obuf, ip->m_multipart, stats);
966 /* FALLTHRU */
967 case SEND_TOSRCH:
968 case SEND_DECRYPT:
969 goto jmulti;
970 case SEND_TOFILE:
971 case SEND_TOPIPE:
972 if (ok_blook(rfc822_body_from_))
973 put_from_(obuf, ip->m_multipart, stats);
974 /* FALLTHRU */
975 case SEND_MBOX:
976 case SEND_RFC822:
977 case SEND_SHOW:
978 break;
980 break;
981 case MIME_TEXT_HTML:
982 case MIME_TEXT:
983 case MIME_TEXT_PLAIN:
984 switch (action) {
985 case SEND_TODISP:
986 case SEND_TODISP_ALL:
987 case SEND_QUOTE:
988 case SEND_QUOTE_ALL:
989 ispipe = TRU1;
990 switch (_pipecmd(&pipecomm, ip)) {
991 case PIPE_MSG:
992 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
993 stats, NULL);
994 /* We would print this as plain text, so better force going home */
995 goto jleave;
996 case PIPE_TEXT:
997 case PIPE_COMM:
998 case PIPE_NULL:
999 break;
1000 case PIPE_ASYNC:
1001 ispipe = FAL0;
1002 break;
1004 /* FALLTRHU */
1005 default:
1006 break;
1008 break;
1009 case MIME_DISCARD:
1010 if (action != SEND_DECRYPT)
1011 goto jleave;
1012 break;
1013 case MIME_PKCS7:
1014 if (action != SEND_MBOX && action != SEND_RFC822 &&
1015 action != SEND_SHOW && ip->m_multipart != NULL)
1016 goto jmulti;
1017 /* FALLTHRU */
1018 default:
1019 switch (action) {
1020 case SEND_TODISP:
1021 case SEND_TODISP_ALL:
1022 case SEND_QUOTE:
1023 case SEND_QUOTE_ALL:
1024 ispipe = TRU1;
1025 switch (_pipecmd(&pipecomm, ip)) {
1026 case PIPE_MSG:
1027 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
1028 stats, NULL);
1029 pipecomm = NULL;
1030 break;
1031 case PIPE_ASYNC:
1032 ispipe = FAL0;
1033 /* FALLTHRU */
1034 case PIPE_COMM:
1035 case PIPE_NULL:
1036 break;
1037 case PIPE_TEXT:
1038 goto jcopyout; /* break; break; */
1040 if (pipecomm != NULL)
1041 break;
1042 if (level == 0 && cnt) {
1043 char const *x = _("[Binary content]\n");
1044 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1046 goto jleave;
1047 case SEND_TOFILE:
1048 case SEND_TOPIPE:
1049 case SEND_TOSRCH:
1050 case SEND_DECRYPT:
1051 case SEND_MBOX:
1052 case SEND_RFC822:
1053 case SEND_SHOW:
1054 break;
1056 break;
1057 case MIME_ALTERNATIVE:
1058 case MIME_RELATED:
1059 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
1060 !ok_blook(print_alternatives)) {
1061 /* XXX This (a) should not remain (b) should be own fun */
1062 struct mpstack {
1063 struct mpstack *outer;
1064 struct mimepart *mp;
1065 } outermost, * volatile curr = &outermost, * volatile mpsp;
1066 sighandler_type volatile opsh, oish, ohsh;
1067 size_t volatile partcnt = 0/* silence CC */;
1068 bool_t volatile neednl = FAL0;
1070 curr->outer = NULL;
1071 curr->mp = ip;
1073 __sndalter_sig = 0;
1074 opsh = safe_signal(SIGPIPE, &__sndalter_onsig);
1075 oish = safe_signal(SIGINT, &__sndalter_onsig);
1076 ohsh = safe_signal(SIGHUP, &__sndalter_onsig);
1077 if (sigsetjmp(__sndalter_actjmp, 1)) {
1078 rv = -1;
1079 goto jalter_unroll;
1082 for (np = ip->m_multipart;;) {
1083 partcnt = 0;
1084 jalter_redo:
1085 for (; np != NULL; np = np->m_nextpart) {
1086 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
1087 if (neednl)
1088 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1089 _print_part_info(obuf, np, doign, level, qf, stats);
1091 neednl = TRU1;
1093 switch (np->m_mimecontent) {
1094 case MIME_ALTERNATIVE:
1095 case MIME_RELATED:
1096 case MIME_MULTI:
1097 case MIME_DIGEST:
1098 mpsp = ac_alloc(sizeof *mpsp);
1099 mpsp->outer = curr;
1100 mpsp->mp = np->m_multipart;
1101 curr->mp = np;
1102 curr = mpsp;
1103 np = mpsp->mp;
1104 neednl = FAL0;
1105 goto jalter_redo;
1106 default:
1107 switch (_pipecmd(&pipecomm, np)) {
1108 default:
1109 continue;
1110 case PIPE_TEXT:
1111 break;
1113 /* FALLTHRU */
1114 case MIME_TEXT_PLAIN:
1115 ++partcnt;
1116 if (action == SEND_QUOTE && partcnt > 1 &&
1117 ip->m_mimecontent == MIME_ALTERNATIVE)
1118 break;
1119 quoteflt_flush(qf);
1120 if (action == SEND_QUOTE && partcnt > 1) {
1121 struct quoteflt *dummy = quoteflt_dummy();
1122 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
1123 NULL);
1124 quoteflt_flush(dummy);
1126 neednl = FAL0;
1127 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
1128 level + 1);
1129 quoteflt_reset(qf, origobuf);
1131 if (rv < 0) {
1132 jalter_unroll:
1133 for (;; curr = mpsp) {
1134 if ((mpsp = curr->outer) == NULL)
1135 break;
1136 ac_free(curr);
1139 break;
1143 mpsp = curr->outer;
1144 if (mpsp == NULL)
1145 break;
1146 ac_free(curr);
1147 curr = mpsp;
1148 np = curr->mp->m_nextpart;
1150 safe_signal(SIGHUP, ohsh);
1151 safe_signal(SIGINT, oish);
1152 safe_signal(SIGPIPE, opsh);
1153 if (__sndalter_sig != 0)
1154 n_raise(__sndalter_sig);
1155 goto jleave;
1157 /* FALLTHRU */
1158 case MIME_MULTI:
1159 case MIME_DIGEST:
1160 switch (action) {
1161 case SEND_TODISP:
1162 case SEND_TODISP_ALL:
1163 case SEND_QUOTE:
1164 case SEND_QUOTE_ALL:
1165 case SEND_TOFILE:
1166 case SEND_TOPIPE:
1167 case SEND_TOSRCH:
1168 case SEND_DECRYPT:
1169 jmulti:
1170 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
1171 ip->m_multipart != NULL &&
1172 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
1173 ip->m_multipart->m_nextpart == NULL) {
1174 char const *x = _("[Missing multipart boundary - use \"show\" "
1175 "to display the raw message]\n");
1176 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1179 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1180 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
1181 continue;
1182 ispipe = FAL0;
1183 switch (action) {
1184 case SEND_TOFILE:
1185 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
1186 break;
1187 stats = NULL;
1188 if ((obuf = newfile(np, UNVOLATILE(&ispipe))) == NULL)
1189 continue;
1190 if (!ispipe)
1191 break;
1192 if (sigsetjmp(_send_pipejmp, 1)) {
1193 rv = -1;
1194 goto jpipe_close;
1196 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1197 break;
1198 case SEND_TODISP:
1199 case SEND_TODISP_ALL:
1200 case SEND_QUOTE_ALL:
1201 if (ip->m_mimecontent != MIME_MULTI &&
1202 ip->m_mimecontent != MIME_ALTERNATIVE &&
1203 ip->m_mimecontent != MIME_RELATED &&
1204 ip->m_mimecontent != MIME_DIGEST)
1205 break;
1206 _print_part_info(obuf, np, doign, level, qf, stats);
1207 break;
1208 case SEND_MBOX:
1209 case SEND_RFC822:
1210 case SEND_SHOW:
1211 case SEND_TOSRCH:
1212 case SEND_QUOTE:
1213 case SEND_DECRYPT:
1214 case SEND_TOPIPE:
1215 break;
1218 quoteflt_flush(qf);
1219 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
1220 rv = -1;
1221 quoteflt_reset(qf, origobuf);
1223 if (action == SEND_QUOTE)
1224 break;
1225 if (action == SEND_TOFILE && obuf != origobuf) {
1226 if (!ispipe)
1227 Fclose(obuf);
1228 else {
1229 jpipe_close:
1230 safe_signal(SIGPIPE, SIG_IGN);
1231 Pclose(obuf, TRU1);
1232 safe_signal(SIGPIPE, oldpipe);
1236 goto jleave;
1237 case SEND_MBOX:
1238 case SEND_RFC822:
1239 case SEND_SHOW:
1240 break;
1244 /* Copy out message body */
1245 jcopyout:
1246 if (doign == allignore && level == 0) /* skip final blank line */
1247 --cnt;
1248 switch (ip->m_mime_enc) {
1249 case MIMEE_BIN:
1250 case MIMEE_7B:
1251 case MIMEE_8B:
1252 convert = CONV_NONE;
1253 break;
1254 case MIMEE_QP:
1255 convert = CONV_FROMQP;
1256 break;
1257 case MIMEE_B64:
1258 switch (ip->m_mimecontent) {
1259 case MIME_TEXT:
1260 case MIME_TEXT_PLAIN:
1261 case MIME_TEXT_HTML:
1262 convert = CONV_FROMB64_T;
1263 break;
1264 default:
1265 convert = CONV_FROMB64;
1267 break;
1268 default:
1269 convert = CONV_NONE;
1272 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1273 action == SEND_RFC822 || action == SEND_SHOW)
1274 convert = CONV_NONE;
1275 #ifdef HAVE_ICONV
1276 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1277 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1278 action == SEND_TOSRCH) &&
1279 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1280 ip->m_mimecontent == MIME_TEXT_HTML ||
1281 ip->m_mimecontent == MIME_TEXT)) {
1282 char const *tcs = charset_get_lc();
1284 if (iconvd != (iconv_t)-1)
1285 n_iconv_close(iconvd);
1286 /* TODO Since Base64 has an odd 4:3 relation in between input
1287 * TODO and output an input line may end with a partial
1288 * TODO multibyte character; this is no problem at all unless
1289 * TODO we send to the display or whatever, i.e., ensure
1290 * TODO makeprint() or something; to avoid this trap, *force*
1291 * TODO iconv(), in which case this layer will handle leftovers
1292 * TODO correctly */
1293 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
1294 asccasecmp(charset_get_7bit(), ip->m_charset))) {
1295 iconvd = n_iconv_open(tcs, ip->m_charset);
1297 * TODO errors should DEFINETELY not be scrolled away!
1298 * TODO what about an error buffer (think old shsp(1)),
1299 * TODO re-dump errors since last snapshot when the
1300 * TODO command loop enters again? i.e., at least print
1301 * TODO "There were errors ?" before the next prompt,
1302 * TODO so that the user can look at the error buffer?
1304 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1305 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
1306 /*rv = 1; goto jleave;*/
1310 #endif
1312 if (pipecomm != NULL && (action == SEND_TODISP ||
1313 action == SEND_TODISP_ALL || action == SEND_QUOTE ||
1314 action == SEND_QUOTE_ALL)) {
1315 qbuf = obuf;
1316 pbuf = _pipefile(pipecomm, ip, UNVOLATILE(&qbuf),
1317 (action == SEND_QUOTE || action == SEND_QUOTE_ALL), !ispipe);
1318 if (pbuf == NULL) {
1319 #ifdef HAVE_ICONV
1320 if (iconvd != (iconv_t)-1)
1321 n_iconv_close(iconvd);
1322 #endif
1323 rv = -1;
1324 goto jleave;
1326 action = SEND_TOPIPE;
1327 if (pbuf != qbuf) {
1328 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1329 if (sigsetjmp(_send_pipejmp, 1))
1330 goto jend;
1332 } else
1333 pbuf = qbuf = obuf;
1336 bool_t volatile eof;
1337 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1338 ui64_t *save_stats = stats;
1340 if (pbuf != origobuf) {
1341 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1342 stats = NULL;
1344 eof = FAL0;
1345 rest.s = NULL;
1346 rest.l = 0;
1348 if (pbuf == qbuf) {
1349 __sendp_sig = 0;
1350 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1351 if (sigsetjmp(__sendp_actjmp, 1)) {
1352 if (rest.s != NULL)
1353 free(rest.s);
1354 free(line);
1355 #ifdef HAVE_ICONV
1356 if (iconvd != (iconv_t)-1)
1357 n_iconv_close(iconvd);
1358 #endif
1359 safe_signal(SIGPIPE, __sendp_opipe);
1360 n_raise(__sendp_sig);
1364 quoteflt_reset(qf, pbuf);
1365 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1366 joutln:
1367 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1368 ferror(pbuf)) {
1369 rv = -1; /* XXX Should bail away?! */
1370 break;
1373 if (!eof && rest.l != 0) {
1374 linelen = 0;
1375 eof = TRU1;
1376 action |= _TD_EOF;
1377 goto joutln;
1379 if (pbuf == qbuf)
1380 safe_signal(SIGPIPE, __sendp_opipe);
1382 quoteflt_flush(qf);
1383 if (rest.s != NULL)
1384 free(rest.s);
1386 if (pbuf != origobuf) {
1387 qf->qf_pfix_len = save_qf_pfix_len;
1388 stats = save_stats;
1392 jend:
1393 if (line != NULL)
1394 free(line);
1395 if (pbuf != qbuf) {
1396 safe_signal(SIGPIPE, SIG_IGN);
1397 Pclose(pbuf, ispipe);
1398 safe_signal(SIGPIPE, oldpipe);
1399 if (qbuf != NULL && qbuf != obuf)
1400 pipecpy(qbuf, obuf, origobuf, qf, stats);
1402 #ifdef HAVE_ICONV
1403 if (iconvd != (iconv_t)-1)
1404 n_iconv_close(iconvd);
1405 #endif
1406 jleave:
1407 NYD_LEAVE;
1408 return rv;
1411 static FILE *
1412 newfile(struct mimepart *ip, int *ispipe)
1414 struct str in, out;
1415 char *f;
1416 FILE *fp;
1417 NYD_ENTER;
1419 f = ip->m_filename;
1420 *ispipe = 0;
1422 if (f != NULL && f != (char*)-1) {
1423 in.s = f;
1424 in.l = strlen(f);
1425 makeprint(&in, &out);
1426 out.l = delctrl(out.s, out.l);
1427 f = savestrbuf(out.s, out.l);
1428 free(out.s);
1431 if (options & OPT_INTERACTIVE) {
1432 char *f2, *f3;
1433 jgetname:
1434 printf(_("Enter filename for part %s (%s)"),
1435 (ip->m_partstring != NULL) ? ip->m_partstring : "?",
1436 ip->m_ct_type_plain);
1437 f2 = readstr_input(": ", (f != (char*)-1 && f != NULL)
1438 ? fexpand_nshell_quote(f) : NULL);
1439 if (f2 == NULL || *f2 == '\0') {
1440 n_err(_("... skipping this\n"));
1441 fp = NULL;
1442 goto jleave;
1443 } else if (*f2 == '|')
1444 /* Pipes are expanded by the shell */
1445 f = f2;
1446 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NSHELL)) == NULL)
1447 /* (Error message written by fexpand()) */
1448 goto jgetname;
1449 else
1450 f = f3;
1452 if (f == NULL || f == (char*)-1) {
1453 fp = NULL;
1454 goto jleave;
1457 if (*f == '|') {
1458 char const *cp;
1459 cp = ok_vlook(SHELL);
1460 if (cp == NULL)
1461 cp = XSHELL;
1462 fp = Popen(f + 1, "w", cp, NULL, 1);
1463 if (!(*ispipe = (fp != NULL)))
1464 n_perr(f, 0);
1465 } else {
1466 if ((fp = Fopen(f, "w")) == NULL)
1467 n_err(_("Cannot open \"%s\"\n"), f);
1469 jleave:
1470 NYD_LEAVE;
1471 return fp;
1474 static void
1475 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1476 ui64_t *stats)
1478 char *line = NULL; /* TODO line pool */
1479 size_t linesize = 0, linelen, cnt;
1480 ssize_t all_sz, sz;
1481 NYD_ENTER;
1483 fflush(pipebuf);
1484 rewind(pipebuf);
1485 cnt = fsize(pipebuf);
1486 all_sz = 0;
1488 quoteflt_reset(qf, outbuf);
1489 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1490 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1491 break;
1492 all_sz += sz;
1494 if ((sz = quoteflt_flush(qf)) > 0)
1495 all_sz += sz;
1496 if (line)
1497 free(line);
1499 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1500 *stats += all_sz;
1501 Fclose(pipebuf);
1502 NYD_LEAVE;
1505 static void
1506 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1507 ui64_t *stats)
1509 char statout[3], *cp = statout;
1510 NYD_ENTER;
1512 if (mp->m_flag & MREAD)
1513 *cp++ = 'R';
1514 if (!(mp->m_flag & MNEW))
1515 *cp++ = 'O';
1516 *cp = 0;
1517 if (statout[0]) {
1518 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1519 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1520 if (i > 0 && stats != NULL)
1521 *stats += i;
1523 NYD_LEAVE;
1526 static void
1527 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1528 ui64_t *stats)
1530 char xstatout[4];
1531 char *xp = xstatout;
1532 NYD_ENTER;
1534 if (mp->m_flag & MFLAGGED)
1535 *xp++ = 'F';
1536 if (mp->m_flag & MANSWERED)
1537 *xp++ = 'A';
1538 if (mp->m_flag & MDRAFTED)
1539 *xp++ = 'T';
1540 *xp = 0;
1541 if (xstatout[0]) {
1542 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1543 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1544 if (i > 0 && stats != NULL)
1545 *stats += i;
1547 NYD_LEAVE;
1550 static void
1551 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1553 char const *froma, *date, *nl;
1554 int i;
1555 NYD_ENTER;
1557 if (ip != NULL && ip->m_from != NULL) {
1558 froma = ip->m_from;
1559 date = fakedate(ip->m_time);
1560 nl = "\n";
1561 } else {
1562 froma = myname;
1563 date = time_current.tc_ctime;
1564 nl = "";
1567 colour_put(fp, COLOURSPEC_FROM_);
1568 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1569 colour_reset(fp);
1570 if (i > 0 && stats != NULL)
1571 *stats += i;
1572 NYD_LEAVE;
1575 FL int
1576 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1577 char const *prefix, enum sendaction action, ui64_t *stats)
1579 struct quoteflt qf;
1580 size_t cnt, sz, i;
1581 FILE *ibuf;
1582 enum parseflags pf;
1583 struct mimepart *ip;
1584 int rv = -1, c;
1585 NYD_ENTER;
1587 if (mp == dot && action != SEND_TOSRCH)
1588 pstate |= PS_DID_PRINT_DOT;
1589 if (stats != NULL)
1590 *stats = 0;
1591 quoteflt_init(&qf, prefix);
1593 /* First line is the From_ line, so no headers there to worry about */
1594 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1595 goto jleave;
1597 cnt = mp->m_size;
1598 sz = 0;
1600 struct str const *cpre, *csuf;
1601 #ifdef HAVE_COLOUR
1602 cpre = colour_get(COLOURSPEC_FROM_);
1603 csuf = colour_get(COLOURSPEC_RESET);
1604 #else
1605 cpre = csuf = NULL;
1606 #endif
1607 if (mp->m_flag & MNOFROM) {
1608 if (doign != allignore && doign != fwdignore && action != SEND_RFC822)
1609 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1610 (cpre != NULL ? cpre->s : ""),
1611 (int)qf.qf_pfix_len, (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
1612 fakefrom(mp), fakedate(mp->m_time),
1613 (csuf != NULL ? csuf->s : ""));
1614 } else {
1615 if (doign != allignore && doign != fwdignore && action != SEND_RFC822) {
1616 if (qf.qf_pfix_len > 0) {
1617 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1618 if (i != qf.qf_pfix_len)
1619 goto jleave;
1620 sz += i;
1622 #ifdef HAVE_COLOUR
1623 if (cpre != NULL) {
1624 fputs(cpre->s, obuf);
1625 cpre = (struct str const*)0x1;
1627 #endif
1630 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1631 if (doign != allignore && doign != fwdignore &&
1632 action != SEND_RFC822) {
1633 #ifdef HAVE_COLOUR
1634 if (c == '\n' && csuf != NULL) {
1635 cpre = (struct str const*)0x1;
1636 fputs(csuf->s, obuf);
1638 #endif
1639 putc(c, obuf);
1640 sz++;
1642 --cnt;
1643 if (c == '\n')
1644 break;
1647 #ifdef HAVE_COLOUR
1648 if (csuf != NULL && cpre != (struct str const*)0x1)
1649 fputs(csuf->s, obuf);
1650 #endif
1653 if (sz > 0 && stats != NULL)
1654 *stats += sz;
1656 pf = 0;
1657 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1658 pf |= PARSE_DECRYPT | PARSE_PARTS;
1659 if ((ip = parsemsg(mp, pf)) == NULL)
1660 goto jleave;
1662 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1663 jleave:
1664 quoteflt_destroy(&qf);
1665 NYD_LEAVE;
1666 return rv;
1669 /* s-it-mode */