NEWS: update for s-nail-14_5_2-sort.patch
[s-mailx.git] / send.c
blob902c82521904f1da0d87229724e49f3f317aa1ad
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;
483 struct str const *cpre, *csuf;
484 #ifdef HAVE_COLOUR
485 cpre = colour_get(COLOURSPEC_FROM_);
486 csuf = colour_get(COLOURSPEC_RESET);
487 #else
488 cpre = csuf = NULL;
489 #endif
490 if (mp->m_flag & MNOFROM) {
491 if (doign != allignore && doign != fwdignore &&
492 action != SEND_RFC822)
493 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
494 (cpre != NULL ? cpre->s : ""),
495 (int)qf.qf_pfix_len,
496 (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
497 fakefrom(mp), fakedate(mp->m_time),
498 (csuf != NULL ? csuf->s : ""));
499 } else {
500 if (doign != allignore && doign != fwdignore &&
501 action != SEND_RFC822) {
502 if (qf.qf_pfix_len > 0) {
503 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix,
504 qf.qf_pfix_len, obuf);
505 if (i != qf.qf_pfix_len)
506 goto jleave;
507 sz += i;
509 #ifdef HAVE_COLOUR
510 if (cpre != NULL) {
511 fputs(cpre->s, obuf);
512 cpre = (struct str const*)0x1;
514 #endif
516 while (cnt && (c = getc(ibuf)) != EOF) {
517 if (doign != allignore && doign != fwdignore &&
518 action != SEND_RFC822) {
519 #ifdef HAVE_COLOUR
520 if (c == '\n' && csuf != NULL) {
521 cpre = (struct str const*)0x1;
522 fputs(csuf->s, obuf);
524 #endif
525 putc(c, obuf);
526 sz++;
528 cnt--;
529 if (c == '\n')
530 break;
532 #ifdef HAVE_COLOUR
533 if (csuf != NULL && cpre != (struct str const*)0x1)
534 fputs(csuf->s, obuf);
535 #endif
538 if (sz)
539 _addstats(stats, 1, sz);
541 pf = 0;
542 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
543 pf |= PARSE_DECRYPT|PARSE_PARTS;
544 if ((ip = parsemsg(mp, pf)) == NULL)
545 goto jleave;
547 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
548 jleave:
549 quoteflt_destroy(&qf);
550 return rv;
553 static int
554 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
555 struct ignoretab *doign, struct quoteflt *qf,
556 enum sendaction volatile action, off_t *volatile stats, int level)
558 int volatile ispipe, rt = 0;
559 struct str rest;
560 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
561 size_t linesize = 0, linelen, cnt;
562 int dostat, infld = 0, ignoring = 1, isenc, c;
563 struct mimepart *volatile np;
564 FILE *volatile ibuf = NULL, *volatile pbuf = obuf,
565 *volatile qbuf = obuf, *origobuf = obuf;
566 enum conversion volatile convert;
567 sighandler_type volatile oldpipe = SIG_DFL;
568 long lineno = 0;
570 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
571 action != SEND_MBOX && action != SEND_RFC822 &&
572 action != SEND_SHOW)
573 goto skip;
574 dostat = 0;
575 if (level == 0) {
576 if (doign != NULL) {
577 if (!is_ign("status", 6, doign))
578 dostat |= 1;
579 if (!is_ign("x-status", 8, doign))
580 dostat |= 2;
581 } else
582 dostat = 3;
584 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
585 return -1;
586 cnt = ip->m_size;
587 if (ip->m_mimecontent == MIME_DISCARD)
588 goto skip;
590 if ((ip->m_flag & MNOFROM) == 0)
591 while (cnt && (c = getc(ibuf)) != EOF) {
592 cnt--;
593 if (c == '\n')
594 break;
596 isenc = 0;
597 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
598 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
599 action == SEND_TOSRCH || action == SEND_TOFLTR ?
600 CONV_FROMHDR : CONV_NONE;
602 /* Work the headers */
603 quoteflt_reset(qf, obuf);
604 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
605 lineno++;
606 if (line[0] == '\n') {
608 * If line is blank, we've reached end of
609 * headers, so force out status: field
610 * and note that we are no longer in header
611 * fields
613 if (dostat & 1)
614 statusput(zmp, obuf, qf, stats);
615 if (dostat & 2)
616 xstatusput(zmp, obuf, qf, stats);
617 if (doign != allignore)
618 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
619 qf, stats, NULL);
620 break;
622 isenc &= ~1;
623 if (infld && blankchar(line[0])) {
625 * If this line is a continuation (via space or tab)
626 * of a previous header field, determine if the start
627 * of the line is a MIME encoded word.
629 if (isenc & 2) {
630 for (cp = line; blankchar(*cp); cp++);
631 if (cp > line && linelen - (cp - line) > 8 &&
632 cp[0] == '=' && cp[1] == '?')
633 isenc |= 1;
635 } else {
637 * Pick up the header field if we have one.
639 for (cp = line; (c = *cp & 0377) && c != ':' &&
640 !spacechar(c); ++cp)
642 cp2 = cp;
643 while (spacechar(*cp))
644 ++cp;
645 if (cp[0] != ':' && level == 0 && lineno == 1) {
647 * Not a header line, force out status:
648 * This happens in uucp style mail where
649 * there are no headers at all.
651 if (dostat & 1)
652 statusput(zmp, obuf, qf, stats);
653 if (dostat & 2)
654 xstatusput(zmp, obuf, qf, stats);
655 if (doign != allignore)
656 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX,
657 qf, stats, NULL);
658 break;
661 * If it is an ignored field and
662 * we care about such things, skip it.
664 c = *cp2;
665 *cp2 = 0; /* temporarily null terminate */
666 if ((doign &&
667 is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
668 (action == SEND_MBOX &&
669 !ok_blook(keep_content_length) &&
670 (asccasecmp(line, "content-length")==0
671 || asccasecmp(line, "lines") == 0)))
672 ignoring = 1;
673 else if (asccasecmp(line, "status") == 0) {
675 * If the field is "status," go compute
676 * and print the real Status: field
678 if (dostat & 1) {
679 statusput(zmp, obuf, qf, stats);
680 dostat &= ~1;
681 ignoring = 1;
683 } else if (asccasecmp(line, "x-status") == 0) {
685 * If the field is "status," go compute
686 * and print the real Status: field
688 if (dostat & 2) {
689 xstatusput(zmp, obuf, qf, stats);
690 dostat &= ~2;
691 ignoring = 1;
693 } else {
694 ignoring = 0;
695 #ifdef HAVE_COLOUR
696 pipecomm = savestrbuf(line,
697 PTR2SIZE(cp2 - line));
698 #endif
700 *cp2 = c;
701 infld = 1;
704 * Determine if the end of the line is a MIME encoded word.
706 isenc &= ~2;
707 if (cnt && (c = getc(ibuf)) != EOF) {
708 if (blankchar(c)) {
709 if (linelen > 0 && line[linelen - 1] == '\n')
710 cp = &line[linelen - 2];
711 else
712 cp = &line[linelen - 1];
713 while (cp >= line && whitechar(*cp))
714 ++cp;
715 if (cp - line > 8 && cp[0] == '=' &&
716 cp[-1] == '?')
717 isenc |= 2;
719 ungetc(c, ibuf);
721 if (!ignoring) {
722 size_t len = linelen;
723 start = line;
724 if (action == SEND_TODISP ||
725 action == SEND_TODISP_ALL ||
726 action == SEND_QUOTE ||
727 action == SEND_QUOTE_ALL ||
728 action == SEND_TOSRCH ||
729 action == SEND_TOFLTR) {
731 * Strip blank characters if two MIME-encoded
732 * words follow on continuing lines.
734 if (isenc & 1)
735 while (len > 0 && blankchar(*start)) {
736 ++start;
737 --len;
739 if (isenc & 2)
740 if (len > 0 && start[len - 1] == '\n')
741 --len;
742 while (len > 0 && blankchar(start[len - 1]))
743 --len;
745 #ifdef HAVE_COLOUR
747 bool_t colour_stripped = FAL0;
748 if (pipecomm != NULL) {
749 colour_put_header(obuf, pipecomm);
750 if (len > 0 && start[len - 1] == '\n') {
751 colour_stripped = TRU1;
752 --len;
755 #endif
756 _out(start, len, obuf, convert, action, qf, stats,
757 NULL);
758 #ifdef HAVE_COLOUR
759 if (pipecomm != NULL) {
760 colour_reset(obuf); /* XXX reset after \n!! */
761 if (colour_stripped)
762 fputc('\n', obuf);
765 #endif
766 if (ferror(obuf)) {
767 free(line);
768 return -1;
772 quoteflt_flush(qf);
773 free(line);
774 line = NULL;
776 skip:
777 switch (ip->m_mimecontent) {
778 case MIME_822:
779 switch (action) {
780 case SEND_TOFLTR:
781 putc('\0', obuf);
782 /*FALLTHRU*/
783 case SEND_TODISP:
784 case SEND_TODISP_ALL:
785 case SEND_QUOTE:
786 case SEND_QUOTE_ALL:
787 if (ok_blook(rfc822_body_from_)) {
788 if (qf->qf_pfix_len > 0) {
789 size_t i = fwrite(qf->qf_pfix,
790 sizeof *qf->qf_pfix,
791 qf->qf_pfix_len, obuf);
792 if (i == qf->qf_pfix_len)
793 _addstats(stats, 0, i);
795 put_from_(obuf, ip->m_multipart, stats);
797 /*FALLTHRU*/
798 case SEND_TOSRCH:
799 case SEND_DECRYPT:
800 goto multi;
801 case SEND_TOFILE:
802 case SEND_TOPIPE:
803 if (ok_blook(rfc822_body_from_))
804 put_from_(obuf, ip->m_multipart, stats);
805 /*FALLTHRU*/
806 case SEND_MBOX:
807 case SEND_RFC822:
808 case SEND_SHOW:
809 break;
811 break;
812 case MIME_TEXT_HTML:
813 if (action == SEND_TOFLTR)
814 putc('\b', obuf);
815 /*FALLTHRU*/
816 case MIME_TEXT:
817 case MIME_TEXT_PLAIN:
818 switch (action) {
819 case SEND_TODISP:
820 case SEND_TODISP_ALL:
821 case SEND_QUOTE:
822 case SEND_QUOTE_ALL:
823 ispipe = TRU1;
824 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
825 case PIPE_MSG:
826 _out(pipecomm, strlen(pipecomm), obuf,
827 CONV_NONE, SEND_MBOX, qf, stats, NULL);
828 pipecomm = NULL;
829 /* FALLTRHU */
830 case PIPE_TEXT:
831 case PIPE_COMM:
832 case PIPE_ASYNC:
833 case PIPE_NULL:
834 break;
836 /* FALLTRHU */
837 default:
838 break;
840 break;
841 case MIME_DISCARD:
842 if (action != SEND_DECRYPT)
843 return rt;
844 break;
845 case MIME_PKCS7:
846 if (action != SEND_MBOX && action != SEND_RFC822 &&
847 action != SEND_SHOW && ip->m_multipart)
848 goto multi;
849 /*FALLTHRU*/
850 default:
851 switch (action) {
852 case SEND_TODISP:
853 case SEND_TODISP_ALL:
854 case SEND_QUOTE:
855 case SEND_QUOTE_ALL:
856 ispipe = TRU1;
857 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
858 case PIPE_MSG:
859 _out(pipecomm, strlen(pipecomm), obuf,
860 CONV_NONE, SEND_MBOX, qf, stats, NULL);
861 pipecomm = NULL;
862 break;
863 case PIPE_ASYNC:
864 ispipe = FAL0;
865 /* FALLTHRU */
866 case PIPE_COMM:
867 case PIPE_NULL:
868 break;
869 case PIPE_TEXT:
870 goto jcopyout; /* break; break; */
872 if (pipecomm != NULL)
873 break;
874 if (level == 0 && cnt) {
875 char const *x = tr(210, "[Binary content]\n");
876 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
877 qf, stats, NULL);
879 /*FALLTHRU*/
880 case SEND_TOFLTR:
881 return rt;
882 case SEND_TOFILE:
883 case SEND_TOPIPE:
884 case SEND_TOSRCH:
885 case SEND_DECRYPT:
886 case SEND_MBOX:
887 case SEND_RFC822:
888 case SEND_SHOW:
889 break;
891 break;
892 case MIME_ALTERNATIVE:
893 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
894 !ok_blook(print_alternatives)) {
895 bool_t doact = FAL0;
896 for (np = ip->m_multipart; np; np = np->m_nextpart)
897 if (np->m_mimecontent == MIME_TEXT_PLAIN)
898 doact = TRU1;
899 if (doact) {
900 for (np = ip->m_multipart; np;
901 np = np->m_nextpart) {
902 if (np->m_ct_type_plain != NULL &&
903 action != SEND_QUOTE) {
904 _print_part_info(&rest, np,
905 doign, level);
906 _out(rest.s, rest.l, obuf,
907 CONV_NONE, SEND_MBOX,
908 qf, stats, NULL);
910 if (doact && np->m_mimecontent ==
911 MIME_TEXT_PLAIN) {
912 doact = FAL0;
913 rt = sendpart(zmp, np, obuf,
914 doign, qf, action,
915 stats, level + 1);
916 quoteflt_reset(qf, origobuf);
917 if (rt < 0)
918 break;
921 return rt;
924 /*FALLTHRU*/
925 case MIME_MULTI:
926 case MIME_DIGEST:
927 switch (action) {
928 case SEND_TODISP:
929 case SEND_TODISP_ALL:
930 case SEND_QUOTE:
931 case SEND_QUOTE_ALL:
932 case SEND_TOFILE:
933 case SEND_TOPIPE:
934 case SEND_TOSRCH:
935 case SEND_TOFLTR:
936 case SEND_DECRYPT:
937 multi:
938 if ((action == SEND_TODISP ||
939 action == SEND_TODISP_ALL) &&
940 ip->m_multipart != NULL &&
941 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
942 ip->m_multipart->m_nextpart == NULL) {
943 char const *x = tr(85,
944 "[Missing multipart boundary - "
945 "use \"show\" to display "
946 "the raw message]\n");
947 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX,
948 qf, stats, NULL);
950 for (np = ip->m_multipart; np; np = np->m_nextpart) {
951 if (np->m_mimecontent == MIME_DISCARD &&
952 action != SEND_DECRYPT)
953 continue;
954 ispipe = FAL0;
955 switch (action) {
956 case SEND_TOFILE:
957 if (np->m_partstring &&
958 strcmp(np->m_partstring,
959 "1") == 0)
960 break;
961 stats = NULL;
962 if ((obuf = newfile(np,
963 UNVOLATILE(&ispipe)))
964 == NULL)
965 continue;
966 if (!ispipe)
967 break;
968 if (sigsetjmp(pipejmp, 1)) {
969 rt = -1;
970 goto jpipe_close;
972 oldpipe = safe_signal(SIGPIPE, onpipe);
973 break;
974 case SEND_TODISP:
975 case SEND_TODISP_ALL:
976 case SEND_QUOTE_ALL:
977 if (ip->m_mimecontent != MIME_MULTI &&
978 ip->m_mimecontent !=
979 MIME_ALTERNATIVE &&
980 ip->m_mimecontent !=
981 MIME_DIGEST)
982 break;
983 _print_part_info(&rest, np, doign,
984 level);
985 _out(rest.s, rest.l, obuf,
986 CONV_NONE, SEND_MBOX, qf,
987 stats, NULL);
988 break;
989 case SEND_TOFLTR:
990 putc('\0', obuf);
991 /*FALLTHRU*/
992 case SEND_MBOX:
993 case SEND_RFC822:
994 case SEND_SHOW:
995 case SEND_TOSRCH:
996 case SEND_QUOTE:
997 case SEND_DECRYPT:
998 case SEND_TOPIPE:
999 break;
1002 quoteflt_flush(qf);
1003 if (sendpart(zmp, np, obuf, doign, qf,
1004 action, stats, level+1) < 0)
1005 rt = -1;
1006 quoteflt_reset(qf, origobuf);
1007 if (action == SEND_QUOTE)
1008 break;
1009 if (action == SEND_TOFILE && obuf != origobuf) {
1010 if (!ispipe)
1011 Fclose(obuf);
1012 else {
1013 jpipe_close: safe_signal(SIGPIPE, SIG_IGN);
1014 Pclose(obuf, TRU1);
1015 safe_signal(SIGPIPE, oldpipe);
1019 return rt;
1020 case SEND_MBOX:
1021 case SEND_RFC822:
1022 case SEND_SHOW:
1023 break;
1028 * Copy out message body
1030 jcopyout:
1031 if (doign == allignore && level == 0) /* skip final blank line */
1032 cnt--;
1033 switch (ip->m_mimeenc) {
1034 case MIME_BIN:
1035 if (stats)
1036 stats[0] = -1;
1037 /*FALLTHRU*/
1038 case MIME_7B:
1039 case MIME_8B:
1040 convert = CONV_NONE;
1041 break;
1042 case MIME_QP:
1043 convert = CONV_FROMQP;
1044 break;
1045 case MIME_B64:
1046 switch (ip->m_mimecontent) {
1047 case MIME_TEXT:
1048 case MIME_TEXT_PLAIN:
1049 case MIME_TEXT_HTML:
1050 convert = CONV_FROMB64_T;
1051 break;
1052 default:
1053 convert = CONV_FROMB64;
1055 break;
1056 default:
1057 convert = CONV_NONE;
1059 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1060 action == SEND_RFC822 || action == SEND_SHOW)
1061 convert = CONV_NONE;
1062 #ifdef HAVE_ICONV
1063 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1064 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1065 action == SEND_TOSRCH) &&
1066 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1067 ip->m_mimecontent == MIME_TEXT_HTML ||
1068 ip->m_mimecontent == MIME_TEXT)) {
1069 char const *tcs = charset_get_lc();
1071 if (iconvd != (iconv_t)-1)
1072 n_iconv_close(iconvd);
1073 /* TODO Since Base64 has an odd 4:3 relation in between input
1074 * TODO and output an input line may end with a partial
1075 * TODO multibyte character; this is no problem at all unless
1076 * TODO we send to the display or whatever, i.e., ensure
1077 * TODO makeprint() or something; to avoid this trap, *force*
1078 * TODO iconv(), in which case this layer will handle leftovers
1079 * TODO correctly */
1080 if (convert == CONV_FROMB64_T ||
1081 (asccasecmp(tcs, ip->m_charset) &&
1082 asccasecmp(charset_get_7bit(),
1083 ip->m_charset))) {
1084 iconvd = n_iconv_open(tcs, ip->m_charset);
1085 /* XXX Don't bail out if we cannot iconv(3) here;
1086 * XXX alternatively we could avoid trying to open
1087 * XXX if ip->m_charset is "unknown-8bit", which was
1088 * XXX the one that has bitten me?? */
1090 * TODO errors should DEFINETELY not be scrolled away!
1091 * TODO what about an error buffer (think old shsp(1)),
1092 * TODO re-dump errors since last snapshot when the
1093 * TODO command loop enters again? i.e., at least print
1094 * TODO "There were errors ?" before the next prompt,
1095 * TODO so that the user can look at the error buffer?
1097 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1098 fprintf(stderr, tr(179,
1099 "Cannot convert from %s to %s\n"),
1100 ip->m_charset, tcs);
1101 /*return -1;*/
1105 #endif
1106 if (pipecomm != NULL &&
1107 (action == SEND_TODISP || action == SEND_TODISP_ALL ||
1108 action == SEND_QUOTE || action == SEND_QUOTE_ALL)) {
1109 qbuf = obuf;
1110 pbuf = _pipefile(pipecomm, UNVOLATILE(&qbuf),
1111 action == SEND_QUOTE || action == SEND_QUOTE_ALL,
1112 !ispipe);
1113 action = SEND_TOPIPE;
1114 if (pbuf != qbuf) {
1115 oldpipe = safe_signal(SIGPIPE, onpipe);
1116 if (sigsetjmp(pipejmp, 1))
1117 goto end;
1119 } else
1120 pbuf = qbuf = obuf;
1123 bool_t eof;
1124 size_t save_qf_pfix_len = qf->qf_pfix_len;
1125 off_t *save_stats = stats;
1127 if (pbuf != origobuf) {
1128 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1129 stats = NULL;
1131 eof = FAL0;
1132 rest.s = NULL;
1133 rest.l = 0;
1135 quoteflt_reset(qf, pbuf);
1136 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1137 joutln:
1138 if (_out(line, linelen, pbuf, convert, action, qf, stats,
1139 &rest) < 0 || ferror(pbuf)) {
1140 rt = -1; /* XXX Should bail away?! */
1141 break;
1144 if (!eof && rest.l != 0) {
1145 linelen = 0;
1146 eof = TRU1;
1147 action |= _TD_EOF;
1148 goto joutln;
1150 quoteflt_flush(qf);
1151 if (rest.s != NULL)
1152 free(rest.s);
1154 if (pbuf != origobuf) {
1155 qf->qf_pfix_len = save_qf_pfix_len;
1156 stats = save_stats;
1160 end: free(line);
1161 if (pbuf != qbuf) {
1162 safe_signal(SIGPIPE, SIG_IGN);
1163 Pclose(pbuf, ispipe);
1164 safe_signal(SIGPIPE, oldpipe);
1165 if (qbuf != obuf)
1166 pipecpy(qbuf, obuf, origobuf, qf, stats);
1168 #ifdef HAVE_ICONV
1169 if (iconvd != (iconv_t)-1)
1170 n_iconv_close(iconvd);
1171 #endif
1172 return rt;
1175 static struct mimepart *
1176 parsemsg(struct message *mp, enum parseflags pf)
1178 struct mimepart *ip;
1180 ip = csalloc(1, sizeof *ip);
1181 ip->m_flag = mp->m_flag;
1182 ip->m_have = mp->m_have;
1183 ip->m_block = mp->m_block;
1184 ip->m_offset = mp->m_offset;
1185 ip->m_size = mp->m_size;
1186 ip->m_xsize = mp->m_xsize;
1187 ip->m_lines = mp->m_lines;
1188 ip->m_xlines = mp->m_lines;
1189 if (parsepart(mp, ip, pf, 0) != OKAY)
1190 return NULL;
1191 return ip;
1194 static enum okay
1195 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1196 int level)
1198 char *cp;
1200 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
1201 if (ip->m_ct_type != NULL) {
1202 ip->m_ct_type_plain = savestr(ip->m_ct_type);
1203 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
1204 *cp = '\0';
1205 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
1206 ip->m_ct_type_plain = UNCONST("message/rfc822");
1207 else
1208 ip->m_ct_type_plain = UNCONST("text/plain");
1210 if (ip->m_ct_type)
1211 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
1212 if (ip->m_charset == NULL)
1213 ip->m_charset = charset_get_7bit();
1214 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
1215 (struct message *)ip);
1216 ip->m_mimeenc = ip->m_ct_transfer_enc ?
1217 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
1218 if ((cp = hfield1("content-disposition", (struct message *)ip)) == 0 ||
1219 (ip->m_filename = mime_getparam("filename", cp)) == 0)
1220 if (ip->m_ct_type != NULL)
1221 ip->m_filename = mime_getparam("name", ip->m_ct_type);
1222 ip->m_mimecontent = mime_classify_content_of_part(ip);
1224 if (pf & PARSE_PARTS) {
1225 if (level > 9999) {
1226 fprintf(stderr, tr(36,
1227 "MIME content too deeply nested\n"));
1228 return STOP;
1230 switch (ip->m_mimecontent) {
1231 case MIME_PKCS7:
1232 if (pf & PARSE_DECRYPT) {
1233 #ifdef HAVE_SSL
1234 parsepkcs7(zmp, ip, pf, level);
1235 break;
1236 #else
1237 fprintf(stderr, tr(225,
1238 "No SSL support compiled in.\n"));
1239 return STOP;
1240 #endif
1242 /*FALLTHRU*/
1243 default:
1244 break;
1245 case MIME_MULTI:
1246 case MIME_ALTERNATIVE:
1247 case MIME_DIGEST:
1248 _parsemultipart(zmp, ip, pf, level);
1249 break;
1250 case MIME_822:
1251 parse822(zmp, ip, pf, level);
1252 break;
1255 return OKAY;
1258 static void
1259 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
1261 struct mimepart *pp;
1262 size_t sz;
1264 *np = csalloc(1, sizeof **np);
1265 (*np)->m_flag = MNOFROM;
1266 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
1267 (*np)->m_block = mailx_blockof(offs);
1268 (*np)->m_offset = mailx_offsetof(offs);
1269 if (part) {
1270 (*part)++;
1271 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
1272 sz += 20;
1273 (*np)->m_partstring = salloc(sz);
1274 if (ip->m_partstring)
1275 snprintf((*np)->m_partstring, sz, "%s.%u",
1276 ip->m_partstring, *part);
1277 else
1278 snprintf((*np)->m_partstring, sz, "%u", *part);
1279 } else
1280 (*np)->m_mimecontent = MIME_DISCARD;
1281 (*np)->m_parent = ip;
1282 if (ip->m_multipart) {
1283 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
1284 pp->m_nextpart = *np;
1285 } else
1286 ip->m_multipart = *np;
1289 static void
1290 endpart(struct mimepart **np, off_t xoffs, long lines)
1292 off_t offs;
1294 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
1295 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
1296 (*np)->m_lines = (*np)->m_xlines = lines;
1297 *np = NULL;
1300 static void
1301 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1302 int level)
1304 int c, lastc = '\n';
1305 size_t cnt;
1306 FILE *ibuf;
1307 off_t offs;
1308 struct mimepart *np;
1309 long lines;
1311 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
1312 return;
1313 cnt = ip->m_size;
1314 lines = ip->m_lines;
1315 while (cnt && ((c = getc(ibuf)) != EOF)) {
1316 cnt--;
1317 if (c == '\n') {
1318 lines--;
1319 if (lastc == '\n')
1320 break;
1322 lastc = c;
1324 offs = ftell(ibuf);
1325 np = csalloc(1, sizeof *np);
1326 np->m_flag = MNOFROM;
1327 np->m_have = HAVE_HEADER|HAVE_BODY;
1328 np->m_block = mailx_blockof(offs);
1329 np->m_offset = mailx_offsetof(offs);
1330 np->m_size = np->m_xsize = cnt;
1331 np->m_lines = np->m_xlines = lines;
1332 np->m_partstring = ip->m_partstring;
1333 np->m_parent = ip;
1334 ip->m_multipart = np;
1335 if (ok_blook(rfc822_body_from_)) {
1336 substdate((struct message *)np);
1337 np->m_from = fakefrom((struct message *)np);
1339 parsepart(zmp, np, pf, level+1);
1342 #ifdef HAVE_SSL
1343 static void
1344 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
1345 int level)
1347 struct message m, *xmp;
1348 struct mimepart *np;
1349 char *to, *cc;
1351 memcpy(&m, ip, sizeof m);
1352 to = hfield1("to", zmp);
1353 cc = hfield1("cc", zmp);
1354 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
1355 np = csalloc(1, sizeof *np);
1356 np->m_flag = xmp->m_flag;
1357 np->m_have = xmp->m_have;
1358 np->m_block = xmp->m_block;
1359 np->m_offset = xmp->m_offset;
1360 np->m_size = xmp->m_size;
1361 np->m_xsize = xmp->m_xsize;
1362 np->m_lines = xmp->m_lines;
1363 np->m_xlines = xmp->m_xlines;
1364 np->m_partstring = ip->m_partstring;
1365 if (parsepart(zmp, np, pf, level+1) == OKAY) {
1366 np->m_parent = ip;
1367 ip->m_multipart = np;
1371 #endif
1374 * Get a file for an attachment.
1376 static FILE *
1377 newfile(struct mimepart *ip, int *ispipe)
1379 char *f = ip->m_filename;
1380 struct str in, out;
1381 FILE *fp;
1383 *ispipe = 0;
1384 if (f != NULL && f != (char *)-1) {
1385 in.s = f;
1386 in.l = strlen(f);
1387 mime_fromhdr(&in, &out, TD_ISPR);
1388 memcpy(f, out.s, out.l);
1389 *(f + out.l) = '\0';
1390 free(out.s);
1393 if (options & OPT_INTERACTIVE) {
1394 char *f2, *f3;
1395 jgetname: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1396 ip->m_partstring ? ip->m_partstring : "?",
1397 ip->m_ct_type_plain);
1398 f2 = readstr_input(": ", f != (char *)-1 ? f : NULL);
1399 if (f2 == NULL || *f2 == '\0') {
1400 fprintf(stderr, tr(279, "... skipping this\n"));
1401 return (NULL);
1402 } else if (*f2 == '|')
1403 /* Pipes are expanded by the shell */
1404 f = f2;
1405 else if ((f3 = file_expand(f2)) == NULL)
1406 /* (Error message written by file_expand()) */
1407 goto jgetname;
1408 else
1409 f = f3;
1411 if (f == NULL || f == (char *)-1)
1412 return NULL;
1414 if (*f == '|') {
1415 char const *cp;
1416 cp = ok_vlook(SHELL);
1417 if (cp == NULL)
1418 cp = XSHELL;
1419 fp = Popen(f + 1, "w", cp, 1);
1420 if (! (*ispipe = (fp != NULL)))
1421 perror(f);
1422 } else {
1423 if ((fp = Fopen(f, "w")) == NULL)
1424 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1426 return (fp);
1429 static void
1430 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1431 off_t *stats)
1433 char *line = NULL;
1434 size_t linesize = 0, linelen, cnt;
1435 ssize_t all_sz, sz;
1437 fflush(pipebuf);
1438 rewind(pipebuf);
1439 cnt = fsize(pipebuf);
1440 all_sz = 0;
1442 quoteflt_reset(qf, outbuf);
1443 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0)
1444 != NULL) {
1445 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1446 break;
1447 all_sz += sz;
1449 if ((sz = quoteflt_flush(qf)) > 0)
1450 all_sz += sz;
1451 if (line)
1452 free(line);
1454 if (all_sz > 0 && outbuf == origobuf)
1455 _addstats(stats, 1, all_sz);
1456 fclose(pipebuf);
1460 * Output a reasonable looking status field.
1462 static void
1463 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1464 off_t *stats)
1466 char statout[3];
1467 char *cp = statout;
1469 if (mp->m_flag & MREAD)
1470 *cp++ = 'R';
1471 if ((mp->m_flag & MNEW) == 0)
1472 *cp++ = 'O';
1473 *cp = 0;
1474 if (statout[0]) {
1475 int i = fprintf(obuf, "%.*sStatus: %s\n",
1476 (int)qf->qf_pfix_len,
1477 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1478 statout);
1479 if (i > 0)
1480 _addstats(stats, 1, i);
1484 static void
1485 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1486 off_t *stats)
1488 char xstatout[4];
1489 char *xp = xstatout;
1491 if (mp->m_flag & MFLAGGED)
1492 *xp++ = 'F';
1493 if (mp->m_flag & MANSWERED)
1494 *xp++ = 'A';
1495 if (mp->m_flag & MDRAFTED)
1496 *xp++ = 'T';
1497 *xp = 0;
1498 if (xstatout[0]) {
1499 int i = fprintf(obuf, "%.*sX-Status: %s\n",
1500 (int)qf->qf_pfix_len,
1501 (qf->qf_pfix_len > 0) ? qf->qf_pfix : 0,
1502 xstatout);
1503 if (i > 0)
1504 _addstats(stats, 1, i);
1508 static void
1509 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1511 char const *froma, *date, *nl;
1512 int i;
1514 if (ip && ip->m_from) {
1515 froma = ip->m_from;
1516 date = fakedate(ip->m_time);
1517 nl = "\n";
1518 } else {
1519 froma = myname;
1520 date = time_current.tc_ctime;
1521 nl = "";
1524 colour_put(fp, COLOURSPEC_FROM_);
1525 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1526 colour_reset(fp);
1527 if (i > 0)
1528 _addstats(stats, (*nl != '\0'), i);