openssl.c, catd/en_US: remove some useless strings
[s-mailx.git] / send.c
blob26cc4a55756d5951c876e72cea209ab1cc560efd
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 #include "nail.h"
42 enum pipeflags {
43 PIPE_NULL, /* No pipe- mimetype handler */
44 PIPE_COMM, /* Normal command */
45 PIPE_ASYNC, /* Normal command, run asynchronous */
46 PIPE_TEXT, /* @ special command to force treatment as text */
47 PIPE_MSG /* Display message (returned as command string) */
50 enum parseflags {
51 PARSE_DEFAULT = 0,
52 PARSE_DECRYPT = 01,
53 PARSE_PARTS = 02
56 static void onpipe(int signo);
58 static void _parsemultipart(struct message *zmp, struct mimepart *ip,
59 enum parseflags pf, int level);
61 /* Going for user display, print Part: info string */
62 static void _print_part_info(struct str *out, struct mimepart *mip,
63 struct ignoretab *doign, int level);
65 /* Adjust output statistics */
66 SINLINE void _addstats(off_t *stats, off_t lines, off_t bytes);
68 /* Call mime_write() as approbiate and adjust statistics */
69 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
70 enum conversion convert, enum sendaction action,
71 struct quoteflt *qf, off_t *stats, struct str *rest);
73 /* Query possible pipe command for MIME type */
74 static enum pipeflags _pipecmd(char **result, char const *content_type);
76 /* Create a pipe */
77 static FILE * _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote,
78 bool_t async);
80 static int sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
81 struct ignoretab *doign, struct quoteflt *qf,
82 enum sendaction action, off_t *stats, int level);
83 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
84 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
85 enum parseflags pf, int level);
86 static void newpart(struct mimepart *ip, struct mimepart **np, off_t offs,
87 int *part);
88 static void endpart(struct mimepart **np, off_t xoffs, long lines);
89 static void parse822(struct message *zmp, struct mimepart *ip,
90 enum parseflags pf, int level);
91 #ifdef HAVE_SSL
92 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
93 enum parseflags pf, int level);
94 #endif
95 static FILE *newfile(struct mimepart *ip, int *ispipe);
96 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
97 struct quoteflt *qf, off_t *stats);
98 static void statusput(const struct message *mp, FILE *obuf,
99 struct quoteflt *qf, off_t *stats);
100 static void xstatusput(const struct message *mp, FILE *obuf,
101 struct quoteflt *qf, off_t *stats);
102 static void put_from_(FILE *fp, struct mimepart *ip, off_t *stats);
104 static sigjmp_buf pipejmp;
106 /*ARGSUSED*/
107 static void
108 onpipe(int signo)
110 (void)signo;
111 siglongjmp(pipejmp, 1);
114 static void
115 _parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
116 int level)
119 * TODO Instead of the recursive multiple run parse we have today,
120 * TODO the send/MIME layer rewrite must create a "tree" of parts with
121 * TODO a single-pass parse, then address each part directly as
122 * TODO necessary; since boundaries start with -- and the content
123 * TODO rather forms a stack this is pretty cheap indeed!
125 struct mimepart *np = NULL;
126 char *boundary, *line = NULL;
127 size_t linesize = 0, linelen, cnt, boundlen;
128 FILE *ibuf;
129 off_t offs;
130 int part = 0;
131 long lines = 0;
133 if ((boundary = mime_get_boundary(ip->m_ct_type, &linelen)) == NULL)
134 return;
135 boundlen = linelen;
136 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
137 return;
138 cnt = ip->m_size;
139 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
140 if (line[0] == '\n')
141 break;
142 offs = ftell(ibuf);
143 newpart(ip, &np, offs, NULL);
144 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
145 /* XXX linelen includes LF */
146 if (! ((lines > 0 || part == 0) && linelen > boundlen &&
147 memcmp(line, boundary, boundlen) == 0)) {
148 ++lines;
149 continue;
151 /* Subpart boundary? */
152 if (line[boundlen] == '\n') {
153 offs = ftell(ibuf);
154 if (part > 0) {
155 endpart(&np, offs - boundlen - 2, lines);
156 newpart(ip, &np, offs - boundlen - 2, NULL);
158 endpart(&np, offs, 2);
159 newpart(ip, &np, offs, &part);
160 lines = 0;
161 continue;
164 * Final boundary? Be aware of cases where there is no
165 * separating newline in between boundaries, as has been seen
166 * in a message with "Content-Type: multipart/appledouble;"
168 if (linelen < boundlen + 2)
169 continue;
170 linelen -= boundlen + 2;
171 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
172 (linelen > 0 && line[boundlen + 2] != '\n'))
173 continue;
174 offs = ftell(ibuf);
175 if (part != 0) {
176 endpart(&np, offs - boundlen - 4, lines);
177 newpart(ip, &np, offs - boundlen - 4, NULL);
179 endpart(&np, offs + cnt, 2);
180 break;
182 if (np) {
183 offs = ftell(ibuf);
184 endpart(&np, offs, lines);
186 for (np = ip->m_multipart; np; np = np->m_nextpart)
187 if (np->m_mimecontent != MIME_DISCARD)
188 parsepart(zmp, np, pf, level + 1);
189 free(line);
192 static void
193 _print_part_info(struct str *out, struct mimepart *mip,
194 struct ignoretab *doign, int level)
196 struct str ct = {NULL, 0}, cd = {NULL, 0};
197 char const *ps;
199 /* Max. 24 */
200 if (is_ign("content-type", 12, doign)) {
201 out->s = mip->m_ct_type_plain;
202 out->l = strlen(out->s);
203 ct.s = ac_alloc(out->l + 2 +1);
204 ct.s[0] = ',';
205 ct.s[1] = ' ';
206 ct.l = 2;
207 if (is_prefix("application/", out->s)) {
208 memcpy(ct.s + 2, "appl../", 7);
209 ct.l += 7;
210 out->l -= 12;
211 out->s += 12;
212 out->l = smin(out->l, 17);
213 } else
214 out->l = smin(out->l, 24);
215 memcpy(ct.s + ct.l, out->s, out->l);
216 ct.l += out->l;
217 ct.s[ct.l] = '\0';
220 /* Max. 27 */
221 if (is_ign("content-disposition", 19, doign) &&
222 mip->m_filename != NULL) {
223 cd.s = ac_alloc(2 + 25 + 1);
224 cd.l = snprintf(cd.s, 2 + 25 + 1, ", %.25s", mip->m_filename);
227 /* Take care of "99.99", i.e., 5 */
228 if ((ps = mip->m_partstring) == NULL || ps[0] == '\0')
229 ps = "?";
232 * Assume maximum possible sizes for 64 bit integers here to avoid any
233 * buffer overflows just in case we have a bug somewhere and / or the
234 * snprintf() is our internal version that doesn't really provide hard
235 * buffer cuts
237 #define __msg "%s[-- #%s : %lu/%lu%s%s --]\n"
238 out->l = sizeof(__msg) + strlen(ps) + 2*21 + ct.l + cd.l + 1;
239 out->s = salloc(out->l);
240 out->l = snprintf(out->s, out->l, __msg,
241 (level || (ps[0] != '1' && ps[1] == '\0')) ? "\n" : "",
242 ps, (ul_it)mip->m_lines, (ul_it)mip->m_size,
243 (ct.s != NULL ? ct.s : ""), (cd.s != NULL ? cd.s : ""));
244 out->s[out->l] = '\0';
245 #undef __msg
247 if (cd.s != NULL)
248 ac_free(cd.s);
249 if (ct.s != NULL)
250 ac_free(ct.s);
253 SINLINE void
254 _addstats(off_t *stats, off_t lines, off_t bytes)
256 if (stats != NULL) {
257 if (stats[0] >= 0)
258 stats[0] += lines;
259 stats[1] += bytes;
263 SINLINE ssize_t
264 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
265 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
267 ssize_t sz = 0, n;
268 int flags;
269 char const *cp;
271 #if 0
272 Well ... it turns out to not work like that since of course a valid
273 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
274 after an empty line has been seen, which cannot be detected that easily
275 right here!
276 ifdef HAVE_ASSERTS /* TODO assert legacy */
277 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
278 * TODO other input situations handle RFC 4155 OR, if newly generated,
279 * TODO enforce quoted-printable if there is From_, as "required" by
280 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
281 * TODO if it may happen in this path, we should just treat decryption
282 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
283 if (action == SEND_MBOX || action == SEND_DECRYPT)
284 assert(! is_head(buf, len));
285 #else
286 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
287 is_head(buf, len)) {
288 putc('>', fp);
289 ++sz;
291 #endif
293 flags = ((int)action & _TD_EOF);
294 action &= ~_TD_EOF;
295 n = mime_write(buf, len, fp,
296 action == SEND_MBOX ? CONV_NONE : convert,
297 flags |
298 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
299 action == SEND_QUOTE ||
300 action == SEND_QUOTE_ALL ?
301 TD_ISPR|TD_ICONV :
302 action == SEND_TOSRCH || action == SEND_TOPIPE ?
303 TD_ICONV :
304 action == SEND_TOFLTR ?
305 TD_DELCTRL :
306 action == SEND_SHOW ?
307 TD_ISPR : TD_NONE),
308 qf, rest);
309 if (n < 0)
310 sz = n;
311 else if (n > 0) {
312 sz += n;
313 n = 0;
314 if (stats != NULL && stats[0] != -1)
315 for (cp = buf; cp < &buf[sz]; ++cp)
316 if (*cp == '\n')
317 ++n;
318 _addstats(stats, n, sz);
320 return sz;
323 static enum pipeflags
324 _pipecmd(char **result, char const *content_type)
326 enum pipeflags ret;
327 char *s, *cp;
328 char const *cq;
330 ret = PIPE_NULL;
331 *result = NULL;
332 if (content_type == NULL)
333 goto jleave;
335 /* First check wether there is a special pipe-MIMETYPE handler */
336 s = ac_alloc(strlen(content_type) + 6);
337 memcpy(s, "pipe-", 5);
338 cp = &s[5];
339 cq = content_type;
341 *cp++ = lowerconv(*cq);
342 while (*cq++ != '\0');
343 cp = value(s);
344 ac_free(s);
346 if (cp == NULL)
347 goto jleave;
349 /* User specified a command, inspect for special cases */
350 if (cp[0] != '@') {
351 /* Normal command line */
352 ret = PIPE_COMM;
353 *result = cp;
354 } else if (*++cp == '\0') {
355 /* Treat as plain text */
356 ret = PIPE_TEXT;
357 } else if (! msglist_is_single) {
358 /* Viewing multiple messages in one go, don't block system */
359 ret = PIPE_MSG;
360 *result = UNCONST(tr(86,
361 "[Directly address message only to display this]\n"));
362 } else {
363 /* Viewing a single message only */
364 #if 0 /* TODO send/MIME layer rewrite: when we have a single-pass parser
365 * TODO then the parsing phase and the send phase will be separated;
366 * TODO that allows us to ask a user *before* we start the send, i.e.,
367 * TODO *before* a pager pipe is setup (which is the problem with
368 * TODO the '#if 0' code here) */
369 size_t l = strlen(content_type);
370 char const *x = tr(999, "Should i display a part `%s' (y/n)? ");
371 s = ac_alloc(l += strlen(x) + 1);
372 snprintf(s, l - 1, x, content_type);
373 l = yorn(s);
374 puts(""); /* .. we've hijacked a pipe 8-] ... */
375 ac_free(s);
376 if (! l) {
377 x = tr(210, "[User skipped diplay]\n");
378 ret = PIPE_MSG;
379 *result = UNCONST(x);
380 } else
381 #endif
382 if (cp[0] == '&')
383 /* Asynchronous command, normal command line */
384 ret = PIPE_ASYNC, *result = ++cp;
385 else
386 ret = PIPE_COMM, *result = cp;
388 jleave:
389 return ret;
392 static FILE *
393 _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote, bool_t async)
395 char const *sh;
396 FILE *rbuf = *qbuf;
398 if (quote) {
399 char *tempPipe;
401 if ((*qbuf = Ftemp(&tempPipe, "Rp", "w+", 0600, 1)) == NULL) {
402 perror(tr(173, "tmpfile"));
403 *qbuf = rbuf;
405 unlink(tempPipe);
406 Ftfree(&tempPipe);
407 async = FAL0;
409 if ((sh = value("SHELL")) == NULL)
410 sh = SHELL;
411 if ((rbuf = Popen(pipecomm, "W", sh,
412 async ? -1 : fileno(*qbuf))) == NULL)
413 perror(pipecomm);
414 else {
415 fflush(*qbuf);
416 if (*qbuf != stdout)
417 fflush(stdout);
419 return rbuf;
423 * Send message described by the passed pointer to the
424 * passed output buffer. Return -1 on error.
425 * Adjust the status: field if need be.
426 * If doign is given, suppress ignored header fields.
427 * prefix is a string to prepend to each output line.
428 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
429 * stats[0] is line count, stats[1] is character count. stats may be NULL.
430 * Note that stats[0] is valid for SEND_MBOX only.
433 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
434 char const *prefix, enum sendaction action, off_t *stats)
436 int rv = -1, c;
437 size_t cnt, sz, i;
438 FILE *ibuf;
439 enum parseflags pf;
440 struct mimepart *ip;
441 struct quoteflt qf;
443 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
444 did_print_dot = 1;
445 if (stats)
446 stats[0] = stats[1] = 0;
447 quoteflt_init(&qf, prefix);
450 * First line is the From_ line, so no headers there to worry about.
452 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
453 return -1;
455 cnt = mp->m_size;
456 sz = 0;
457 if (mp->m_flag & MNOFROM) {
458 if (doign != allignore && doign != fwdignore &&
459 action != SEND_RFC822)
460 sz = fprintf(obuf, "%.*sFrom %s %s\n",
461 (int)qf.qf_pfix_len,
462 (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
463 fakefrom(mp), fakedate(mp->m_time));
464 } else {
465 if (qf.qf_pfix_len > 0 && doign != allignore &&
466 doign != fwdignore && action != SEND_RFC822) {
467 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix,
468 qf.qf_pfix_len, obuf);
469 if (i != qf.qf_pfix_len)
470 goto jleave;
471 sz += i;
473 while (cnt && (c = getc(ibuf)) != EOF) {
474 if (doign != allignore && doign != fwdignore &&
475 action != SEND_RFC822) {
476 putc(c, obuf);
477 sz++;
479 cnt--;
480 if (c == '\n')
481 break;
484 if (sz)
485 _addstats(stats, 1, sz);
487 pf = 0;
488 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
489 pf |= PARSE_DECRYPT|PARSE_PARTS;
490 if ((ip = parsemsg(mp, pf)) == NULL)
491 goto jleave;
493 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
494 jleave:
495 quoteflt_destroy(&qf);
496 return rv;
499 static int
500 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
501 struct ignoretab *doign, struct quoteflt *qf,
502 enum sendaction volatile action, off_t *volatile stats, int level)
504 int volatile ispipe, rt = 0;
505 struct str rest;
506 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
507 size_t linesize = 0, linelen, cnt, len;
508 int dostat, infld = 0, ignoring = 1, isenc, c, eof;
509 struct mimepart *volatile np;
510 FILE *volatile ibuf = NULL, *volatile pbuf = obuf,
511 *volatile qbuf = obuf, *origobuf = obuf;
512 enum conversion volatile convert;
513 sighandler_type volatile oldpipe = SIG_DFL;
514 long lineno = 0;
516 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
517 action != SEND_MBOX && action != SEND_RFC822 &&
518 action != SEND_SHOW)
519 goto skip;
520 dostat = 0;
521 if (level == 0) {
522 if (doign != NULL) {
523 if (!is_ign("status", 6, doign))
524 dostat |= 1;
525 if (!is_ign("x-status", 8, doign))
526 dostat |= 2;
527 } else
528 dostat = 3;
530 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
531 return -1;
532 cnt = ip->m_size;
533 if (ip->m_mimecontent == MIME_DISCARD)
534 goto skip;
536 if ((ip->m_flag & MNOFROM) == 0)
537 while (cnt && (c = getc(ibuf)) != EOF) {
538 cnt--;
539 if (c == '\n')
540 break;
542 isenc = 0;
543 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
544 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
545 action == SEND_TOSRCH || action == SEND_TOFLTR ?
546 CONV_FROMHDR : CONV_NONE;
548 /* Work the headers */
549 quoteflt_reset(qf, obuf);
550 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
551 lineno++;
552 if (line[0] == '\n') {
554 * If line is blank, we've reached end of
555 * headers, so force out status: field
556 * and note that we are no longer in header
557 * fields
559 if (dostat & 1)
560 statusput(zmp, obuf, qf, stats);
561 if (dostat & 2)
562 xstatusput(zmp, obuf, qf, stats);
563 if (doign != allignore)
564 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
565 qf, stats, NULL);
566 break;
568 isenc &= ~1;
569 if (infld && blankchar(line[0])) {
571 * If this line is a continuation (via space or tab)
572 * of a previous header field, determine if the start
573 * of the line is a MIME encoded word.
575 if (isenc & 2) {
576 for (cp = line; blankchar(*cp); cp++);
577 if (cp > line && linelen - (cp - line) > 8 &&
578 cp[0] == '=' && cp[1] == '?')
579 isenc |= 1;
581 } else {
583 * Pick up the header field if we have one.
585 for (cp = line; (c = *cp & 0377) && c != ':' &&
586 ! spacechar(c); ++cp)
588 cp2 = cp;
589 while (spacechar(*cp))
590 ++cp;
591 if (cp[0] != ':' && level == 0 && lineno == 1) {
593 * Not a header line, force out status:
594 * This happens in uucp style mail where
595 * there are no headers at all.
597 if (dostat & 1)
598 statusput(zmp, obuf, qf, stats);
599 if (dostat & 2)
600 xstatusput(zmp, obuf, qf, stats);
601 if (doign != allignore)
602 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX,
603 qf, stats, NULL);
604 break;
607 * If it is an ignored field and
608 * we care about such things, skip it.
610 c = *cp2;
611 *cp2 = 0; /* temporarily null terminate */
612 if ((doign && is_ign(line, cp2 - line, doign)) ||
613 (action == SEND_MBOX &&
614 ! value("keep-content-length") &&
615 (asccasecmp(line, "content-length")==0
616 || asccasecmp(line, "lines") == 0)))
617 ignoring = 1;
618 else if (asccasecmp(line, "status") == 0) {
620 * If the field is "status," go compute
621 * and print the real Status: field
623 if (dostat & 1) {
624 statusput(zmp, obuf, qf, stats);
625 dostat &= ~1;
626 ignoring = 1;
628 } else if (asccasecmp(line, "x-status") == 0) {
630 * If the field is "status," go compute
631 * and print the real Status: field
633 if (dostat & 2) {
634 xstatusput(zmp, obuf, qf, stats);
635 dostat &= ~2;
636 ignoring = 1;
638 } else
639 ignoring = 0;
640 *cp2 = c;
641 infld = 1;
644 * Determine if the end of the line is a MIME encoded word.
646 isenc &= ~2;
647 if (cnt && (c = getc(ibuf)) != EOF) {
648 if (blankchar(c)) {
649 if (linelen > 0 && line[linelen - 1] == '\n')
650 cp = &line[linelen - 2];
651 else
652 cp = &line[linelen - 1];
653 while (cp >= line && whitechar(*cp))
654 ++cp;
655 if (cp - line > 8 && cp[0] == '=' &&
656 cp[-1] == '?')
657 isenc |= 2;
659 ungetc(c, ibuf);
661 if (! ignoring) {
662 start = line;
663 len = linelen;
664 if (action == SEND_TODISP ||
665 action == SEND_TODISP_ALL ||
666 action == SEND_QUOTE ||
667 action == SEND_QUOTE_ALL ||
668 action == SEND_TOSRCH ||
669 action == SEND_TOFLTR) {
671 * Strip blank characters if two MIME-encoded
672 * words follow on continuing lines.
674 if (isenc & 1)
675 while (len > 0&& blankchar(*start)) {
676 ++start;
677 --len;
679 if (isenc & 2)
680 if (len > 0 && start[len - 1] == '\n')
681 --len;
682 while (len > 0 && blankchar(start[len - 1]))
683 --len;
685 _out(start, len, obuf, convert, action, qf, stats,
686 NULL);
687 if (ferror(obuf)) {
688 free(line);
689 return -1;
693 quoteflt_flush(qf);
694 free(line);
695 line = NULL;
697 skip:
698 switch (ip->m_mimecontent) {
699 case MIME_822:
700 switch (action) {
701 case SEND_TOFLTR:
702 putc('\0', obuf);
703 /*FALLTHRU*/
704 case SEND_TODISP:
705 case SEND_TODISP_ALL:
706 case SEND_QUOTE:
707 case SEND_QUOTE_ALL:
708 if (value("rfc822-body-from_")) {
709 if (qf->qf_pfix_len > 0) {
710 size_t i = fwrite(qf->qf_pfix,
711 sizeof *qf->qf_pfix,
712 qf->qf_pfix_len, obuf);
713 if (i == qf->qf_pfix_len)
714 _addstats(stats, 0, i);
716 put_from_(obuf, ip->m_multipart, stats);
718 /*FALLTHRU*/
719 case SEND_TOSRCH:
720 case SEND_DECRYPT:
721 goto multi;
722 case SEND_TOFILE:
723 case SEND_TOPIPE:
724 if (value("rfc822-body-from_"))
725 put_from_(obuf, ip->m_multipart, stats);
726 /*FALLTHRU*/
727 case SEND_MBOX:
728 case SEND_RFC822:
729 case SEND_SHOW:
730 break;
732 break;
733 case MIME_TEXT_HTML:
734 if (action == SEND_TOFLTR)
735 putc('\b', obuf);
736 /*FALLTHRU*/
737 case MIME_TEXT:
738 case MIME_TEXT_PLAIN:
739 switch (action) {
740 case SEND_TODISP:
741 case SEND_TODISP_ALL:
742 case SEND_QUOTE:
743 case SEND_QUOTE_ALL:
744 ispipe = TRU1;
745 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
746 case PIPE_MSG:
747 _out(pipecomm, strlen(pipecomm), obuf,
748 CONV_NONE, SEND_MBOX, qf, stats, NULL);
749 pipecomm = NULL;
750 /* FALLTRHU */
751 case PIPE_TEXT:
752 case PIPE_COMM:
753 case PIPE_ASYNC:
754 case PIPE_NULL:
755 break;
757 /* FALLTRHU */
758 default:
759 break;
761 break;
762 case MIME_DISCARD:
763 if (action != SEND_DECRYPT)
764 return rt;
765 break;
766 case MIME_PKCS7:
767 if (action != SEND_MBOX && action != SEND_RFC822 &&
768 action != SEND_SHOW && ip->m_multipart)
769 goto multi;
770 /*FALLTHRU*/
771 default:
772 switch (action) {
773 case SEND_TODISP:
774 case SEND_TODISP_ALL:
775 case SEND_QUOTE:
776 case SEND_QUOTE_ALL:
777 ispipe = TRU1;
778 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
779 case PIPE_MSG:
780 _out(pipecomm, strlen(pipecomm), obuf,
781 CONV_NONE, SEND_MBOX, qf, stats, NULL);
782 pipecomm = NULL;
783 break;
784 case PIPE_ASYNC:
785 ispipe = FAL0;
786 /* FALLTHRU */
787 case PIPE_COMM:
788 case PIPE_NULL:
789 break;
790 case PIPE_TEXT:
791 goto jcopyout; /* break; break; */
793 if (pipecomm != NULL)
794 break;
795 if (level == 0 && cnt) {
796 char const *x = tr(210, "[Binary content]\n");
797 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
798 qf, stats, NULL);
800 /*FALLTHRU*/
801 case SEND_TOFLTR:
802 return rt;
803 case SEND_TOFILE:
804 case SEND_TOPIPE:
805 case SEND_TOSRCH:
806 case SEND_DECRYPT:
807 case SEND_MBOX:
808 case SEND_RFC822:
809 case SEND_SHOW:
810 break;
812 break;
813 case MIME_ALTERNATIVE:
814 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
815 value("print-alternatives") == NULL) {
816 bool_t doact = FAL0;
817 for (np = ip->m_multipart; np; np = np->m_nextpart)
818 if (np->m_mimecontent == MIME_TEXT_PLAIN)
819 doact = TRU1;
820 if (doact) {
821 for (np = ip->m_multipart; np;
822 np = np->m_nextpart) {
823 if (np->m_ct_type_plain != NULL &&
824 action != SEND_QUOTE) {
825 _print_part_info(&rest, np,
826 doign, level);
827 _out(rest.s, rest.l, obuf,
828 CONV_NONE, SEND_MBOX,
829 qf, stats, NULL);
831 if (doact && np->m_mimecontent ==
832 MIME_TEXT_PLAIN) {
833 doact = FAL0;
834 rt = sendpart(zmp, np, obuf,
835 doign, qf, action,
836 stats, level + 1);
837 quoteflt_reset(qf, origobuf);
838 if (rt < 0)
839 break;
842 return rt;
845 /*FALLTHRU*/
846 case MIME_MULTI:
847 case MIME_DIGEST:
848 switch (action) {
849 case SEND_TODISP:
850 case SEND_TODISP_ALL:
851 case SEND_QUOTE:
852 case SEND_QUOTE_ALL:
853 case SEND_TOFILE:
854 case SEND_TOPIPE:
855 case SEND_TOSRCH:
856 case SEND_TOFLTR:
857 case SEND_DECRYPT:
858 multi:
859 if ((action == SEND_TODISP ||
860 action == SEND_TODISP_ALL) &&
861 ip->m_multipart != NULL &&
862 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
863 ip->m_multipart->m_nextpart == NULL) {
864 char const *x = tr(85,
865 "[Missing multipart boundary - "
866 "use \"show\" to display "
867 "the raw message]\n");
868 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
869 qf, stats, NULL);
871 for (np = ip->m_multipart; np; np = np->m_nextpart) {
872 if (np->m_mimecontent == MIME_DISCARD &&
873 action != SEND_DECRYPT)
874 continue;
875 ispipe = FAL0;
876 switch (action) {
877 case SEND_TOFILE:
878 if (np->m_partstring &&
879 strcmp(np->m_partstring,
880 "1") == 0)
881 break;
882 stats = NULL;
883 if ((obuf = newfile(np,
884 UNVOLATILE(&ispipe)))
885 == NULL)
886 continue;
887 if (! ispipe)
888 break;
889 if (sigsetjmp(pipejmp, 1)) {
890 rt = -1;
891 goto jpipe_close;
893 oldpipe = safe_signal(SIGPIPE, onpipe);
894 break;
895 case SEND_TODISP:
896 case SEND_TODISP_ALL:
897 case SEND_QUOTE_ALL:
898 if (ip->m_mimecontent != MIME_MULTI &&
899 ip->m_mimecontent !=
900 MIME_ALTERNATIVE &&
901 ip->m_mimecontent !=
902 MIME_DIGEST)
903 break;
904 _print_part_info(&rest, np, doign,
905 level);
906 _out(rest.s, rest.l, obuf,
907 CONV_NONE, SEND_MBOX, qf,
908 stats, NULL);
909 break;
910 case SEND_TOFLTR:
911 putc('\0', obuf);
912 /*FALLTHRU*/
913 case SEND_MBOX:
914 case SEND_RFC822:
915 case SEND_SHOW:
916 case SEND_TOSRCH:
917 case SEND_QUOTE:
918 case SEND_DECRYPT:
919 case SEND_TOPIPE:
920 break;
923 quoteflt_flush(qf);
924 if (sendpart(zmp, np, obuf, doign, qf,
925 action, stats, level+1) < 0)
926 rt = -1;
927 quoteflt_reset(qf, origobuf);
928 if (action == SEND_QUOTE)
929 break;
930 if (action == SEND_TOFILE && obuf != origobuf) {
931 if (! ispipe)
932 Fclose(obuf);
933 else {
934 jpipe_close: safe_signal(SIGPIPE, SIG_IGN);
935 Pclose(obuf, TRU1);
936 safe_signal(SIGPIPE, oldpipe);
940 return rt;
941 case SEND_MBOX:
942 case SEND_RFC822:
943 case SEND_SHOW:
944 break;
949 * Copy out message body
951 jcopyout:
952 if (doign == allignore && level == 0) /* skip final blank line */
953 cnt--;
954 switch (ip->m_mimeenc) {
955 case MIME_BIN:
956 if (stats)
957 stats[0] = -1;
958 /*FALLTHRU*/
959 case MIME_7B:
960 case MIME_8B:
961 convert = CONV_NONE;
962 break;
963 case MIME_QP:
964 convert = CONV_FROMQP;
965 break;
966 case MIME_B64:
967 switch (ip->m_mimecontent) {
968 case MIME_TEXT:
969 case MIME_TEXT_PLAIN:
970 case MIME_TEXT_HTML:
971 convert = CONV_FROMB64_T;
972 break;
973 default:
974 convert = CONV_FROMB64;
976 break;
977 default:
978 convert = CONV_NONE;
980 if (action == SEND_DECRYPT || action == SEND_MBOX ||
981 action == SEND_RFC822 || action == SEND_SHOW)
982 convert = CONV_NONE;
983 #ifdef HAVE_ICONV
984 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
985 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
986 action == SEND_TOSRCH) &&
987 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
988 ip->m_mimecontent == MIME_TEXT_HTML ||
989 ip->m_mimecontent == MIME_TEXT)) {
990 char const *tcs = charset_get_lc();
992 if (iconvd != (iconv_t)-1)
993 n_iconv_close(iconvd);
994 /* TODO Since Base64 has an odd 4:3 relation in between input
995 * TODO and output an input line may end with a partial
996 * TODO multibyte character; this is no problem at all unless
997 * TODO we send to the display or whatever, i.e., ensure
998 * TODO makeprint() or something; to avoid this trap, *force*
999 * TODO iconv(), in which case this layer will handle leftovers
1000 * TODO correctly */
1001 if (convert == CONV_FROMB64_T ||
1002 (asccasecmp(tcs, ip->m_charset) &&
1003 asccasecmp(charset_get_7bit(),
1004 ip->m_charset))) {
1005 iconvd = n_iconv_open(tcs, ip->m_charset);
1006 /* XXX Don't bail out if we cannot iconv(3) here;
1007 * XXX alternatively we could avoid trying to open
1008 * XXX if ip->m_charset is "unknown-8bit", which was
1009 * XXX the one that has bitten me?? */
1011 * TODO errors should DEFINETELY not be scrolled away!
1012 * TODO what about an error buffer (think old shsp(1)),
1013 * TODO re-dump errors since last snapshot when the
1014 * TODO command loop enters again? i.e., at least print
1015 * TODO "There were errors ?" before the next prompt,
1016 * TODO so that the user can look at the error buffer?
1018 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1019 fprintf(stderr, tr(179,
1020 "Cannot convert from %s to %s\n"),
1021 ip->m_charset, tcs);
1022 /*return -1;*/
1026 #endif
1027 if (pipecomm != NULL &&
1028 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
1029 action == SEND_QUOTE || action == SEND_QUOTE_ALL)) {
1030 qbuf = obuf;
1031 pbuf = _pipefile(pipecomm, UNVOLATILE(&qbuf),
1032 action == SEND_QUOTE || action == SEND_QUOTE_ALL,
1033 ! ispipe);
1034 action = SEND_TOPIPE;
1035 if (pbuf != qbuf) {
1036 oldpipe = safe_signal(SIGPIPE, onpipe);
1037 if (sigsetjmp(pipejmp, 1))
1038 goto end;
1040 } else
1041 pbuf = qbuf = obuf;
1043 {/* XXX C99 */
1044 size_t save_qf_pfix_len = qf->qf_pfix_len;
1045 off_t *save_stats = stats;
1047 if (pbuf != origobuf) {
1048 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1049 stats = NULL;
1051 eof = 0;
1052 rest.s = NULL;
1053 rest.l = 0;
1055 quoteflt_reset(qf, pbuf);
1056 while (! eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1057 ++lineno;
1058 joutln:
1059 len = (size_t)_out(line, linelen, pbuf, convert, action,
1060 qf, stats, &rest);
1061 if ((ssize_t)len < 0 || ferror(pbuf)) {
1062 rt = -1;
1063 break;
1066 if (! eof && rest.l != 0) {
1067 linelen = 0;
1068 eof = 1;
1069 action |= _TD_EOF;
1070 goto joutln;
1072 quoteflt_flush(qf);
1073 if (rest.s != NULL)
1074 free(rest.s);
1076 if (pbuf != origobuf) {
1077 qf->qf_pfix_len = save_qf_pfix_len;
1078 stats = save_stats;
1082 end: free(line);
1083 if (pbuf != qbuf) {
1084 safe_signal(SIGPIPE, SIG_IGN);
1085 Pclose(pbuf, ispipe);
1086 safe_signal(SIGPIPE, oldpipe);
1087 if (qbuf != obuf)
1088 pipecpy(qbuf, obuf, origobuf, qf, stats);
1090 #ifdef HAVE_ICONV
1091 if (iconvd != (iconv_t)-1)
1092 n_iconv_close(iconvd);
1093 #endif
1094 return rt;
1097 static struct mimepart *
1098 parsemsg(struct message *mp, enum parseflags pf)
1100 struct mimepart *ip;
1102 ip = csalloc(1, sizeof *ip);
1103 ip->m_flag = mp->m_flag;
1104 ip->m_have = mp->m_have;
1105 ip->m_block = mp->m_block;
1106 ip->m_offset = mp->m_offset;
1107 ip->m_size = mp->m_size;
1108 ip->m_xsize = mp->m_xsize;
1109 ip->m_lines = mp->m_lines;
1110 ip->m_xlines = mp->m_lines;
1111 if (parsepart(mp, ip, pf, 0) != OKAY)
1112 return NULL;
1113 return ip;
1116 static enum okay
1117 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1118 int level)
1120 char *cp;
1122 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
1123 if (ip->m_ct_type != NULL) {
1124 ip->m_ct_type_plain = savestr(ip->m_ct_type);
1125 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
1126 *cp = '\0';
1127 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
1128 ip->m_ct_type_plain = UNCONST("message/rfc822");
1129 else
1130 ip->m_ct_type_plain = UNCONST("text/plain");
1132 if (ip->m_ct_type)
1133 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
1134 if (ip->m_charset == NULL)
1135 ip->m_charset = charset_get_7bit();
1136 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
1137 (struct message *)ip);
1138 ip->m_mimeenc = ip->m_ct_transfer_enc ?
1139 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
1140 if ((cp = hfield1("content-disposition", (struct message *)ip)) == 0 ||
1141 (ip->m_filename = mime_getparam("filename", cp)) == 0)
1142 if (ip->m_ct_type != NULL)
1143 ip->m_filename = mime_getparam("name", ip->m_ct_type);
1144 ip->m_mimecontent = mime_classify_content_of_part(ip);
1146 if (pf & PARSE_PARTS) {
1147 if (level > 9999) {
1148 fprintf(stderr, tr(36,
1149 "MIME content too deeply nested\n"));
1150 return STOP;
1152 switch (ip->m_mimecontent) {
1153 case MIME_PKCS7:
1154 if (pf & PARSE_DECRYPT) {
1155 #ifdef HAVE_SSL
1156 parsepkcs7(zmp, ip, pf, level);
1157 break;
1158 #else
1159 fprintf(stderr, tr(225,
1160 "No SSL support compiled in.\n"));
1161 return STOP;
1162 #endif
1164 /*FALLTHRU*/
1165 default:
1166 break;
1167 case MIME_MULTI:
1168 case MIME_ALTERNATIVE:
1169 case MIME_DIGEST:
1170 _parsemultipart(zmp, ip, pf, level);
1171 break;
1172 case MIME_822:
1173 parse822(zmp, ip, pf, level);
1174 break;
1177 return OKAY;
1180 static void
1181 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
1183 struct mimepart *pp;
1184 size_t sz;
1186 *np = csalloc(1, sizeof **np);
1187 (*np)->m_flag = MNOFROM;
1188 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
1189 (*np)->m_block = mailx_blockof(offs);
1190 (*np)->m_offset = mailx_offsetof(offs);
1191 if (part) {
1192 (*part)++;
1193 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
1194 sz += 20;
1195 (*np)->m_partstring = salloc(sz);
1196 if (ip->m_partstring)
1197 snprintf((*np)->m_partstring, sz, "%s.%u",
1198 ip->m_partstring, *part);
1199 else
1200 snprintf((*np)->m_partstring, sz, "%u", *part);
1201 } else
1202 (*np)->m_mimecontent = MIME_DISCARD;
1203 (*np)->m_parent = ip;
1204 if (ip->m_multipart) {
1205 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
1206 pp->m_nextpart = *np;
1207 } else
1208 ip->m_multipart = *np;
1211 static void
1212 endpart(struct mimepart **np, off_t xoffs, long lines)
1214 off_t offs;
1216 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
1217 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
1218 (*np)->m_lines = (*np)->m_xlines = lines;
1219 *np = NULL;
1222 static void
1223 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1224 int level)
1226 int c, lastc = '\n';
1227 size_t cnt;
1228 FILE *ibuf;
1229 off_t offs;
1230 struct mimepart *np;
1231 long lines;
1233 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
1234 return;
1235 cnt = ip->m_size;
1236 lines = ip->m_lines;
1237 while (cnt && ((c = getc(ibuf)) != EOF)) {
1238 cnt--;
1239 if (c == '\n') {
1240 lines--;
1241 if (lastc == '\n')
1242 break;
1244 lastc = c;
1246 offs = ftell(ibuf);
1247 np = csalloc(1, sizeof *np);
1248 np->m_flag = MNOFROM;
1249 np->m_have = HAVE_HEADER|HAVE_BODY;
1250 np->m_block = mailx_blockof(offs);
1251 np->m_offset = mailx_offsetof(offs);
1252 np->m_size = np->m_xsize = cnt;
1253 np->m_lines = np->m_xlines = lines;
1254 np->m_partstring = ip->m_partstring;
1255 np->m_parent = ip;
1256 ip->m_multipart = np;
1257 if (value("rfc822-body-from_")) {
1258 substdate((struct message *)np);
1259 np->m_from = fakefrom((struct message *)np);
1261 parsepart(zmp, np, pf, level+1);
1264 #ifdef HAVE_SSL
1265 static void
1266 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1267 int level)
1269 struct message m, *xmp;
1270 struct mimepart *np;
1271 char *to, *cc;
1273 memcpy(&m, ip, sizeof m);
1274 to = hfield1("to", zmp);
1275 cc = hfield1("cc", zmp);
1276 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
1277 np = csalloc(1, sizeof *np);
1278 np->m_flag = xmp->m_flag;
1279 np->m_have = xmp->m_have;
1280 np->m_block = xmp->m_block;
1281 np->m_offset = xmp->m_offset;
1282 np->m_size = xmp->m_size;
1283 np->m_xsize = xmp->m_xsize;
1284 np->m_lines = xmp->m_lines;
1285 np->m_xlines = xmp->m_xlines;
1286 np->m_partstring = ip->m_partstring;
1287 if (parsepart(zmp, np, pf, level+1) == OKAY) {
1288 np->m_parent = ip;
1289 ip->m_multipart = np;
1293 #endif
1296 * Get a file for an attachment.
1298 static FILE *
1299 newfile(struct mimepart *ip, int *ispipe)
1301 char *f = ip->m_filename;
1302 struct str in, out;
1303 FILE *fp;
1305 *ispipe = 0;
1306 if (f != NULL && f != (char *)-1) {
1307 in.s = f;
1308 in.l = strlen(f);
1309 mime_fromhdr(&in, &out, TD_ISPR);
1310 memcpy(f, out.s, out.l);
1311 *(f + out.l) = '\0';
1312 free(out.s);
1315 if (options & OPT_INTERACTIVE) {
1316 char *f2, *f3;
1317 jgetname: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1318 ip->m_partstring ? ip->m_partstring : "?",
1319 ip->m_ct_type_plain);
1320 f2 = readstr_input(": ", f != (char *)-1 ? f : NULL);
1321 if (f2 == NULL || *f2 == '\0') {
1322 fprintf(stderr, tr(279, "... skipping this\n"));
1323 return (NULL);
1324 } else if (*f2 == '|')
1325 /* Pipes are expanded by the shell */
1326 f = f2;
1327 else if ((f3 = file_expand(f2)) == NULL)
1328 /* (Error message written by file_expand()) */
1329 goto jgetname;
1330 else
1331 f = f3;
1333 if (f == NULL || f == (char *)-1)
1334 return NULL;
1336 if (*f == '|') {
1337 char const *cp;
1338 cp = value("SHELL");
1339 if (cp == NULL)
1340 cp = SHELL;
1341 fp = Popen(f + 1, "w", cp, 1);
1342 if (! (*ispipe = (fp != NULL)))
1343 perror(f);
1344 } else {
1345 if ((fp = Fopen(f, "w")) == NULL)
1346 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1348 return (fp);
1351 static void
1352 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1353 off_t *stats)
1355 char *line = NULL;
1356 size_t linesize = 0, linelen, cnt;
1357 ssize_t all_sz, sz;
1359 fflush(pipebuf);
1360 rewind(pipebuf);
1361 cnt = fsize(pipebuf);
1362 all_sz = 0;
1364 quoteflt_reset(qf, outbuf);
1365 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0)
1366 != NULL) {
1367 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1368 break;
1369 all_sz += sz;
1371 if ((sz = quoteflt_flush(qf)) > 0)
1372 all_sz += sz;
1373 if (line)
1374 free(line);
1376 if (all_sz > 0 && outbuf == origobuf)
1377 _addstats(stats, 1, all_sz);
1378 fclose(pipebuf);
1382 * Output a reasonable looking status field.
1384 static void
1385 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1386 off_t *stats)
1388 char statout[3];
1389 char *cp = statout;
1391 if (mp->m_flag & MREAD)
1392 *cp++ = 'R';
1393 if ((mp->m_flag & MNEW) == 0)
1394 *cp++ = 'O';
1395 *cp = 0;
1396 if (statout[0]) {
1397 int i = fprintf(obuf, "%.*sStatus: %s\n",
1398 (int)qf->qf_pfix_len,
1399 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1400 statout);
1401 if (i > 0)
1402 _addstats(stats, 1, i);
1406 static void
1407 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1408 off_t *stats)
1410 char xstatout[4];
1411 char *xp = xstatout;
1413 if (mp->m_flag & MFLAGGED)
1414 *xp++ = 'F';
1415 if (mp->m_flag & MANSWERED)
1416 *xp++ = 'A';
1417 if (mp->m_flag & MDRAFTED)
1418 *xp++ = 'T';
1419 *xp = 0;
1420 if (xstatout[0]) {
1421 int i = fprintf(obuf, "%.*sX-Status: %s\n",
1422 (int)qf->qf_pfix_len,
1423 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1424 xstatout);
1425 if (i > 0)
1426 _addstats(stats, 1, i);
1430 static void
1431 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1433 char const *froma, *date, *nl;
1434 int i;
1436 if (ip && ip->m_from) {
1437 froma = ip->m_from;
1438 date = fakedate(ip->m_time);
1439 nl = "\n";
1440 } else {
1441 froma = myname;
1442 date = time_current.tc_ctime;
1443 nl = "";
1446 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1447 if (i > 0)
1448 _addstats(stats, (*nl != '\0'), i);