Add vok_*() family, backed by (non-final) _var_vok*()
[s-mailx.git] / send.c
blob35bae959d592ef9d10d31bf161009dc1c9ba40ac
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 - 2013 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.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 enum pipeflags {
45 PIPE_NULL, /* No pipe- mimetype handler */
46 PIPE_COMM, /* Normal command */
47 PIPE_ASYNC, /* Normal command, run asynchronous */
48 PIPE_TEXT, /* @ special command to force treatment as text */
49 PIPE_MSG /* Display message (returned as command string) */
52 enum parseflags {
53 PARSE_DEFAULT = 0,
54 PARSE_DECRYPT = 01,
55 PARSE_PARTS = 02
58 static void onpipe(int signo);
60 static void _parsemultipart(struct message *zmp, struct mimepart *ip,
61 enum parseflags pf, int level);
63 /* Going for user display, print Part: info string */
64 static void _print_part_info(struct str *out, struct mimepart *mip,
65 struct ignoretab *doign, int level);
67 /* Adjust output statistics */
68 SINLINE void _addstats(off_t *stats, off_t lines, off_t bytes);
70 /* Call mime_write() as approbiate and adjust statistics */
71 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
72 enum conversion convert, enum sendaction action,
73 struct quoteflt *qf, off_t *stats, struct str *rest);
75 /* Query possible pipe command for MIME type */
76 static enum pipeflags _pipecmd(char **result, char const *content_type);
78 /* Create a pipe */
79 static FILE * _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote,
80 bool_t async);
82 static int sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
83 struct ignoretab *doign, struct quoteflt *qf,
84 enum sendaction action, off_t *stats, int level);
85 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
86 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
87 enum parseflags pf, int level);
88 static void newpart(struct mimepart *ip, struct mimepart **np, off_t offs,
89 int *part);
90 static void endpart(struct mimepart **np, off_t xoffs, long lines);
91 static void parse822(struct message *zmp, struct mimepart *ip,
92 enum parseflags pf, int level);
93 #ifdef HAVE_SSL
94 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
95 enum parseflags pf, int level);
96 #endif
97 static FILE *newfile(struct mimepart *ip, int *ispipe);
98 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
99 struct quoteflt *qf, off_t *stats);
100 static void statusput(const struct message *mp, FILE *obuf,
101 struct quoteflt *qf, off_t *stats);
102 static void xstatusput(const struct message *mp, FILE *obuf,
103 struct quoteflt *qf, off_t *stats);
104 static void put_from_(FILE *fp, struct mimepart *ip, off_t *stats);
106 static sigjmp_buf pipejmp;
108 /*ARGSUSED*/
109 static void
110 onpipe(int signo)
112 (void)signo;
113 siglongjmp(pipejmp, 1);
116 static void
117 _parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
118 int level)
121 * TODO Instead of the recursive multiple run parse we have today,
122 * TODO the send/MIME layer rewrite must create a "tree" of parts with
123 * TODO a single-pass parse, then address each part directly as
124 * TODO necessary; since boundaries start with -- and the content
125 * TODO rather forms a stack this is pretty cheap indeed!
127 struct mimepart *np = NULL;
128 char *boundary, *line = NULL;
129 size_t linesize = 0, linelen, cnt, boundlen;
130 FILE *ibuf;
131 off_t offs;
132 int part = 0;
133 long lines = 0;
135 if ((boundary = mime_get_boundary(ip->m_ct_type, &linelen)) == NULL)
136 return;
137 boundlen = linelen;
138 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
139 return;
140 cnt = ip->m_size;
141 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
142 if (line[0] == '\n')
143 break;
144 offs = ftell(ibuf);
145 newpart(ip, &np, offs, NULL);
146 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
147 /* XXX linelen includes LF */
148 if (! ((lines > 0 || part == 0) && linelen > boundlen &&
149 memcmp(line, boundary, boundlen) == 0)) {
150 ++lines;
151 continue;
153 /* Subpart boundary? */
154 if (line[boundlen] == '\n') {
155 offs = ftell(ibuf);
156 if (part > 0) {
157 endpart(&np, offs - boundlen - 2, lines);
158 newpart(ip, &np, offs - boundlen - 2, NULL);
160 endpart(&np, offs, 2);
161 newpart(ip, &np, offs, &part);
162 lines = 0;
163 continue;
166 * Final boundary? Be aware of cases where there is no
167 * separating newline in between boundaries, as has been seen
168 * in a message with "Content-Type: multipart/appledouble;"
170 if (linelen < boundlen + 2)
171 continue;
172 linelen -= boundlen + 2;
173 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
174 (linelen > 0 && line[boundlen + 2] != '\n'))
175 continue;
176 offs = ftell(ibuf);
177 if (part != 0) {
178 endpart(&np, offs - boundlen - 4, lines);
179 newpart(ip, &np, offs - boundlen - 4, NULL);
181 endpart(&np, offs + cnt, 2);
182 break;
184 if (np) {
185 offs = ftell(ibuf);
186 endpart(&np, offs, lines);
188 for (np = ip->m_multipart; np; np = np->m_nextpart)
189 if (np->m_mimecontent != MIME_DISCARD)
190 parsepart(zmp, np, pf, level + 1);
191 free(line);
194 static void
195 _print_part_info(struct str *out, struct mimepart *mip,
196 struct ignoretab *doign, int level)
198 struct str ct = {NULL, 0}, cd = {NULL, 0};
199 char const *ps;
201 /* Max. 24 */
202 if (is_ign("content-type", 12, doign)) {
203 out->s = mip->m_ct_type_plain;
204 out->l = strlen(out->s);
205 ct.s = ac_alloc(out->l + 2 +1);
206 ct.s[0] = ',';
207 ct.s[1] = ' ';
208 ct.l = 2;
209 if (is_prefix("application/", out->s)) {
210 memcpy(ct.s + 2, "appl../", 7);
211 ct.l += 7;
212 out->l -= 12;
213 out->s += 12;
214 out->l = MIN(out->l, 17);
215 } else
216 out->l = MIN(out->l, 24);
217 memcpy(ct.s + ct.l, out->s, out->l);
218 ct.l += out->l;
219 ct.s[ct.l] = '\0';
222 /* Max. 27 */
223 if (is_ign("content-disposition", 19, doign) &&
224 mip->m_filename != NULL) {
225 struct str ti, to;
227 ti.l = strlen(ti.s = mip->m_filename);
228 mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV | TD_DELCTRL);
229 to.l = MIN(to.l, 25);
230 cd.s = ac_alloc(to.l + 2 +1);
231 cd.s[0] = ',';
232 cd.s[1] = ' ';
233 memcpy(cd.s + 2, to.s, to.l);
234 to.l += 2;
235 cd.s[to.l] = '\0';
236 free(to.s);
239 /* Take care of "99.99", i.e., 5 */
240 if ((ps = mip->m_partstring) == NULL || ps[0] == '\0')
241 ps = "?";
244 * Assume maximum possible sizes for 64 bit integers here to avoid any
245 * buffer overflows just in case we have a bug somewhere and / or the
246 * snprintf() is our internal version that doesn't really provide hard
247 * buffer cuts
249 #define __msg "%s[-- #%s : %lu/%lu%s%s --]\n"
250 out->l = sizeof(__msg) + strlen(ps) + 2*21 + ct.l + cd.l + 1;
251 out->s = salloc(out->l);
252 out->l = snprintf(out->s, out->l, __msg,
253 (level || (ps[0] != '1' && ps[1] == '\0')) ? "\n" : "",
254 ps, (ul_it)mip->m_lines, (ul_it)mip->m_size,
255 (ct.s != NULL ? ct.s : ""), (cd.s != NULL ? cd.s : ""));
256 out->s[out->l] = '\0';
257 #undef __msg
259 if (cd.s != NULL)
260 ac_free(cd.s);
261 if (ct.s != NULL)
262 ac_free(ct.s);
265 SINLINE void
266 _addstats(off_t *stats, off_t lines, off_t bytes)
268 if (stats != NULL) {
269 if (stats[0] >= 0)
270 stats[0] += lines;
271 stats[1] += bytes;
275 SINLINE ssize_t
276 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
277 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
279 ssize_t sz = 0, n;
280 int flags;
281 char const *cp;
283 #if 0
284 Well ... it turns out to not work like that since of course a valid
285 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
286 after an empty line has been seen, which cannot be detected that easily
287 right here!
288 ifdef HAVE_DEBUG /* TODO assert legacy */
289 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
290 * TODO other input situations handle RFC 4155 OR, if newly generated,
291 * TODO enforce quoted-printable if there is From_, as "required" by
292 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
293 * TODO if it may happen in this path, we should just treat decryption
294 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
295 if (action == SEND_MBOX || action == SEND_DECRYPT)
296 assert(! is_head(buf, len));
297 #else
298 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
299 is_head(buf, len)) {
300 putc('>', fp);
301 ++sz;
303 #endif
305 flags = ((int)action & _TD_EOF);
306 action &= ~_TD_EOF;
307 n = mime_write(buf, len, fp,
308 action == SEND_MBOX ? CONV_NONE : convert,
309 flags |
310 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
311 action == SEND_QUOTE ||
312 action == SEND_QUOTE_ALL ?
313 TD_ISPR|TD_ICONV :
314 action == SEND_TOSRCH || action == SEND_TOPIPE ?
315 TD_ICONV :
316 action == SEND_TOFLTR ?
317 TD_DELCTRL :
318 action == SEND_SHOW ?
319 TD_ISPR : TD_NONE),
320 qf, rest);
321 if (n < 0)
322 sz = n;
323 else if (n > 0) {
324 sz = (ssize_t)((size_t)sz + n);
325 n = 0;
326 if (stats != NULL && stats[0] != -1)
327 for (cp = buf; cp < &buf[sz]; ++cp)
328 if (*cp == '\n')
329 ++n;
330 _addstats(stats, n, sz);
332 return sz;
335 static enum pipeflags
336 _pipecmd(char **result, char const *content_type)
338 enum pipeflags ret;
339 char *s, *cp;
340 char const *cq;
342 ret = PIPE_NULL;
343 *result = NULL;
344 if (content_type == NULL)
345 goto jleave;
347 /* First check wether there is a special pipe-MIMETYPE handler */
348 s = ac_alloc(strlen(content_type) + 6);
349 memcpy(s, "pipe-", 5);
350 cp = &s[5];
351 cq = content_type;
353 *cp++ = lowerconv(*cq);
354 while (*cq++ != '\0');
355 cp = value(s);
356 ac_free(s);
358 if (cp == NULL)
359 goto jleave;
361 /* User specified a command, inspect for special cases */
362 if (cp[0] != '@') {
363 /* Normal command line */
364 ret = PIPE_COMM;
365 *result = cp;
366 } else if (*++cp == '\0') {
367 /* Treat as plain text */
368 ret = PIPE_TEXT;
369 } else if (! msglist_is_single) {
370 /* Viewing multiple messages in one go, don't block system */
371 ret = PIPE_MSG;
372 *result = UNCONST(tr(86,
373 "[Directly address message only to display this]\n"));
374 } else {
375 /* Viewing a single message only */
376 #if 0 /* TODO send/MIME layer rewrite: when we have a single-pass parser
377 * TODO then the parsing phase and the send phase will be separated;
378 * TODO that allows us to ask a user *before* we start the send, i.e.,
379 * TODO *before* a pager pipe is setup (which is the problem with
380 * TODO the '#if 0' code here) */
381 size_t l = strlen(content_type);
382 char const *x = tr(999, "Should i display a part `%s' (y/n)? ");
383 s = ac_alloc(l += strlen(x) + 1);
384 snprintf(s, l - 1, x, content_type);
385 l = yorn(s);
386 puts(""); /* .. we've hijacked a pipe 8-] ... */
387 ac_free(s);
388 if (! l) {
389 x = tr(210, "[User skipped diplay]\n");
390 ret = PIPE_MSG;
391 *result = UNCONST(x);
392 } else
393 #endif
394 if (cp[0] == '&')
395 /* Asynchronous command, normal command line */
396 ret = PIPE_ASYNC, *result = ++cp;
397 else
398 ret = PIPE_COMM, *result = cp;
400 jleave:
401 return ret;
404 static FILE *
405 _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote, bool_t async)
407 char const *sh;
408 FILE *rbuf = *qbuf;
410 if (quote) {
411 char *tempPipe;
413 if ((*qbuf = Ftemp(&tempPipe, "Rp", "w+", 0600, 1)) == NULL) {
414 perror(tr(173, "tmpfile"));
415 *qbuf = rbuf;
417 unlink(tempPipe);
418 Ftfree(&tempPipe);
419 async = FAL0;
421 if ((sh = ok_vlook(SHELL)) == NULL)
422 sh = XSHELL;
423 if ((rbuf = Popen(pipecomm, "W", sh,
424 async ? -1 : fileno(*qbuf))) == NULL)
425 perror(pipecomm);
426 else {
427 fflush(*qbuf);
428 if (*qbuf != stdout)
429 fflush(stdout);
431 return rbuf;
435 * Send message described by the passed pointer to the
436 * passed output buffer. Return -1 on error.
437 * Adjust the status: field if need be.
438 * If doign is given, suppress ignored header fields.
439 * prefix is a string to prepend to each output line.
440 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
441 * stats[0] is line count, stats[1] is character count. stats may be NULL.
442 * Note that stats[0] is valid for SEND_MBOX only.
444 FL int
445 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
446 char const *prefix, enum sendaction action, off_t *stats)
448 int rv = -1, c;
449 size_t cnt, sz, i;
450 FILE *ibuf;
451 enum parseflags pf;
452 struct mimepart *ip;
453 struct quoteflt qf;
455 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
456 did_print_dot = 1;
457 if (stats)
458 stats[0] = stats[1] = 0;
459 quoteflt_init(&qf, prefix);
462 * First line is the From_ line, so no headers there to worry about.
464 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
465 return -1;
467 cnt = mp->m_size;
468 sz = 0;
469 if (mp->m_flag & MNOFROM) {
470 if (doign != allignore && doign != fwdignore &&
471 action != SEND_RFC822)
472 sz = fprintf(obuf, "%.*sFrom %s %s\n",
473 (int)qf.qf_pfix_len,
474 (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
475 fakefrom(mp), fakedate(mp->m_time));
476 } else {
477 if (qf.qf_pfix_len > 0 && doign != allignore &&
478 doign != fwdignore && action != SEND_RFC822) {
479 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix,
480 qf.qf_pfix_len, obuf);
481 if (i != qf.qf_pfix_len)
482 goto jleave;
483 sz += i;
485 while (cnt && (c = getc(ibuf)) != EOF) {
486 if (doign != allignore && doign != fwdignore &&
487 action != SEND_RFC822) {
488 putc(c, obuf);
489 sz++;
491 cnt--;
492 if (c == '\n')
493 break;
496 if (sz)
497 _addstats(stats, 1, sz);
499 pf = 0;
500 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
501 pf |= PARSE_DECRYPT|PARSE_PARTS;
502 if ((ip = parsemsg(mp, pf)) == NULL)
503 goto jleave;
505 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
506 jleave:
507 quoteflt_destroy(&qf);
508 return rv;
511 static int
512 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
513 struct ignoretab *doign, struct quoteflt *qf,
514 enum sendaction volatile action, off_t *volatile stats, int level)
516 int volatile ispipe, rt = 0;
517 struct str rest;
518 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
519 size_t linesize = 0, linelen, cnt;
520 int dostat, infld = 0, ignoring = 1, isenc, c;
521 struct mimepart *volatile np;
522 FILE *volatile ibuf = NULL, *volatile pbuf = obuf,
523 *volatile qbuf = obuf, *origobuf = obuf;
524 enum conversion volatile convert;
525 sighandler_type volatile oldpipe = SIG_DFL;
526 long lineno = 0;
528 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
529 action != SEND_MBOX && action != SEND_RFC822 &&
530 action != SEND_SHOW)
531 goto skip;
532 dostat = 0;
533 if (level == 0) {
534 if (doign != NULL) {
535 if (!is_ign("status", 6, doign))
536 dostat |= 1;
537 if (!is_ign("x-status", 8, doign))
538 dostat |= 2;
539 } else
540 dostat = 3;
542 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
543 return -1;
544 cnt = ip->m_size;
545 if (ip->m_mimecontent == MIME_DISCARD)
546 goto skip;
548 if ((ip->m_flag & MNOFROM) == 0)
549 while (cnt && (c = getc(ibuf)) != EOF) {
550 cnt--;
551 if (c == '\n')
552 break;
554 isenc = 0;
555 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
556 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
557 action == SEND_TOSRCH || action == SEND_TOFLTR ?
558 CONV_FROMHDR : CONV_NONE;
560 /* Work the headers */
561 quoteflt_reset(qf, obuf);
562 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
563 lineno++;
564 if (line[0] == '\n') {
566 * If line is blank, we've reached end of
567 * headers, so force out status: field
568 * and note that we are no longer in header
569 * fields
571 if (dostat & 1)
572 statusput(zmp, obuf, qf, stats);
573 if (dostat & 2)
574 xstatusput(zmp, obuf, qf, stats);
575 if (doign != allignore)
576 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
577 qf, stats, NULL);
578 break;
580 isenc &= ~1;
581 if (infld && blankchar(line[0])) {
583 * If this line is a continuation (via space or tab)
584 * of a previous header field, determine if the start
585 * of the line is a MIME encoded word.
587 if (isenc & 2) {
588 for (cp = line; blankchar(*cp); cp++);
589 if (cp > line && linelen - (cp - line) > 8 &&
590 cp[0] == '=' && cp[1] == '?')
591 isenc |= 1;
593 } else {
595 * Pick up the header field if we have one.
597 for (cp = line; (c = *cp & 0377) && c != ':' &&
598 !spacechar(c); ++cp)
600 cp2 = cp;
601 while (spacechar(*cp))
602 ++cp;
603 if (cp[0] != ':' && level == 0 && lineno == 1) {
605 * Not a header line, force out status:
606 * This happens in uucp style mail where
607 * there are no headers at all.
609 if (dostat & 1)
610 statusput(zmp, obuf, qf, stats);
611 if (dostat & 2)
612 xstatusput(zmp, obuf, qf, stats);
613 if (doign != allignore)
614 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX,
615 qf, stats, NULL);
616 break;
619 * If it is an ignored field and
620 * we care about such things, skip it.
622 c = *cp2;
623 *cp2 = 0; /* temporarily null terminate */
624 if ((doign && is_ign(line, cp2 - line, doign)) ||
625 (action == SEND_MBOX &&
626 !ok_blook(keep_content_length) &&
627 (asccasecmp(line, "content-length")==0
628 || asccasecmp(line, "lines") == 0)))
629 ignoring = 1;
630 else if (asccasecmp(line, "status") == 0) {
632 * If the field is "status," go compute
633 * and print the real Status: field
635 if (dostat & 1) {
636 statusput(zmp, obuf, qf, stats);
637 dostat &= ~1;
638 ignoring = 1;
640 } else if (asccasecmp(line, "x-status") == 0) {
642 * If the field is "status," go compute
643 * and print the real Status: field
645 if (dostat & 2) {
646 xstatusput(zmp, obuf, qf, stats);
647 dostat &= ~2;
648 ignoring = 1;
650 } else
651 ignoring = 0;
652 *cp2 = c;
653 infld = 1;
656 * Determine if the end of the line is a MIME encoded word.
658 isenc &= ~2;
659 if (cnt && (c = getc(ibuf)) != EOF) {
660 if (blankchar(c)) {
661 if (linelen > 0 && line[linelen - 1] == '\n')
662 cp = &line[linelen - 2];
663 else
664 cp = &line[linelen - 1];
665 while (cp >= line && whitechar(*cp))
666 ++cp;
667 if (cp - line > 8 && cp[0] == '=' &&
668 cp[-1] == '?')
669 isenc |= 2;
671 ungetc(c, ibuf);
673 if (!ignoring) {
674 size_t len = linelen;
675 start = line;
676 if (action == SEND_TODISP ||
677 action == SEND_TODISP_ALL ||
678 action == SEND_QUOTE ||
679 action == SEND_QUOTE_ALL ||
680 action == SEND_TOSRCH ||
681 action == SEND_TOFLTR) {
683 * Strip blank characters if two MIME-encoded
684 * words follow on continuing lines.
686 if (isenc & 1)
687 while (len > 0 && blankchar(*start)) {
688 ++start;
689 --len;
691 if (isenc & 2)
692 if (len > 0 && start[len - 1] == '\n')
693 --len;
694 while (len > 0 && blankchar(start[len - 1]))
695 --len;
697 _out(start, len, obuf, convert, action, qf, stats,
698 NULL);
699 if (ferror(obuf)) {
700 free(line);
701 return -1;
705 quoteflt_flush(qf);
706 free(line);
707 line = NULL;
709 skip:
710 switch (ip->m_mimecontent) {
711 case MIME_822:
712 switch (action) {
713 case SEND_TOFLTR:
714 putc('\0', obuf);
715 /*FALLTHRU*/
716 case SEND_TODISP:
717 case SEND_TODISP_ALL:
718 case SEND_QUOTE:
719 case SEND_QUOTE_ALL:
720 if (ok_blook(rfc822_body_from_)) {
721 if (qf->qf_pfix_len > 0) {
722 size_t i = fwrite(qf->qf_pfix,
723 sizeof *qf->qf_pfix,
724 qf->qf_pfix_len, obuf);
725 if (i == qf->qf_pfix_len)
726 _addstats(stats, 0, i);
728 put_from_(obuf, ip->m_multipart, stats);
730 /*FALLTHRU*/
731 case SEND_TOSRCH:
732 case SEND_DECRYPT:
733 goto multi;
734 case SEND_TOFILE:
735 case SEND_TOPIPE:
736 if (ok_blook(rfc822_body_from_))
737 put_from_(obuf, ip->m_multipart, stats);
738 /*FALLTHRU*/
739 case SEND_MBOX:
740 case SEND_RFC822:
741 case SEND_SHOW:
742 break;
744 break;
745 case MIME_TEXT_HTML:
746 if (action == SEND_TOFLTR)
747 putc('\b', obuf);
748 /*FALLTHRU*/
749 case MIME_TEXT:
750 case MIME_TEXT_PLAIN:
751 switch (action) {
752 case SEND_TODISP:
753 case SEND_TODISP_ALL:
754 case SEND_QUOTE:
755 case SEND_QUOTE_ALL:
756 ispipe = TRU1;
757 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
758 case PIPE_MSG:
759 _out(pipecomm, strlen(pipecomm), obuf,
760 CONV_NONE, SEND_MBOX, qf, stats, NULL);
761 pipecomm = NULL;
762 /* FALLTRHU */
763 case PIPE_TEXT:
764 case PIPE_COMM:
765 case PIPE_ASYNC:
766 case PIPE_NULL:
767 break;
769 /* FALLTRHU */
770 default:
771 break;
773 break;
774 case MIME_DISCARD:
775 if (action != SEND_DECRYPT)
776 return rt;
777 break;
778 case MIME_PKCS7:
779 if (action != SEND_MBOX && action != SEND_RFC822 &&
780 action != SEND_SHOW && ip->m_multipart)
781 goto multi;
782 /*FALLTHRU*/
783 default:
784 switch (action) {
785 case SEND_TODISP:
786 case SEND_TODISP_ALL:
787 case SEND_QUOTE:
788 case SEND_QUOTE_ALL:
789 ispipe = TRU1;
790 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
791 case PIPE_MSG:
792 _out(pipecomm, strlen(pipecomm), obuf,
793 CONV_NONE, SEND_MBOX, qf, stats, NULL);
794 pipecomm = NULL;
795 break;
796 case PIPE_ASYNC:
797 ispipe = FAL0;
798 /* FALLTHRU */
799 case PIPE_COMM:
800 case PIPE_NULL:
801 break;
802 case PIPE_TEXT:
803 goto jcopyout; /* break; break; */
805 if (pipecomm != NULL)
806 break;
807 if (level == 0 && cnt) {
808 char const *x = tr(210, "[Binary content]\n");
809 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
810 qf, stats, NULL);
812 /*FALLTHRU*/
813 case SEND_TOFLTR:
814 return rt;
815 case SEND_TOFILE:
816 case SEND_TOPIPE:
817 case SEND_TOSRCH:
818 case SEND_DECRYPT:
819 case SEND_MBOX:
820 case SEND_RFC822:
821 case SEND_SHOW:
822 break;
824 break;
825 case MIME_ALTERNATIVE:
826 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
827 !ok_blook(print_alternatives)) {
828 bool_t doact = FAL0;
829 for (np = ip->m_multipart; np; np = np->m_nextpart)
830 if (np->m_mimecontent == MIME_TEXT_PLAIN)
831 doact = TRU1;
832 if (doact) {
833 for (np = ip->m_multipart; np;
834 np = np->m_nextpart) {
835 if (np->m_ct_type_plain != NULL &&
836 action != SEND_QUOTE) {
837 _print_part_info(&rest, np,
838 doign, level);
839 _out(rest.s, rest.l, obuf,
840 CONV_NONE, SEND_MBOX,
841 qf, stats, NULL);
843 if (doact && np->m_mimecontent ==
844 MIME_TEXT_PLAIN) {
845 doact = FAL0;
846 rt = sendpart(zmp, np, obuf,
847 doign, qf, action,
848 stats, level + 1);
849 quoteflt_reset(qf, origobuf);
850 if (rt < 0)
851 break;
854 return rt;
857 /*FALLTHRU*/
858 case MIME_MULTI:
859 case MIME_DIGEST:
860 switch (action) {
861 case SEND_TODISP:
862 case SEND_TODISP_ALL:
863 case SEND_QUOTE:
864 case SEND_QUOTE_ALL:
865 case SEND_TOFILE:
866 case SEND_TOPIPE:
867 case SEND_TOSRCH:
868 case SEND_TOFLTR:
869 case SEND_DECRYPT:
870 multi:
871 if ((action == SEND_TODISP ||
872 action == SEND_TODISP_ALL) &&
873 ip->m_multipart != NULL &&
874 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
875 ip->m_multipart->m_nextpart == NULL) {
876 char const *x = tr(85,
877 "[Missing multipart boundary - "
878 "use \"show\" to display "
879 "the raw message]\n");
880 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
881 qf, stats, NULL);
883 for (np = ip->m_multipart; np; np = np->m_nextpart) {
884 if (np->m_mimecontent == MIME_DISCARD &&
885 action != SEND_DECRYPT)
886 continue;
887 ispipe = FAL0;
888 switch (action) {
889 case SEND_TOFILE:
890 if (np->m_partstring &&
891 strcmp(np->m_partstring,
892 "1") == 0)
893 break;
894 stats = NULL;
895 if ((obuf = newfile(np,
896 UNVOLATILE(&ispipe)))
897 == NULL)
898 continue;
899 if (!ispipe)
900 break;
901 if (sigsetjmp(pipejmp, 1)) {
902 rt = -1;
903 goto jpipe_close;
905 oldpipe = safe_signal(SIGPIPE, onpipe);
906 break;
907 case SEND_TODISP:
908 case SEND_TODISP_ALL:
909 case SEND_QUOTE_ALL:
910 if (ip->m_mimecontent != MIME_MULTI &&
911 ip->m_mimecontent !=
912 MIME_ALTERNATIVE &&
913 ip->m_mimecontent !=
914 MIME_DIGEST)
915 break;
916 _print_part_info(&rest, np, doign,
917 level);
918 _out(rest.s, rest.l, obuf,
919 CONV_NONE, SEND_MBOX, qf,
920 stats, NULL);
921 break;
922 case SEND_TOFLTR:
923 putc('\0', obuf);
924 /*FALLTHRU*/
925 case SEND_MBOX:
926 case SEND_RFC822:
927 case SEND_SHOW:
928 case SEND_TOSRCH:
929 case SEND_QUOTE:
930 case SEND_DECRYPT:
931 case SEND_TOPIPE:
932 break;
935 quoteflt_flush(qf);
936 if (sendpart(zmp, np, obuf, doign, qf,
937 action, stats, level+1) < 0)
938 rt = -1;
939 quoteflt_reset(qf, origobuf);
940 if (action == SEND_QUOTE)
941 break;
942 if (action == SEND_TOFILE && obuf != origobuf) {
943 if (!ispipe)
944 Fclose(obuf);
945 else {
946 jpipe_close: safe_signal(SIGPIPE, SIG_IGN);
947 Pclose(obuf, TRU1);
948 safe_signal(SIGPIPE, oldpipe);
952 return rt;
953 case SEND_MBOX:
954 case SEND_RFC822:
955 case SEND_SHOW:
956 break;
961 * Copy out message body
963 jcopyout:
964 if (doign == allignore && level == 0) /* skip final blank line */
965 cnt--;
966 switch (ip->m_mimeenc) {
967 case MIME_BIN:
968 if (stats)
969 stats[0] = -1;
970 /*FALLTHRU*/
971 case MIME_7B:
972 case MIME_8B:
973 convert = CONV_NONE;
974 break;
975 case MIME_QP:
976 convert = CONV_FROMQP;
977 break;
978 case MIME_B64:
979 switch (ip->m_mimecontent) {
980 case MIME_TEXT:
981 case MIME_TEXT_PLAIN:
982 case MIME_TEXT_HTML:
983 convert = CONV_FROMB64_T;
984 break;
985 default:
986 convert = CONV_FROMB64;
988 break;
989 default:
990 convert = CONV_NONE;
992 if (action == SEND_DECRYPT || action == SEND_MBOX ||
993 action == SEND_RFC822 || action == SEND_SHOW)
994 convert = CONV_NONE;
995 #ifdef HAVE_ICONV
996 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
997 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
998 action == SEND_TOSRCH) &&
999 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1000 ip->m_mimecontent == MIME_TEXT_HTML ||
1001 ip->m_mimecontent == MIME_TEXT)) {
1002 char const *tcs = charset_get_lc();
1004 if (iconvd != (iconv_t)-1)
1005 n_iconv_close(iconvd);
1006 /* TODO Since Base64 has an odd 4:3 relation in between input
1007 * TODO and output an input line may end with a partial
1008 * TODO multibyte character; this is no problem at all unless
1009 * TODO we send to the display or whatever, i.e., ensure
1010 * TODO makeprint() or something; to avoid this trap, *force*
1011 * TODO iconv(), in which case this layer will handle leftovers
1012 * TODO correctly */
1013 if (convert == CONV_FROMB64_T ||
1014 (asccasecmp(tcs, ip->m_charset) &&
1015 asccasecmp(charset_get_7bit(),
1016 ip->m_charset))) {
1017 iconvd = n_iconv_open(tcs, ip->m_charset);
1018 /* XXX Don't bail out if we cannot iconv(3) here;
1019 * XXX alternatively we could avoid trying to open
1020 * XXX if ip->m_charset is "unknown-8bit", which was
1021 * XXX the one that has bitten me?? */
1023 * TODO errors should DEFINETELY not be scrolled away!
1024 * TODO what about an error buffer (think old shsp(1)),
1025 * TODO re-dump errors since last snapshot when the
1026 * TODO command loop enters again? i.e., at least print
1027 * TODO "There were errors ?" before the next prompt,
1028 * TODO so that the user can look at the error buffer?
1030 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1031 fprintf(stderr, tr(179,
1032 "Cannot convert from %s to %s\n"),
1033 ip->m_charset, tcs);
1034 /*return -1;*/
1038 #endif
1039 if (pipecomm != NULL &&
1040 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
1041 action == SEND_QUOTE || action == SEND_QUOTE_ALL)) {
1042 qbuf = obuf;
1043 pbuf = _pipefile(pipecomm, UNVOLATILE(&qbuf),
1044 action == SEND_QUOTE || action == SEND_QUOTE_ALL,
1045 !ispipe);
1046 action = SEND_TOPIPE;
1047 if (pbuf != qbuf) {
1048 oldpipe = safe_signal(SIGPIPE, onpipe);
1049 if (sigsetjmp(pipejmp, 1))
1050 goto end;
1052 } else
1053 pbuf = qbuf = obuf;
1056 bool_t eof;
1057 size_t save_qf_pfix_len = qf->qf_pfix_len;
1058 off_t *save_stats = stats;
1060 if (pbuf != origobuf) {
1061 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1062 stats = NULL;
1064 eof = FAL0;
1065 rest.s = NULL;
1066 rest.l = 0;
1068 quoteflt_reset(qf, pbuf);
1069 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1070 joutln:
1071 if (_out(line, linelen, pbuf, convert, action, qf, stats,
1072 &rest) < 0 || ferror(pbuf)) {
1073 rt = -1; /* XXX Should bail away?! */
1074 break;
1077 if (!eof && rest.l != 0) {
1078 linelen = 0;
1079 eof = TRU1;
1080 action |= _TD_EOF;
1081 goto joutln;
1083 quoteflt_flush(qf);
1084 if (rest.s != NULL)
1085 free(rest.s);
1087 if (pbuf != origobuf) {
1088 qf->qf_pfix_len = save_qf_pfix_len;
1089 stats = save_stats;
1093 end: free(line);
1094 if (pbuf != qbuf) {
1095 safe_signal(SIGPIPE, SIG_IGN);
1096 Pclose(pbuf, ispipe);
1097 safe_signal(SIGPIPE, oldpipe);
1098 if (qbuf != obuf)
1099 pipecpy(qbuf, obuf, origobuf, qf, stats);
1101 #ifdef HAVE_ICONV
1102 if (iconvd != (iconv_t)-1)
1103 n_iconv_close(iconvd);
1104 #endif
1105 return rt;
1108 static struct mimepart *
1109 parsemsg(struct message *mp, enum parseflags pf)
1111 struct mimepart *ip;
1113 ip = csalloc(1, sizeof *ip);
1114 ip->m_flag = mp->m_flag;
1115 ip->m_have = mp->m_have;
1116 ip->m_block = mp->m_block;
1117 ip->m_offset = mp->m_offset;
1118 ip->m_size = mp->m_size;
1119 ip->m_xsize = mp->m_xsize;
1120 ip->m_lines = mp->m_lines;
1121 ip->m_xlines = mp->m_lines;
1122 if (parsepart(mp, ip, pf, 0) != OKAY)
1123 return NULL;
1124 return ip;
1127 static enum okay
1128 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1129 int level)
1131 char *cp;
1133 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
1134 if (ip->m_ct_type != NULL) {
1135 ip->m_ct_type_plain = savestr(ip->m_ct_type);
1136 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
1137 *cp = '\0';
1138 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
1139 ip->m_ct_type_plain = UNCONST("message/rfc822");
1140 else
1141 ip->m_ct_type_plain = UNCONST("text/plain");
1143 if (ip->m_ct_type)
1144 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
1145 if (ip->m_charset == NULL)
1146 ip->m_charset = charset_get_7bit();
1147 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
1148 (struct message *)ip);
1149 ip->m_mimeenc = ip->m_ct_transfer_enc ?
1150 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
1151 if ((cp = hfield1("content-disposition", (struct message *)ip)) == 0 ||
1152 (ip->m_filename = mime_getparam("filename", cp)) == 0)
1153 if (ip->m_ct_type != NULL)
1154 ip->m_filename = mime_getparam("name", ip->m_ct_type);
1155 ip->m_mimecontent = mime_classify_content_of_part(ip);
1157 if (pf & PARSE_PARTS) {
1158 if (level > 9999) {
1159 fprintf(stderr, tr(36,
1160 "MIME content too deeply nested\n"));
1161 return STOP;
1163 switch (ip->m_mimecontent) {
1164 case MIME_PKCS7:
1165 if (pf & PARSE_DECRYPT) {
1166 #ifdef HAVE_SSL
1167 parsepkcs7(zmp, ip, pf, level);
1168 break;
1169 #else
1170 fprintf(stderr, tr(225,
1171 "No SSL support compiled in.\n"));
1172 return STOP;
1173 #endif
1175 /*FALLTHRU*/
1176 default:
1177 break;
1178 case MIME_MULTI:
1179 case MIME_ALTERNATIVE:
1180 case MIME_DIGEST:
1181 _parsemultipart(zmp, ip, pf, level);
1182 break;
1183 case MIME_822:
1184 parse822(zmp, ip, pf, level);
1185 break;
1188 return OKAY;
1191 static void
1192 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
1194 struct mimepart *pp;
1195 size_t sz;
1197 *np = csalloc(1, sizeof **np);
1198 (*np)->m_flag = MNOFROM;
1199 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
1200 (*np)->m_block = mailx_blockof(offs);
1201 (*np)->m_offset = mailx_offsetof(offs);
1202 if (part) {
1203 (*part)++;
1204 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
1205 sz += 20;
1206 (*np)->m_partstring = salloc(sz);
1207 if (ip->m_partstring)
1208 snprintf((*np)->m_partstring, sz, "%s.%u",
1209 ip->m_partstring, *part);
1210 else
1211 snprintf((*np)->m_partstring, sz, "%u", *part);
1212 } else
1213 (*np)->m_mimecontent = MIME_DISCARD;
1214 (*np)->m_parent = ip;
1215 if (ip->m_multipart) {
1216 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
1217 pp->m_nextpart = *np;
1218 } else
1219 ip->m_multipart = *np;
1222 static void
1223 endpart(struct mimepart **np, off_t xoffs, long lines)
1225 off_t offs;
1227 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
1228 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
1229 (*np)->m_lines = (*np)->m_xlines = lines;
1230 *np = NULL;
1233 static void
1234 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1235 int level)
1237 int c, lastc = '\n';
1238 size_t cnt;
1239 FILE *ibuf;
1240 off_t offs;
1241 struct mimepart *np;
1242 long lines;
1244 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
1245 return;
1246 cnt = ip->m_size;
1247 lines = ip->m_lines;
1248 while (cnt && ((c = getc(ibuf)) != EOF)) {
1249 cnt--;
1250 if (c == '\n') {
1251 lines--;
1252 if (lastc == '\n')
1253 break;
1255 lastc = c;
1257 offs = ftell(ibuf);
1258 np = csalloc(1, sizeof *np);
1259 np->m_flag = MNOFROM;
1260 np->m_have = HAVE_HEADER|HAVE_BODY;
1261 np->m_block = mailx_blockof(offs);
1262 np->m_offset = mailx_offsetof(offs);
1263 np->m_size = np->m_xsize = cnt;
1264 np->m_lines = np->m_xlines = lines;
1265 np->m_partstring = ip->m_partstring;
1266 np->m_parent = ip;
1267 ip->m_multipart = np;
1268 if (ok_blook(rfc822_body_from_)) {
1269 substdate((struct message *)np);
1270 np->m_from = fakefrom((struct message *)np);
1272 parsepart(zmp, np, pf, level+1);
1275 #ifdef HAVE_SSL
1276 static void
1277 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1278 int level)
1280 struct message m, *xmp;
1281 struct mimepart *np;
1282 char *to, *cc;
1284 memcpy(&m, ip, sizeof m);
1285 to = hfield1("to", zmp);
1286 cc = hfield1("cc", zmp);
1287 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
1288 np = csalloc(1, sizeof *np);
1289 np->m_flag = xmp->m_flag;
1290 np->m_have = xmp->m_have;
1291 np->m_block = xmp->m_block;
1292 np->m_offset = xmp->m_offset;
1293 np->m_size = xmp->m_size;
1294 np->m_xsize = xmp->m_xsize;
1295 np->m_lines = xmp->m_lines;
1296 np->m_xlines = xmp->m_xlines;
1297 np->m_partstring = ip->m_partstring;
1298 if (parsepart(zmp, np, pf, level+1) == OKAY) {
1299 np->m_parent = ip;
1300 ip->m_multipart = np;
1304 #endif
1307 * Get a file for an attachment.
1309 static FILE *
1310 newfile(struct mimepart *ip, int *ispipe)
1312 char *f = ip->m_filename;
1313 struct str in, out;
1314 FILE *fp;
1316 *ispipe = 0;
1317 if (f != NULL && f != (char *)-1) {
1318 in.s = f;
1319 in.l = strlen(f);
1320 mime_fromhdr(&in, &out, TD_ISPR);
1321 memcpy(f, out.s, out.l);
1322 *(f + out.l) = '\0';
1323 free(out.s);
1326 if (options & OPT_INTERACTIVE) {
1327 char *f2, *f3;
1328 jgetname: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1329 ip->m_partstring ? ip->m_partstring : "?",
1330 ip->m_ct_type_plain);
1331 f2 = readstr_input(": ", f != (char *)-1 ? f : NULL);
1332 if (f2 == NULL || *f2 == '\0') {
1333 fprintf(stderr, tr(279, "... skipping this\n"));
1334 return (NULL);
1335 } else if (*f2 == '|')
1336 /* Pipes are expanded by the shell */
1337 f = f2;
1338 else if ((f3 = file_expand(f2)) == NULL)
1339 /* (Error message written by file_expand()) */
1340 goto jgetname;
1341 else
1342 f = f3;
1344 if (f == NULL || f == (char *)-1)
1345 return NULL;
1347 if (*f == '|') {
1348 char const *cp;
1349 cp = ok_vlook(SHELL);
1350 if (cp == NULL)
1351 cp = XSHELL;
1352 fp = Popen(f + 1, "w", cp, 1);
1353 if (! (*ispipe = (fp != NULL)))
1354 perror(f);
1355 } else {
1356 if ((fp = Fopen(f, "w")) == NULL)
1357 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1359 return (fp);
1362 static void
1363 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1364 off_t *stats)
1366 char *line = NULL;
1367 size_t linesize = 0, linelen, cnt;
1368 ssize_t all_sz, sz;
1370 fflush(pipebuf);
1371 rewind(pipebuf);
1372 cnt = fsize(pipebuf);
1373 all_sz = 0;
1375 quoteflt_reset(qf, outbuf);
1376 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0)
1377 != NULL) {
1378 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1379 break;
1380 all_sz += sz;
1382 if ((sz = quoteflt_flush(qf)) > 0)
1383 all_sz += sz;
1384 if (line)
1385 free(line);
1387 if (all_sz > 0 && outbuf == origobuf)
1388 _addstats(stats, 1, all_sz);
1389 fclose(pipebuf);
1393 * Output a reasonable looking status field.
1395 static void
1396 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1397 off_t *stats)
1399 char statout[3];
1400 char *cp = statout;
1402 if (mp->m_flag & MREAD)
1403 *cp++ = 'R';
1404 if ((mp->m_flag & MNEW) == 0)
1405 *cp++ = 'O';
1406 *cp = 0;
1407 if (statout[0]) {
1408 int i = fprintf(obuf, "%.*sStatus: %s\n",
1409 (int)qf->qf_pfix_len,
1410 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1411 statout);
1412 if (i > 0)
1413 _addstats(stats, 1, i);
1417 static void
1418 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1419 off_t *stats)
1421 char xstatout[4];
1422 char *xp = xstatout;
1424 if (mp->m_flag & MFLAGGED)
1425 *xp++ = 'F';
1426 if (mp->m_flag & MANSWERED)
1427 *xp++ = 'A';
1428 if (mp->m_flag & MDRAFTED)
1429 *xp++ = 'T';
1430 *xp = 0;
1431 if (xstatout[0]) {
1432 int i = fprintf(obuf, "%.*sX-Status: %s\n",
1433 (int)qf->qf_pfix_len,
1434 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1435 xstatout);
1436 if (i > 0)
1437 _addstats(stats, 1, i);
1441 static void
1442 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1444 char const *froma, *date, *nl;
1445 int i;
1447 if (ip && ip->m_from) {
1448 froma = ip->m_from;
1449 date = fakedate(ip->m_time);
1450 nl = "\n";
1451 } else {
1452 froma = myname;
1453 date = time_current.tc_ctime;
1454 nl = "";
1457 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1458 if (i > 0)
1459 _addstats(stats, (*nl != '\0'), i);