Remove debug-only `core' and `clobber' commands..
[s-mailx.git] / send.c
blobccb039b3bb2b349eda62ddfb3a86c6b97b4a25bc
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 - 2014 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 !strncmp(line, boundary, boundlen))) {
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;
200 struct str const *cpre, *csuf;
202 /* Max. 24 */
203 if (is_ign("content-type", 12, doign)) {
204 out->s = mip->m_ct_type_plain;
205 out->l = strlen(out->s);
206 ct.s = ac_alloc(out->l + 2 +1);
207 ct.s[0] = ',';
208 ct.s[1] = ' ';
209 ct.l = 2;
210 if (is_prefix("application/", out->s)) {
211 memcpy(ct.s + 2, "appl../", 7);
212 ct.l += 7;
213 out->l -= 12;
214 out->s += 12;
215 out->l = MIN(out->l, 17);
216 } else
217 out->l = MIN(out->l, 24);
218 memcpy(ct.s + ct.l, out->s, out->l);
219 ct.l += out->l;
220 ct.s[ct.l] = '\0';
223 /* Max. 27 */
224 if (is_ign("content-disposition", 19, doign) &&
225 mip->m_filename != NULL) {
226 struct str ti, to;
228 ti.l = strlen(ti.s = mip->m_filename);
229 mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV | TD_DELCTRL);
230 to.l = MIN(to.l, 25);
231 cd.s = ac_alloc(to.l + 2 +1);
232 cd.s[0] = ',';
233 cd.s[1] = ' ';
234 memcpy(cd.s + 2, to.s, to.l);
235 to.l += 2;
236 cd.s[to.l] = '\0';
237 free(to.s);
240 /* Take care of "99.99", i.e., 5 */
241 if ((ps = mip->m_partstring) == NULL || ps[0] == '\0')
242 ps = "?";
244 #ifdef HAVE_COLOUR
245 cpre = colour_get(COLOURSPEC_PARTINFO);
246 csuf = colour_get(COLOURSPEC_RESET);
247 #else
248 cpre = csuf = NULL;
249 #endif
251 /* Assume maximum possible sizes for 64 bit integers here to avoid any
252 * buffer overflows just in case we have a bug somewhere and / or the
253 * snprintf() is our internal version that doesn't really provide hard
254 * buffer cuts */
255 #define __msg "%s%s[-- #%s : %lu/%lu%s%s --]%s\n"
256 out->l = sizeof(__msg) +
257 #ifdef HAVE_COLOUR
258 (cpre != NULL ? cpre->l + csuf->l : 0) +
259 #endif
260 strlen(ps) + 2*21 + ct.l + cd.l + 1;
261 out->s = salloc(out->l);
262 out->l = snprintf(out->s, out->l, __msg,
263 (level || (ps[0] != '1' && ps[1] == '\0') ? "\n" : ""),
264 (cpre != NULL ? cpre->s : ""),
265 ps, (ul_it)mip->m_lines, (ul_it)mip->m_size,
266 (ct.s != NULL ? ct.s : ""),
267 (cd.s != NULL ? cd.s : ""),
268 (csuf != NULL ? csuf->s : ""));
269 out->s[out->l] = '\0';
270 #undef __msg
272 if (cd.s != NULL)
273 ac_free(cd.s);
274 if (ct.s != NULL)
275 ac_free(ct.s);
278 SINLINE void
279 _addstats(off_t *stats, off_t lines, off_t bytes)
281 if (stats != NULL) {
282 if (stats[0] >= 0)
283 stats[0] += lines;
284 stats[1] += bytes;
288 SINLINE ssize_t
289 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
290 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
292 ssize_t sz = 0, n;
293 int flags;
294 char const *cp;
296 #if 0
297 Well ... it turns out to not work like that since of course a valid
298 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
299 after an empty line has been seen, which cannot be detected that easily
300 right here!
301 ifdef HAVE_DEBUG /* TODO assert legacy */
302 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
303 * TODO other input situations handle RFC 4155 OR, if newly generated,
304 * TODO enforce quoted-printable if there is From_, as "required" by
305 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
306 * TODO if it may happen in this path, we should just treat decryption
307 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
308 if (action == SEND_MBOX || action == SEND_DECRYPT)
309 assert(! is_head(buf, len));
310 #else
311 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
312 is_head(buf, len)) {
313 putc('>', fp);
314 ++sz;
316 #endif
318 flags = ((int)action & _TD_EOF);
319 action &= ~_TD_EOF;
320 n = mime_write(buf, len, fp,
321 action == SEND_MBOX ? CONV_NONE : convert,
322 flags |
323 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
324 action == SEND_QUOTE ||
325 action == SEND_QUOTE_ALL ?
326 TD_ISPR|TD_ICONV :
327 action == SEND_TOSRCH || action == SEND_TOPIPE ?
328 TD_ICONV :
329 action == SEND_TOFLTR ?
330 TD_DELCTRL :
331 action == SEND_SHOW ?
332 TD_ISPR : TD_NONE),
333 qf, rest);
334 if (n < 0)
335 sz = n;
336 else if (n > 0) {
337 sz = (ssize_t)((size_t)sz + n);
338 n = 0;
339 if (stats != NULL && stats[0] != -1)
340 for (cp = buf; cp < &buf[sz]; ++cp)
341 if (*cp == '\n')
342 ++n;
343 _addstats(stats, n, sz);
345 return sz;
348 static enum pipeflags
349 _pipecmd(char **result, char const *content_type)
351 enum pipeflags ret;
352 char *s, *cp;
353 char const *cq;
355 ret = PIPE_NULL;
356 *result = NULL;
357 if (content_type == NULL)
358 goto jleave;
360 /* First check wether there is a special pipe-MIMETYPE handler */
361 s = ac_alloc(strlen(content_type) + 6);
362 memcpy(s, "pipe-", 5);
363 cp = &s[5];
364 cq = content_type;
366 *cp++ = lowerconv(*cq);
367 while (*cq++ != '\0');
368 cp = vok_vlook(s);
369 ac_free(s);
371 if (cp == NULL)
372 goto jleave;
374 /* User specified a command, inspect for special cases */
375 if (cp[0] != '@') {
376 /* Normal command line */
377 ret = PIPE_COMM;
378 *result = cp;
379 } else if (*++cp == '\0') {
380 /* Treat as plain text */
381 ret = PIPE_TEXT;
382 } else if (! msglist_is_single) {
383 /* Viewing multiple messages in one go, don't block system */
384 ret = PIPE_MSG;
385 *result = UNCONST(tr(86,
386 "[Directly address message only to display this]\n"));
387 } else {
388 /* Viewing a single message only */
389 #if 0 /* TODO send/MIME layer rewrite: when we have a single-pass parser
390 * TODO then the parsing phase and the send phase will be separated;
391 * TODO that allows us to ask a user *before* we start the send, i.e.,
392 * TODO *before* a pager pipe is setup (which is the problem with
393 * TODO the '#if 0' code here) */
394 size_t l = strlen(content_type);
395 char const *x = tr(999, "Should i display a part `%s' (y/n)? ");
396 s = ac_alloc(l += strlen(x) + 1);
397 snprintf(s, l - 1, x, content_type);
398 l = yorn(s);
399 puts(""); /* .. we've hijacked a pipe 8-] ... */
400 ac_free(s);
401 if (! l) {
402 x = tr(210, "[User skipped diplay]\n");
403 ret = PIPE_MSG;
404 *result = UNCONST(x);
405 } else
406 #endif
407 if (cp[0] == '&')
408 /* Asynchronous command, normal command line */
409 ret = PIPE_ASYNC, *result = ++cp;
410 else
411 ret = PIPE_COMM, *result = cp;
413 jleave:
414 return ret;
417 static FILE *
418 _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote, bool_t async)
420 char const *sh;
421 FILE *rbuf = *qbuf;
423 if (quote) {
424 char *tempPipe;
426 if ((*qbuf = Ftemp(&tempPipe, "Rp", "w+", 0600, 1)) == NULL) {
427 perror(tr(173, "tmpfile"));
428 *qbuf = rbuf;
430 unlink(tempPipe);
431 Ftfree(&tempPipe);
432 async = FAL0;
434 if ((sh = ok_vlook(SHELL)) == NULL)
435 sh = XSHELL;
436 if ((rbuf = Popen(pipecomm, "W", sh,
437 async ? -1 : fileno(*qbuf))) == NULL)
438 perror(pipecomm);
439 else {
440 fflush(*qbuf);
441 if (*qbuf != stdout)
442 fflush(stdout);
444 return rbuf;
448 * Send message described by the passed pointer to the
449 * passed output buffer. Return -1 on error.
450 * Adjust the status: field if need be.
451 * If doign is given, suppress ignored header fields.
452 * prefix is a string to prepend to each output line.
453 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
454 * stats[0] is line count, stats[1] is character count. stats may be NULL.
455 * Note that stats[0] is valid for SEND_MBOX only.
457 FL int
458 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
459 char const *prefix, enum sendaction action, off_t *stats)
461 int rv = -1, c;
462 size_t cnt, sz, i;
463 FILE *ibuf;
464 enum parseflags pf;
465 struct mimepart *ip;
466 struct quoteflt qf;
468 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
469 did_print_dot = 1;
470 if (stats)
471 stats[0] = stats[1] = 0;
472 quoteflt_init(&qf, prefix);
475 * First line is the From_ line, so no headers there to worry about.
477 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
478 return -1;
480 cnt = mp->m_size;
481 sz = 0;
482 colour_put(obuf, COLOURSPEC_FROM_);
483 if (mp->m_flag & MNOFROM) {
484 if (doign != allignore && doign != fwdignore &&
485 action != SEND_RFC822)
486 sz = fprintf(obuf, "%.*sFrom %s %s\n",
487 (int)qf.qf_pfix_len,
488 (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
489 fakefrom(mp), fakedate(mp->m_time));
490 } else {
491 if (qf.qf_pfix_len > 0 && doign != allignore &&
492 doign != fwdignore && action != SEND_RFC822) {
493 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix,
494 qf.qf_pfix_len, obuf);
495 if (i != qf.qf_pfix_len) {
496 colour_reset(obuf);
497 goto jleave;
499 sz += i;
501 while (cnt && (c = getc(ibuf)) != EOF) {
502 if (doign != allignore && doign != fwdignore &&
503 action != SEND_RFC822) {
504 putc(c, obuf);
505 sz++;
507 cnt--;
508 if (c == '\n')
509 break;
512 colour_reset(obuf);
513 if (sz)
514 _addstats(stats, 1, sz);
516 pf = 0;
517 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
518 pf |= PARSE_DECRYPT|PARSE_PARTS;
519 if ((ip = parsemsg(mp, pf)) == NULL)
520 goto jleave;
522 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
523 jleave:
524 quoteflt_destroy(&qf);
525 return rv;
528 static int
529 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
530 struct ignoretab *doign, struct quoteflt *qf,
531 enum sendaction volatile action, off_t *volatile stats, int level)
533 int volatile ispipe, rt = 0;
534 struct str rest;
535 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
536 size_t linesize = 0, linelen, cnt;
537 int dostat, infld = 0, ignoring = 1, isenc, c;
538 struct mimepart *volatile np;
539 FILE *volatile ibuf = NULL, *volatile pbuf = obuf,
540 *volatile qbuf = obuf, *origobuf = obuf;
541 enum conversion volatile convert;
542 sighandler_type volatile oldpipe = SIG_DFL;
543 long lineno = 0;
545 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
546 action != SEND_MBOX && action != SEND_RFC822 &&
547 action != SEND_SHOW)
548 goto skip;
549 dostat = 0;
550 if (level == 0) {
551 if (doign != NULL) {
552 if (!is_ign("status", 6, doign))
553 dostat |= 1;
554 if (!is_ign("x-status", 8, doign))
555 dostat |= 2;
556 } else
557 dostat = 3;
559 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
560 return -1;
561 cnt = ip->m_size;
562 if (ip->m_mimecontent == MIME_DISCARD)
563 goto skip;
565 if ((ip->m_flag & MNOFROM) == 0)
566 while (cnt && (c = getc(ibuf)) != EOF) {
567 cnt--;
568 if (c == '\n')
569 break;
571 isenc = 0;
572 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
573 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
574 action == SEND_TOSRCH || action == SEND_TOFLTR ?
575 CONV_FROMHDR : CONV_NONE;
577 /* Work the headers */
578 quoteflt_reset(qf, obuf);
579 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
580 lineno++;
581 if (line[0] == '\n') {
583 * If line is blank, we've reached end of
584 * headers, so force out status: field
585 * and note that we are no longer in header
586 * fields
588 if (dostat & 1)
589 statusput(zmp, obuf, qf, stats);
590 if (dostat & 2)
591 xstatusput(zmp, obuf, qf, stats);
592 if (doign != allignore)
593 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
594 qf, stats, NULL);
595 break;
597 isenc &= ~1;
598 if (infld && blankchar(line[0])) {
600 * If this line is a continuation (via space or tab)
601 * of a previous header field, determine if the start
602 * of the line is a MIME encoded word.
604 if (isenc & 2) {
605 for (cp = line; blankchar(*cp); cp++);
606 if (cp > line && linelen - (cp - line) > 8 &&
607 cp[0] == '=' && cp[1] == '?')
608 isenc |= 1;
610 } else {
611 #ifdef HAVE_COLOUR /* XXX colour handling is yet hacky, should be filter! */
612 if (pipecomm != NULL) {
613 pipecomm = NULL;
614 colour_reset(obuf); /* XXX reset after \n!! */
616 #endif
618 * Pick up the header field if we have one.
620 for (cp = line; (c = *cp & 0377) && c != ':' &&
621 !spacechar(c); ++cp)
623 cp2 = cp;
624 while (spacechar(*cp))
625 ++cp;
626 if (cp[0] != ':' && level == 0 && lineno == 1) {
628 * Not a header line, force out status:
629 * This happens in uucp style mail where
630 * there are no headers at all.
632 if (dostat & 1)
633 statusput(zmp, obuf, qf, stats);
634 if (dostat & 2)
635 xstatusput(zmp, obuf, qf, stats);
636 if (doign != allignore)
637 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX,
638 qf, stats, NULL);
639 break;
642 * If it is an ignored field and
643 * we care about such things, skip it.
645 c = *cp2;
646 *cp2 = 0; /* temporarily null terminate */
647 if ((doign &&
648 is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
649 (action == SEND_MBOX &&
650 !ok_blook(keep_content_length) &&
651 (asccasecmp(line, "content-length")==0
652 || asccasecmp(line, "lines") == 0)))
653 ignoring = 1;
654 else if (asccasecmp(line, "status") == 0) {
656 * If the field is "status," go compute
657 * and print the real Status: field
659 if (dostat & 1) {
660 statusput(zmp, obuf, qf, stats);
661 dostat &= ~1;
662 ignoring = 1;
664 } else if (asccasecmp(line, "x-status") == 0) {
666 * If the field is "status," go compute
667 * and print the real Status: field
669 if (dostat & 2) {
670 xstatusput(zmp, obuf, qf, stats);
671 dostat &= ~2;
672 ignoring = 1;
674 } else {
675 ignoring = 0;
676 #ifdef HAVE_COLOUR
677 pipecomm = savestrbuf(line,
678 PTR2SIZE(cp2 - line));
679 #endif
681 *cp2 = c;
682 infld = 1;
685 * Determine if the end of the line is a MIME encoded word.
687 isenc &= ~2;
688 if (cnt && (c = getc(ibuf)) != EOF) {
689 if (blankchar(c)) {
690 if (linelen > 0 && line[linelen - 1] == '\n')
691 cp = &line[linelen - 2];
692 else
693 cp = &line[linelen - 1];
694 while (cp >= line && whitechar(*cp))
695 ++cp;
696 if (cp - line > 8 && cp[0] == '=' &&
697 cp[-1] == '?')
698 isenc |= 2;
700 ungetc(c, ibuf);
702 if (!ignoring) {
703 size_t len = linelen;
704 start = line;
705 if (action == SEND_TODISP ||
706 action == SEND_TODISP_ALL ||
707 action == SEND_QUOTE ||
708 action == SEND_QUOTE_ALL ||
709 action == SEND_TOSRCH ||
710 action == SEND_TOFLTR) {
712 * Strip blank characters if two MIME-encoded
713 * words follow on continuing lines.
715 if (isenc & 1)
716 while (len > 0 && blankchar(*start)) {
717 ++start;
718 --len;
720 if (isenc & 2)
721 if (len > 0 && start[len - 1] == '\n')
722 --len;
723 while (len > 0 && blankchar(start[len - 1]))
724 --len;
726 #ifdef HAVE_COLOUR
727 if (pipecomm != NULL)
728 colour_put_header(obuf, pipecomm);
729 #endif
730 _out(start, len, obuf, convert, action, qf, stats,
731 NULL);
732 if (ferror(obuf)) {
733 free(line);
734 return -1;
738 #ifdef HAVE_COLOUR
739 pipecomm = NULL;
740 if (infld)
741 colour_reset(obuf); /* XXX reset after \n!! */
742 #endif
743 quoteflt_flush(qf);
744 free(line);
745 line = NULL;
747 skip:
748 switch (ip->m_mimecontent) {
749 case MIME_822:
750 switch (action) {
751 case SEND_TOFLTR:
752 putc('\0', obuf);
753 /*FALLTHRU*/
754 case SEND_TODISP:
755 case SEND_TODISP_ALL:
756 case SEND_QUOTE:
757 case SEND_QUOTE_ALL:
758 if (ok_blook(rfc822_body_from_)) {
759 if (qf->qf_pfix_len > 0) {
760 size_t i = fwrite(qf->qf_pfix,
761 sizeof *qf->qf_pfix,
762 qf->qf_pfix_len, obuf);
763 if (i == qf->qf_pfix_len)
764 _addstats(stats, 0, i);
766 put_from_(obuf, ip->m_multipart, stats);
768 /*FALLTHRU*/
769 case SEND_TOSRCH:
770 case SEND_DECRYPT:
771 goto multi;
772 case SEND_TOFILE:
773 case SEND_TOPIPE:
774 if (ok_blook(rfc822_body_from_))
775 put_from_(obuf, ip->m_multipart, stats);
776 /*FALLTHRU*/
777 case SEND_MBOX:
778 case SEND_RFC822:
779 case SEND_SHOW:
780 break;
782 break;
783 case MIME_TEXT_HTML:
784 if (action == SEND_TOFLTR)
785 putc('\b', obuf);
786 /*FALLTHRU*/
787 case MIME_TEXT:
788 case MIME_TEXT_PLAIN:
789 switch (action) {
790 case SEND_TODISP:
791 case SEND_TODISP_ALL:
792 case SEND_QUOTE:
793 case SEND_QUOTE_ALL:
794 ispipe = TRU1;
795 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
796 case PIPE_MSG:
797 _out(pipecomm, strlen(pipecomm), obuf,
798 CONV_NONE, SEND_MBOX, qf, stats, NULL);
799 pipecomm = NULL;
800 /* FALLTRHU */
801 case PIPE_TEXT:
802 case PIPE_COMM:
803 case PIPE_ASYNC:
804 case PIPE_NULL:
805 break;
807 /* FALLTRHU */
808 default:
809 break;
811 break;
812 case MIME_DISCARD:
813 if (action != SEND_DECRYPT)
814 return rt;
815 break;
816 case MIME_PKCS7:
817 if (action != SEND_MBOX && action != SEND_RFC822 &&
818 action != SEND_SHOW && ip->m_multipart)
819 goto multi;
820 /*FALLTHRU*/
821 default:
822 switch (action) {
823 case SEND_TODISP:
824 case SEND_TODISP_ALL:
825 case SEND_QUOTE:
826 case SEND_QUOTE_ALL:
827 ispipe = TRU1;
828 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
829 case PIPE_MSG:
830 _out(pipecomm, strlen(pipecomm), obuf,
831 CONV_NONE, SEND_MBOX, qf, stats, NULL);
832 pipecomm = NULL;
833 break;
834 case PIPE_ASYNC:
835 ispipe = FAL0;
836 /* FALLTHRU */
837 case PIPE_COMM:
838 case PIPE_NULL:
839 break;
840 case PIPE_TEXT:
841 goto jcopyout; /* break; break; */
843 if (pipecomm != NULL)
844 break;
845 if (level == 0 && cnt) {
846 char const *x = tr(210, "[Binary content]\n");
847 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
848 qf, stats, NULL);
850 /*FALLTHRU*/
851 case SEND_TOFLTR:
852 return rt;
853 case SEND_TOFILE:
854 case SEND_TOPIPE:
855 case SEND_TOSRCH:
856 case SEND_DECRYPT:
857 case SEND_MBOX:
858 case SEND_RFC822:
859 case SEND_SHOW:
860 break;
862 break;
863 case MIME_ALTERNATIVE:
864 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
865 !ok_blook(print_alternatives)) {
866 bool_t doact = FAL0;
867 for (np = ip->m_multipart; np; np = np->m_nextpart)
868 if (np->m_mimecontent == MIME_TEXT_PLAIN)
869 doact = TRU1;
870 if (doact) {
871 for (np = ip->m_multipart; np;
872 np = np->m_nextpart) {
873 if (np->m_ct_type_plain != NULL &&
874 action != SEND_QUOTE) {
875 _print_part_info(&rest, np,
876 doign, level);
877 _out(rest.s, rest.l, obuf,
878 CONV_NONE, SEND_MBOX,
879 qf, stats, NULL);
881 if (doact && np->m_mimecontent ==
882 MIME_TEXT_PLAIN) {
883 doact = FAL0;
884 rt = sendpart(zmp, np, obuf,
885 doign, qf, action,
886 stats, level + 1);
887 quoteflt_reset(qf, origobuf);
888 if (rt < 0)
889 break;
892 return rt;
895 /*FALLTHRU*/
896 case MIME_MULTI:
897 case MIME_DIGEST:
898 switch (action) {
899 case SEND_TODISP:
900 case SEND_TODISP_ALL:
901 case SEND_QUOTE:
902 case SEND_QUOTE_ALL:
903 case SEND_TOFILE:
904 case SEND_TOPIPE:
905 case SEND_TOSRCH:
906 case SEND_TOFLTR:
907 case SEND_DECRYPT:
908 multi:
909 if ((action == SEND_TODISP ||
910 action == SEND_TODISP_ALL) &&
911 ip->m_multipart != NULL &&
912 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
913 ip->m_multipart->m_nextpart == NULL) {
914 char const *x = tr(85,
915 "[Missing multipart boundary - "
916 "use \"show\" to display "
917 "the raw message]\n");
918 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
919 qf, stats, NULL);
921 for (np = ip->m_multipart; np; np = np->m_nextpart) {
922 if (np->m_mimecontent == MIME_DISCARD &&
923 action != SEND_DECRYPT)
924 continue;
925 ispipe = FAL0;
926 switch (action) {
927 case SEND_TOFILE:
928 if (np->m_partstring &&
929 strcmp(np->m_partstring,
930 "1") == 0)
931 break;
932 stats = NULL;
933 if ((obuf = newfile(np,
934 UNVOLATILE(&ispipe)))
935 == NULL)
936 continue;
937 if (!ispipe)
938 break;
939 if (sigsetjmp(pipejmp, 1)) {
940 rt = -1;
941 goto jpipe_close;
943 oldpipe = safe_signal(SIGPIPE, onpipe);
944 break;
945 case SEND_TODISP:
946 case SEND_TODISP_ALL:
947 case SEND_QUOTE_ALL:
948 if (ip->m_mimecontent != MIME_MULTI &&
949 ip->m_mimecontent !=
950 MIME_ALTERNATIVE &&
951 ip->m_mimecontent !=
952 MIME_DIGEST)
953 break;
954 _print_part_info(&rest, np, doign,
955 level);
956 _out(rest.s, rest.l, obuf,
957 CONV_NONE, SEND_MBOX, qf,
958 stats, NULL);
959 break;
960 case SEND_TOFLTR:
961 putc('\0', obuf);
962 /*FALLTHRU*/
963 case SEND_MBOX:
964 case SEND_RFC822:
965 case SEND_SHOW:
966 case SEND_TOSRCH:
967 case SEND_QUOTE:
968 case SEND_DECRYPT:
969 case SEND_TOPIPE:
970 break;
973 quoteflt_flush(qf);
974 if (sendpart(zmp, np, obuf, doign, qf,
975 action, stats, level+1) < 0)
976 rt = -1;
977 quoteflt_reset(qf, origobuf);
978 if (action == SEND_QUOTE)
979 break;
980 if (action == SEND_TOFILE && obuf != origobuf) {
981 if (!ispipe)
982 Fclose(obuf);
983 else {
984 jpipe_close: safe_signal(SIGPIPE, SIG_IGN);
985 Pclose(obuf, TRU1);
986 safe_signal(SIGPIPE, oldpipe);
990 return rt;
991 case SEND_MBOX:
992 case SEND_RFC822:
993 case SEND_SHOW:
994 break;
999 * Copy out message body
1001 jcopyout:
1002 if (doign == allignore && level == 0) /* skip final blank line */
1003 cnt--;
1004 switch (ip->m_mimeenc) {
1005 case MIME_BIN:
1006 if (stats)
1007 stats[0] = -1;
1008 /*FALLTHRU*/
1009 case MIME_7B:
1010 case MIME_8B:
1011 convert = CONV_NONE;
1012 break;
1013 case MIME_QP:
1014 convert = CONV_FROMQP;
1015 break;
1016 case MIME_B64:
1017 switch (ip->m_mimecontent) {
1018 case MIME_TEXT:
1019 case MIME_TEXT_PLAIN:
1020 case MIME_TEXT_HTML:
1021 convert = CONV_FROMB64_T;
1022 break;
1023 default:
1024 convert = CONV_FROMB64;
1026 break;
1027 default:
1028 convert = CONV_NONE;
1030 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1031 action == SEND_RFC822 || action == SEND_SHOW)
1032 convert = CONV_NONE;
1033 #ifdef HAVE_ICONV
1034 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1035 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1036 action == SEND_TOSRCH) &&
1037 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1038 ip->m_mimecontent == MIME_TEXT_HTML ||
1039 ip->m_mimecontent == MIME_TEXT)) {
1040 char const *tcs = charset_get_lc();
1042 if (iconvd != (iconv_t)-1)
1043 n_iconv_close(iconvd);
1044 /* TODO Since Base64 has an odd 4:3 relation in between input
1045 * TODO and output an input line may end with a partial
1046 * TODO multibyte character; this is no problem at all unless
1047 * TODO we send to the display or whatever, i.e., ensure
1048 * TODO makeprint() or something; to avoid this trap, *force*
1049 * TODO iconv(), in which case this layer will handle leftovers
1050 * TODO correctly */
1051 if (convert == CONV_FROMB64_T ||
1052 (asccasecmp(tcs, ip->m_charset) &&
1053 asccasecmp(charset_get_7bit(),
1054 ip->m_charset))) {
1055 iconvd = n_iconv_open(tcs, ip->m_charset);
1056 /* XXX Don't bail out if we cannot iconv(3) here;
1057 * XXX alternatively we could avoid trying to open
1058 * XXX if ip->m_charset is "unknown-8bit", which was
1059 * XXX the one that has bitten me?? */
1061 * TODO errors should DEFINETELY not be scrolled away!
1062 * TODO what about an error buffer (think old shsp(1)),
1063 * TODO re-dump errors since last snapshot when the
1064 * TODO command loop enters again? i.e., at least print
1065 * TODO "There were errors ?" before the next prompt,
1066 * TODO so that the user can look at the error buffer?
1068 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1069 fprintf(stderr, tr(179,
1070 "Cannot convert from %s to %s\n"),
1071 ip->m_charset, tcs);
1072 /*return -1;*/
1076 #endif
1077 if (pipecomm != NULL &&
1078 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
1079 action == SEND_QUOTE || action == SEND_QUOTE_ALL)) {
1080 qbuf = obuf;
1081 pbuf = _pipefile(pipecomm, UNVOLATILE(&qbuf),
1082 action == SEND_QUOTE || action == SEND_QUOTE_ALL,
1083 !ispipe);
1084 action = SEND_TOPIPE;
1085 if (pbuf != qbuf) {
1086 oldpipe = safe_signal(SIGPIPE, onpipe);
1087 if (sigsetjmp(pipejmp, 1))
1088 goto end;
1090 } else
1091 pbuf = qbuf = obuf;
1094 bool_t eof;
1095 size_t save_qf_pfix_len = qf->qf_pfix_len;
1096 off_t *save_stats = stats;
1098 if (pbuf != origobuf) {
1099 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1100 stats = NULL;
1102 eof = FAL0;
1103 rest.s = NULL;
1104 rest.l = 0;
1106 quoteflt_reset(qf, pbuf);
1107 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1108 joutln:
1109 if (_out(line, linelen, pbuf, convert, action, qf, stats,
1110 &rest) < 0 || ferror(pbuf)) {
1111 rt = -1; /* XXX Should bail away?! */
1112 break;
1115 if (!eof && rest.l != 0) {
1116 linelen = 0;
1117 eof = TRU1;
1118 action |= _TD_EOF;
1119 goto joutln;
1121 quoteflt_flush(qf);
1122 if (rest.s != NULL)
1123 free(rest.s);
1125 if (pbuf != origobuf) {
1126 qf->qf_pfix_len = save_qf_pfix_len;
1127 stats = save_stats;
1131 end: free(line);
1132 if (pbuf != qbuf) {
1133 safe_signal(SIGPIPE, SIG_IGN);
1134 Pclose(pbuf, ispipe);
1135 safe_signal(SIGPIPE, oldpipe);
1136 if (qbuf != obuf)
1137 pipecpy(qbuf, obuf, origobuf, qf, stats);
1139 #ifdef HAVE_ICONV
1140 if (iconvd != (iconv_t)-1)
1141 n_iconv_close(iconvd);
1142 #endif
1143 return rt;
1146 static struct mimepart *
1147 parsemsg(struct message *mp, enum parseflags pf)
1149 struct mimepart *ip;
1151 ip = csalloc(1, sizeof *ip);
1152 ip->m_flag = mp->m_flag;
1153 ip->m_have = mp->m_have;
1154 ip->m_block = mp->m_block;
1155 ip->m_offset = mp->m_offset;
1156 ip->m_size = mp->m_size;
1157 ip->m_xsize = mp->m_xsize;
1158 ip->m_lines = mp->m_lines;
1159 ip->m_xlines = mp->m_lines;
1160 if (parsepart(mp, ip, pf, 0) != OKAY)
1161 return NULL;
1162 return ip;
1165 static enum okay
1166 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1167 int level)
1169 char *cp;
1171 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
1172 if (ip->m_ct_type != NULL) {
1173 ip->m_ct_type_plain = savestr(ip->m_ct_type);
1174 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
1175 *cp = '\0';
1176 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
1177 ip->m_ct_type_plain = UNCONST("message/rfc822");
1178 else
1179 ip->m_ct_type_plain = UNCONST("text/plain");
1181 if (ip->m_ct_type)
1182 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
1183 if (ip->m_charset == NULL)
1184 ip->m_charset = charset_get_7bit();
1185 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
1186 (struct message *)ip);
1187 ip->m_mimeenc = ip->m_ct_transfer_enc ?
1188 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
1189 if ((cp = hfield1("content-disposition", (struct message *)ip)) == 0 ||
1190 (ip->m_filename = mime_getparam("filename", cp)) == 0)
1191 if (ip->m_ct_type != NULL)
1192 ip->m_filename = mime_getparam("name", ip->m_ct_type);
1193 ip->m_mimecontent = mime_classify_content_of_part(ip);
1195 if (pf & PARSE_PARTS) {
1196 if (level > 9999) {
1197 fprintf(stderr, tr(36,
1198 "MIME content too deeply nested\n"));
1199 return STOP;
1201 switch (ip->m_mimecontent) {
1202 case MIME_PKCS7:
1203 if (pf & PARSE_DECRYPT) {
1204 #ifdef HAVE_SSL
1205 parsepkcs7(zmp, ip, pf, level);
1206 break;
1207 #else
1208 fprintf(stderr, tr(225,
1209 "No SSL support compiled in.\n"));
1210 return STOP;
1211 #endif
1213 /*FALLTHRU*/
1214 default:
1215 break;
1216 case MIME_MULTI:
1217 case MIME_ALTERNATIVE:
1218 case MIME_DIGEST:
1219 _parsemultipart(zmp, ip, pf, level);
1220 break;
1221 case MIME_822:
1222 parse822(zmp, ip, pf, level);
1223 break;
1226 return OKAY;
1229 static void
1230 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
1232 struct mimepart *pp;
1233 size_t sz;
1235 *np = csalloc(1, sizeof **np);
1236 (*np)->m_flag = MNOFROM;
1237 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
1238 (*np)->m_block = mailx_blockof(offs);
1239 (*np)->m_offset = mailx_offsetof(offs);
1240 if (part) {
1241 (*part)++;
1242 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
1243 sz += 20;
1244 (*np)->m_partstring = salloc(sz);
1245 if (ip->m_partstring)
1246 snprintf((*np)->m_partstring, sz, "%s.%u",
1247 ip->m_partstring, *part);
1248 else
1249 snprintf((*np)->m_partstring, sz, "%u", *part);
1250 } else
1251 (*np)->m_mimecontent = MIME_DISCARD;
1252 (*np)->m_parent = ip;
1253 if (ip->m_multipart) {
1254 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
1255 pp->m_nextpart = *np;
1256 } else
1257 ip->m_multipart = *np;
1260 static void
1261 endpart(struct mimepart **np, off_t xoffs, long lines)
1263 off_t offs;
1265 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
1266 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
1267 (*np)->m_lines = (*np)->m_xlines = lines;
1268 *np = NULL;
1271 static void
1272 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1273 int level)
1275 int c, lastc = '\n';
1276 size_t cnt;
1277 FILE *ibuf;
1278 off_t offs;
1279 struct mimepart *np;
1280 long lines;
1282 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
1283 return;
1284 cnt = ip->m_size;
1285 lines = ip->m_lines;
1286 while (cnt && ((c = getc(ibuf)) != EOF)) {
1287 cnt--;
1288 if (c == '\n') {
1289 lines--;
1290 if (lastc == '\n')
1291 break;
1293 lastc = c;
1295 offs = ftell(ibuf);
1296 np = csalloc(1, sizeof *np);
1297 np->m_flag = MNOFROM;
1298 np->m_have = HAVE_HEADER|HAVE_BODY;
1299 np->m_block = mailx_blockof(offs);
1300 np->m_offset = mailx_offsetof(offs);
1301 np->m_size = np->m_xsize = cnt;
1302 np->m_lines = np->m_xlines = lines;
1303 np->m_partstring = ip->m_partstring;
1304 np->m_parent = ip;
1305 ip->m_multipart = np;
1306 if (ok_blook(rfc822_body_from_)) {
1307 substdate((struct message *)np);
1308 np->m_from = fakefrom((struct message *)np);
1310 parsepart(zmp, np, pf, level+1);
1313 #ifdef HAVE_SSL
1314 static void
1315 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1316 int level)
1318 struct message m, *xmp;
1319 struct mimepart *np;
1320 char *to, *cc;
1322 memcpy(&m, ip, sizeof m);
1323 to = hfield1("to", zmp);
1324 cc = hfield1("cc", zmp);
1325 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
1326 np = csalloc(1, sizeof *np);
1327 np->m_flag = xmp->m_flag;
1328 np->m_have = xmp->m_have;
1329 np->m_block = xmp->m_block;
1330 np->m_offset = xmp->m_offset;
1331 np->m_size = xmp->m_size;
1332 np->m_xsize = xmp->m_xsize;
1333 np->m_lines = xmp->m_lines;
1334 np->m_xlines = xmp->m_xlines;
1335 np->m_partstring = ip->m_partstring;
1336 if (parsepart(zmp, np, pf, level+1) == OKAY) {
1337 np->m_parent = ip;
1338 ip->m_multipart = np;
1342 #endif
1345 * Get a file for an attachment.
1347 static FILE *
1348 newfile(struct mimepart *ip, int *ispipe)
1350 char *f = ip->m_filename;
1351 struct str in, out;
1352 FILE *fp;
1354 *ispipe = 0;
1355 if (f != NULL && f != (char *)-1) {
1356 in.s = f;
1357 in.l = strlen(f);
1358 mime_fromhdr(&in, &out, TD_ISPR);
1359 memcpy(f, out.s, out.l);
1360 *(f + out.l) = '\0';
1361 free(out.s);
1364 if (options & OPT_INTERACTIVE) {
1365 char *f2, *f3;
1366 jgetname: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1367 ip->m_partstring ? ip->m_partstring : "?",
1368 ip->m_ct_type_plain);
1369 f2 = readstr_input(": ", f != (char *)-1 ? f : NULL);
1370 if (f2 == NULL || *f2 == '\0') {
1371 fprintf(stderr, tr(279, "... skipping this\n"));
1372 return (NULL);
1373 } else if (*f2 == '|')
1374 /* Pipes are expanded by the shell */
1375 f = f2;
1376 else if ((f3 = file_expand(f2)) == NULL)
1377 /* (Error message written by file_expand()) */
1378 goto jgetname;
1379 else
1380 f = f3;
1382 if (f == NULL || f == (char *)-1)
1383 return NULL;
1385 if (*f == '|') {
1386 char const *cp;
1387 cp = ok_vlook(SHELL);
1388 if (cp == NULL)
1389 cp = XSHELL;
1390 fp = Popen(f + 1, "w", cp, 1);
1391 if (! (*ispipe = (fp != NULL)))
1392 perror(f);
1393 } else {
1394 if ((fp = Fopen(f, "w")) == NULL)
1395 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1397 return (fp);
1400 static void
1401 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1402 off_t *stats)
1404 char *line = NULL;
1405 size_t linesize = 0, linelen, cnt;
1406 ssize_t all_sz, sz;
1408 fflush(pipebuf);
1409 rewind(pipebuf);
1410 cnt = fsize(pipebuf);
1411 all_sz = 0;
1413 quoteflt_reset(qf, outbuf);
1414 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0)
1415 != NULL) {
1416 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1417 break;
1418 all_sz += sz;
1420 if ((sz = quoteflt_flush(qf)) > 0)
1421 all_sz += sz;
1422 if (line)
1423 free(line);
1425 if (all_sz > 0 && outbuf == origobuf)
1426 _addstats(stats, 1, all_sz);
1427 fclose(pipebuf);
1431 * Output a reasonable looking status field.
1433 static void
1434 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1435 off_t *stats)
1437 char statout[3];
1438 char *cp = statout;
1440 if (mp->m_flag & MREAD)
1441 *cp++ = 'R';
1442 if ((mp->m_flag & MNEW) == 0)
1443 *cp++ = 'O';
1444 *cp = 0;
1445 if (statout[0]) {
1446 int i = fprintf(obuf, "%.*sStatus: %s\n",
1447 (int)qf->qf_pfix_len,
1448 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1449 statout);
1450 if (i > 0)
1451 _addstats(stats, 1, i);
1455 static void
1456 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1457 off_t *stats)
1459 char xstatout[4];
1460 char *xp = xstatout;
1462 if (mp->m_flag & MFLAGGED)
1463 *xp++ = 'F';
1464 if (mp->m_flag & MANSWERED)
1465 *xp++ = 'A';
1466 if (mp->m_flag & MDRAFTED)
1467 *xp++ = 'T';
1468 *xp = 0;
1469 if (xstatout[0]) {
1470 int i = fprintf(obuf, "%.*sX-Status: %s\n",
1471 (int)qf->qf_pfix_len,
1472 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1473 xstatout);
1474 if (i > 0)
1475 _addstats(stats, 1, i);
1479 static void
1480 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1482 char const *froma, *date, *nl;
1483 int i;
1485 if (ip && ip->m_from) {
1486 froma = ip->m_from;
1487 date = fakedate(ip->m_time);
1488 nl = "\n";
1489 } else {
1490 froma = myname;
1491 date = time_current.tc_ctime;
1492 nl = "";
1495 colour_put(fp, COLOURSPEC_FROM_);
1496 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1497 colour_reset(fp);
1498 if (i > 0)
1499 _addstats(stats, (*nl != '\0'), i);