*inbox*: if empty, only bypass *folder* to $MAIL or builtin default
[s-mailx.git] / send.c
blob8b2f814d7b95f1b919c953d6e60188c99935f655
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mail to mail folders and displays.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE send
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
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 static sigjmp_buf _send_pipejmp;
52 /* Going for user display, print Part: info string */
53 static void _print_part_info(FILE *obuf, struct mimepart const *mpp,
54 struct ignoretab *doign, int level,
55 struct quoteflt *qf, ui64_t *stats);
57 /* Query possible pipe command for MIME part */
58 static enum pipeflags _pipecmd(char const **result, struct mimepart const *mpp);
60 /* Create a pipe; if mpp is not NULL, place some NAILENV_* environment
61 * variables accordingly */
62 static FILE * _pipefile(char const *pipecomm, struct mimepart const *mpp,
63 FILE **qbuf, bool_t quote, bool_t async);
65 /* Call mime_write() as approbiate and adjust statistics */
66 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
67 enum conversion convert, enum sendaction action,
68 struct quoteflt *qf, ui64_t *stats, struct str *rest);
70 /* SIGPIPE handler */
71 static void _send_onpipe(int signo);
73 /* Send one part */
74 static int sendpart(struct message *zmp, struct mimepart *ip,
75 FILE *obuf, struct ignoretab *doign,
76 struct quoteflt *qf, enum sendaction action,
77 ui64_t *stats, int level);
79 /* Get a file for an attachment */
80 static FILE * newfile(struct mimepart *ip, int *ispipe);
82 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
83 struct quoteflt *qf, ui64_t *stats);
85 /* Output a reasonable looking status field */
86 static void statusput(const struct message *mp, FILE *obuf,
87 struct quoteflt *qf, ui64_t *stats);
88 static void xstatusput(const struct message *mp, FILE *obuf,
89 struct quoteflt *qf, ui64_t *stats);
91 static void put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats);
93 static void
94 _print_part_info(FILE *obuf, struct mimepart const *mpp, /* TODO strtofmt.. */
95 struct ignoretab *doign, int level, struct quoteflt *qf, ui64_t *stats)
97 char buf[64];
98 struct str ti = {NULL, 0}, to;
99 struct str const *cpre, *csuf;
100 char const *cp;
101 NYD2_ENTER;
103 #ifdef HAVE_COLOUR
104 cpre = colour_get(COLOURSPEC_PARTINFO);
105 csuf = colour_get(COLOURSPEC_RESET);
106 #else
107 cpre = csuf = NULL;
108 #endif
110 /* Take care of "99.99", i.e., 5 */
111 if ((cp = mpp->m_partstring) == NULL || cp[0] == '\0')
112 cp = "?";
113 if (level || (cp[0] != '1' && cp[1] == '\0'))
114 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
115 if (cpre != NULL)
116 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
117 _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
118 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
120 to.l = snprintf(buf, sizeof buf, " %" PRIuZ "/%" PRIuZ " ",
121 (uiz_t)mpp->m_lines, (uiz_t)mpp->m_size);
122 _out(buf, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
124 if ((cp = mpp->m_ct_type_usr_ovwr) != NULL)
125 _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
126 else
127 cp = mpp->m_ct_type_plain;
128 if ((to.l = strlen(cp)) > 30 && is_asccaseprefix(cp, "application/")) {
129 size_t const al = sizeof("appl../") -1, fl = sizeof("application/") -1;
130 size_t i = to.l - fl;
131 char *x = salloc(al + i +1);
133 memcpy(x, "appl../", al);
134 memcpy(x + al, cp + fl, i +1);
135 cp = x;
136 to.l = al + i;
138 _out(cp, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
140 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_ct_enc) != NULL) {
141 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
142 if (to.l > 25 && !asccasecmp(cp, "quoted-printable"))
143 cp = "qu.-pr.";
144 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
147 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_charset) != NULL) {
148 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
149 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
152 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
153 if (csuf != NULL)
154 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
155 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
157 if (is_ign("content-disposition", 19, doign) && mpp->m_filename != NULL &&
158 *mpp->m_filename != '\0') {
159 makeprint(n_str_add_cp(&ti, mpp->m_filename), &to);
160 free(ti.s);
161 to.l = delctrl(to.s, to.l);
163 if (cpre != NULL)
164 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
165 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
166 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
167 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
168 if (csuf != NULL)
169 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
170 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
172 free(to.s);
174 NYD2_LEAVE;
177 static enum pipeflags
178 _pipecmd(char const **result, struct mimepart const *mpp)
180 enum pipeflags rv;
181 char const *cp;
182 NYD2_ENTER;
184 *result = NULL;
186 /* Do we have any handler for this part? */
187 if ((cp = mime_type_mimepart_handler(mpp)) == NULL)
188 rv = PIPE_NULL;
189 else if (cp == MIME_TYPE_HANDLER_TEXT)
190 rv = PIPE_TEXT;
191 else if (
192 #ifdef HAVE_FILTER_HTML_TAGSOUP
193 cp == MIME_TYPE_HANDLER_HTML ||
194 #endif
195 *cp != '@') {
196 *result = cp;
197 rv = PIPE_COMM;
198 } else if (*++cp == '\0') {
199 /* Treat as plain text */
200 rv = PIPE_TEXT;
201 } else if (!(pstate & PS_MSGLIST_DIRECT)) {
202 /* Viewing multiple messages in one go, don't block system */
203 *result = _("[Directly address message only to display this]\n");
204 rv = PIPE_MSG;
205 } else {
206 /* Viewing a single message only */
207 /* TODO send/MIME layer rewrite: when we have a single-pass parser
208 * TODO then the parsing phase and the send phase will be separated;
209 * TODO that allows us to ask a user *before* we start the send, i.e.,
210 * TODO *before* a pager pipe is setup */
211 if (*cp == '&')
212 /* Asynchronous command, normal command line */
213 *result = ++cp, rv = PIPE_ASYNC;
214 else
215 *result = cp, rv = PIPE_COMM;
217 NYD2_LEAVE;
218 return rv;
221 static FILE *
222 _pipefile(char const *pipecomm, struct mimepart const *mpp, FILE **qbuf,
223 bool_t quote, bool_t async)
225 struct str s;
226 char const *env_addon[8], *cp, *sh;
227 FILE *rbuf;
228 NYD_ENTER;
230 rbuf = *qbuf;
232 if (quote) {
233 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER,
234 0600)) == NULL) {
235 n_perr(_("tmpfile"), 0);
236 *qbuf = rbuf;
238 async = FAL0;
241 #ifdef HAVE_FILTER_HTML_TAGSOUP
242 if (pipecomm == MIME_TYPE_HANDLER_HTML) {
243 union {int (*ptf)(void); char const *sh;} u;
245 fflush(*qbuf);
246 if (*qbuf != stdout) /* xxx never? v15: it'll be a filter anyway */
247 fflush(stdout);
249 pipecomm = "Builtin HTML tagsoup filter";
250 u.ptf = &htmlflt_process_main;
251 if((rbuf = Popen((char*)-1, "W", u.sh, NULL, fileno(*qbuf))) == NULL)
252 goto jerror;
253 goto jleave;
255 #endif
257 /* NAIL_FILENAME */
258 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
259 cp = "";
260 env_addon[0] = str_concat_csvl(&s, NAILENV_FILENAME, "=", cp, NULL)->s;
262 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
263 * TODO a file wherever he wants! *Do* create a zero-size temporary file
264 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
265 * TODO the pipe returns? Like this we *can* verify path/name issues! */
266 env_addon[1] = str_concat_csvl(&s, NAILENV_FILENAME_GENERATED, "=",
267 getrandstring(MIN(NAME_MAX / 4, 16)), NULL)->s;
269 /* NAIL_CONTENT{,_EVIDENCE} */
270 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
271 cp = "";
272 env_addon[2] = str_concat_csvl(&s, NAILENV_CONTENT, "=", cp, NULL)->s;
274 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
275 cp = mpp->m_ct_type_usr_ovwr;
276 env_addon[3] = str_concat_csvl(&s, NAILENV_CONTENT_EVIDENCE, "=", cp,
277 NULL)->s;
279 env_addon[4] = str_concat_csvl(&s, NAILENV_TMPDIR, "=", tempdir, NULL)->s;
280 env_addon[5] = str_concat_csvl(&s, "TMPDIR", "=", tempdir, NULL)->s;
282 env_addon[6] = NULL;
284 if ((sh = ok_vlook(SHELL)) == NULL)
285 sh = XSHELL;
287 rbuf = Popen(pipecomm, "W", sh, env_addon, (async ? -1 : fileno(*qbuf)));
288 if (rbuf == NULL) {
289 #ifdef HAVE_FILTER_HTML_TAGSOUP
290 jerror:
291 #endif
292 n_err(_("Cannot run MIME type handler \"%s\": %s\n"),
293 pipecomm, strerror(errno));
294 } else {
295 fflush(*qbuf);
296 if (*qbuf != stdout)
297 fflush(stdout);
299 #ifdef HAVE_FILTER_HTML_TAGSOUP
300 jleave:
301 #endif
302 NYD_LEAVE;
303 return rbuf;
306 SINLINE ssize_t
307 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
308 sendaction action, struct quoteflt *qf, ui64_t *stats, struct str *rest)
310 ssize_t sz = 0, n;
311 int flags;
312 NYD_ENTER;
314 #if 0
315 Well ... it turns out to not work like that since of course a valid
316 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
317 after an empty line has been seen, which cannot be detected that easily
318 right here!
319 ifdef HAVE_DEBUG /* TODO assert legacy */
320 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
321 * TODO other input situations handle RFC 4155 OR, if newly generated,
322 * TODO enforce quoted-printable if there is From_, as "required" by
323 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
324 * TODO if it may happen in this path, we should just treat decryption
325 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
326 if (action == SEND_MBOX || action == SEND_DECRYPT)
327 assert(!is_head(buf, len, FAL0));
328 #else
329 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
330 is_head(buf, len, FAL0)) {
331 putc('>', fp);
332 ++sz;
334 #endif
336 flags = ((int)action & _TD_EOF);
337 action &= ~_TD_EOF;
338 n = mime_write(buf, len, fp,
339 action == SEND_MBOX ? CONV_NONE : convert,
340 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
341 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
342 ? TD_ISPR | TD_ICONV
343 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
344 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
345 qf, rest);
346 if (n < 0)
347 sz = n;
348 else if (n > 0) {
349 sz += n;
350 if (stats != NULL)
351 *stats += sz;
353 NYD_LEAVE;
354 return sz;
357 static void
358 _send_onpipe(int signo)
360 NYD_X; /* Signal handler */
361 UNUSED(signo);
362 siglongjmp(_send_pipejmp, 1);
365 static sigjmp_buf __sendp_actjmp; /* TODO someday.. */
366 static int __sendp_sig; /* TODO someday.. */
367 static sighandler_type __sendp_opipe;
368 static void
369 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
371 NYD_X; /* Signal handler */
372 __sendp_sig = sig;
373 siglongjmp(__sendp_actjmp, 1);
376 static sigjmp_buf __sndalter_actjmp; /* TODO someday.. */
377 static int __sndalter_sig; /* TODO someday.. */
378 static void
379 __sndalter_onsig(int sig) /* TODO someday, we won't need it no more */
381 NYD_X; /* Signal handler */
382 __sndalter_sig = sig;
383 siglongjmp(__sndalter_actjmp, 1);
386 static int
387 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
388 struct ignoretab *doign, struct quoteflt *qf,
389 enum sendaction volatile action, ui64_t * volatile stats, int level)
391 int volatile ispipe, rv = 0;
392 struct str rest;
393 char *line = NULL, *cp, *cp2, *start;
394 char const *pipecomm = NULL;
395 size_t linesize = 0, linelen, cnt;
396 int dostat, infld = 0, ignoring = 1, isenc, c;
397 struct mimepart *volatile np;
398 FILE * volatile ibuf = NULL, * volatile pbuf = obuf, * volatile qbuf = obuf,
399 *origobuf = obuf;
400 enum conversion volatile convert;
401 sighandler_type volatile oldpipe = SIG_DFL;
402 long lineno = 0;
403 NYD_ENTER;
405 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
406 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
407 goto jskip;
409 dostat = 0;
410 if (level == 0) {
411 if (doign != NULL) {
412 if (!is_ign("status", 6, doign))
413 dostat |= 1;
414 if (!is_ign("x-status", 8, doign))
415 dostat |= 2;
416 } else
417 dostat = 3;
419 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
420 rv = -1;
421 goto jleave;
423 cnt = ip->m_size;
425 if (ip->m_mimecontent == MIME_DISCARD)
426 goto jskip;
428 if (!(ip->m_flag & MNOFROM))
429 while (cnt && (c = getc(ibuf)) != EOF) {
430 cnt--;
431 if (c == '\n')
432 break;
434 isenc = 0;
435 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
436 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
437 action == SEND_TOSRCH)
438 ? CONV_FROMHDR : CONV_NONE;
440 /* Work the headers */
441 quoteflt_reset(qf, obuf);
442 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
443 ++lineno;
444 if (line[0] == '\n') {
445 /* If line is blank, we've reached end of headers, so force out
446 * status: field and note that we are no longer in header fields */
447 if (dostat & 1)
448 statusput(zmp, obuf, qf, stats);
449 if (dostat & 2)
450 xstatusput(zmp, obuf, qf, stats);
451 if (doign != allignore)
452 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
453 break;
456 isenc &= ~1;
457 if (infld && blankchar(line[0])) {
458 /* If this line is a continuation (SP / HT) of a previous header
459 * field, determine if the start of the line is a MIME encoded word */
460 if (isenc & 2) {
461 for (cp = line; blankchar(*cp); ++cp);
462 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
463 cp[0] == '=' && cp[1] == '?')
464 isenc |= 1;
466 } else {
467 /* Pick up the header field if we have one */
468 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
470 cp2 = cp;
471 while (spacechar(*cp))
472 ++cp;
473 if (cp[0] != ':' && level == 0 && lineno == 1) {
474 /* Not a header line, force out status: This happens in uucp style
475 * mail where there are no headers at all */
476 if (dostat & 1)
477 statusput(zmp, obuf, qf, stats);
478 if (dostat & 2)
479 xstatusput(zmp, obuf, qf, stats);
480 if (doign != allignore)
481 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
482 break;
485 /* If it is an ignored field and we care about such things, skip it.
486 * Misuse dostat also for another bit xxx use a bitenum + for more */
487 if (ok_blook(keep_content_length))
488 dostat |= 1 << 2;
489 c = *cp2;
490 *cp2 = 0; /* temporarily null terminate */
491 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
492 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
493 (!asccasecmp(line, "content-length") ||
494 !asccasecmp(line, "lines"))))
495 ignoring = 1;
496 else if (!asccasecmp(line, "status")) {
497 /* If field is "status," go compute and print real Status: field */
498 if (dostat & 1) {
499 statusput(zmp, obuf, qf, stats);
500 dostat &= ~1;
501 ignoring = 1;
503 } else if (!asccasecmp(line, "x-status")) {
504 /* If field is "status," go compute and print real Status: field */
505 if (dostat & 2) {
506 xstatusput(zmp, obuf, qf, stats);
507 dostat &= ~2;
508 ignoring = 1;
510 } else {
511 ignoring = 0;
512 /* For colourization we need the complete line, so save it */
513 /* XXX This is all temporary (colour belongs into backend), so
514 * XXX use pipecomm as a temporary storage in the meanwhile */
515 #ifdef HAVE_COLOUR
516 if (colour_table != NULL)
517 pipecomm = savestrbuf(line, PTR2SIZE(cp2 - line));
518 #endif
520 *cp2 = c;
521 dostat &= ~(1 << 2);
522 infld = 1;
525 /* Determine if the end of the line is a MIME encoded word */
526 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
527 * TODO with header follow lines, and it should be up to the backend
528 * TODO what happens and what not, i.e., it doesn't matter wether it's
529 * TODO a MIME-encoded word or not, as long as a single separating space
530 * TODO remains in between lines (the MIME stuff will correctly remove
531 * TODO whitespace in between multiple adjacent encoded words) */
532 isenc &= ~2;
533 if (cnt && (c = getc(ibuf)) != EOF) {
534 if (blankchar(c)) {
535 cp = line + linelen - 1;
536 if (linelen > 0 && *cp == '\n')
537 --cp;
538 while (cp >= line && whitechar(*cp))
539 --cp;
540 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
541 isenc |= 2;
543 ungetc(c, ibuf);
546 if (!ignoring) {
547 size_t len = linelen;
548 start = line;
549 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
550 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
551 action == SEND_TOSRCH) {
552 /* Strip blank characters if two MIME-encoded words follow on
553 * continuing lines */
554 if (isenc & 1)
555 while (len > 0 && blankchar(*start)) {
556 ++start;
557 --len;
559 if (isenc & 2)
560 if (len > 0 && start[len - 1] == '\n')
561 --len;
562 while (len > 0 && blankchar(start[len - 1]))
563 --len;
565 #ifdef HAVE_COLOUR
567 bool_t colour_stripped = FAL0;
568 if (pipecomm != NULL) {
569 colour_put_header(obuf, pipecomm);
570 if (len > 0 && start[len - 1] == '\n') {
571 colour_stripped = TRU1;
572 --len;
575 #endif
576 _out(start, len, obuf, convert, action, qf, stats, NULL);
577 #ifdef HAVE_COLOUR
578 if (pipecomm != NULL) {
579 colour_reset(obuf); /* XXX reset after \n!! */
580 if (colour_stripped)
581 putc('\n', obuf);
584 #endif
585 if (ferror(obuf)) {
586 free(line);
587 rv = -1;
588 goto jleave;
592 quoteflt_flush(qf);
593 free(line);
594 line = NULL;
595 pipecomm = NULL;
597 jskip:
598 switch (ip->m_mimecontent) {
599 case MIME_822:
600 switch (action) {
601 case SEND_TODISP:
602 case SEND_TODISP_ALL:
603 case SEND_QUOTE:
604 case SEND_QUOTE_ALL:
605 if (ok_blook(rfc822_body_from_)) {
606 if (qf->qf_pfix_len > 0) {
607 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
608 qf->qf_pfix_len, obuf);
609 if (i == qf->qf_pfix_len && stats != NULL)
610 *stats += i;
612 put_from_(obuf, ip->m_multipart, stats);
614 /* FALLTHRU */
615 case SEND_TOSRCH:
616 case SEND_DECRYPT:
617 goto jmulti;
618 case SEND_TOFILE:
619 case SEND_TOPIPE:
620 if (ok_blook(rfc822_body_from_))
621 put_from_(obuf, ip->m_multipart, stats);
622 /* FALLTHRU */
623 case SEND_MBOX:
624 case SEND_RFC822:
625 case SEND_SHOW:
626 break;
628 break;
629 case MIME_TEXT_HTML:
630 case MIME_TEXT:
631 case MIME_TEXT_PLAIN:
632 switch (action) {
633 case SEND_TODISP:
634 case SEND_TODISP_ALL:
635 case SEND_QUOTE:
636 case SEND_QUOTE_ALL:
637 ispipe = TRU1;
638 switch (_pipecmd(&pipecomm, ip)) {
639 case PIPE_MSG:
640 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
641 stats, NULL);
642 /* We would print this as plain text, so better force going home */
643 goto jleave;
644 case PIPE_TEXT:
645 case PIPE_COMM:
646 case PIPE_NULL:
647 break;
648 case PIPE_ASYNC:
649 ispipe = FAL0;
650 break;
652 /* FALLTRHU */
653 default:
654 break;
656 break;
657 case MIME_DISCARD:
658 if (action != SEND_DECRYPT)
659 goto jleave;
660 break;
661 case MIME_PKCS7:
662 if (action != SEND_MBOX && action != SEND_RFC822 &&
663 action != SEND_SHOW && ip->m_multipart != NULL)
664 goto jmulti;
665 /* FALLTHRU */
666 default:
667 switch (action) {
668 case SEND_TODISP:
669 case SEND_TODISP_ALL:
670 case SEND_QUOTE:
671 case SEND_QUOTE_ALL:
672 ispipe = TRU1;
673 switch (_pipecmd(&pipecomm, ip)) {
674 case PIPE_MSG:
675 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
676 stats, NULL);
677 pipecomm = NULL;
678 break;
679 case PIPE_ASYNC:
680 ispipe = FAL0;
681 /* FALLTHRU */
682 case PIPE_COMM:
683 case PIPE_NULL:
684 break;
685 case PIPE_TEXT:
686 goto jcopyout; /* break; break; */
688 if (pipecomm != NULL)
689 break;
690 if (level == 0 && cnt) {
691 char const *x = _("[Binary content]\n");
692 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
694 goto jleave;
695 case SEND_TOFILE:
696 case SEND_TOPIPE:
697 case SEND_TOSRCH:
698 case SEND_DECRYPT:
699 case SEND_MBOX:
700 case SEND_RFC822:
701 case SEND_SHOW:
702 break;
704 break;
705 case MIME_ALTERNATIVE:
706 case MIME_RELATED:
707 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
708 !ok_blook(print_alternatives)) {
709 /* XXX This (a) should not remain (b) should be own fun */
710 struct mpstack {
711 struct mpstack *outer;
712 struct mimepart *mp;
713 } outermost, * volatile curr = &outermost, * volatile mpsp;
714 sighandler_type volatile opsh, oish, ohsh;
715 size_t volatile partcnt = 0/* silence CC */;
716 bool_t volatile neednl = FAL0;
718 curr->outer = NULL;
719 curr->mp = ip;
721 __sndalter_sig = 0;
722 opsh = safe_signal(SIGPIPE, &__sndalter_onsig);
723 oish = safe_signal(SIGINT, &__sndalter_onsig);
724 ohsh = safe_signal(SIGHUP, &__sndalter_onsig);
725 if (sigsetjmp(__sndalter_actjmp, 1)) {
726 rv = -1;
727 goto jalter_unroll;
730 for (np = ip->m_multipart;;) {
731 partcnt = 0;
732 jalter_redo:
733 for (; np != NULL; np = np->m_nextpart) {
734 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
735 if (neednl)
736 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
737 _print_part_info(obuf, np, doign, level, qf, stats);
739 neednl = TRU1;
741 switch (np->m_mimecontent) {
742 case MIME_ALTERNATIVE:
743 case MIME_RELATED:
744 case MIME_MULTI:
745 case MIME_DIGEST:
746 mpsp = ac_alloc(sizeof *mpsp);
747 mpsp->outer = curr;
748 mpsp->mp = np->m_multipart;
749 curr->mp = np;
750 curr = mpsp;
751 np = mpsp->mp;
752 neednl = FAL0;
753 goto jalter_redo;
754 default:
755 switch (_pipecmd(&pipecomm, np)) {
756 default:
757 continue;
758 case PIPE_TEXT:
759 break;
761 /* FALLTHRU */
762 case MIME_TEXT_PLAIN:
763 ++partcnt;
764 if (action == SEND_QUOTE && partcnt > 1 &&
765 ip->m_mimecontent == MIME_ALTERNATIVE)
766 break;
767 quoteflt_flush(qf);
768 if (action == SEND_QUOTE && partcnt > 1) {
769 struct quoteflt *dummy = quoteflt_dummy();
770 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
771 NULL);
772 quoteflt_flush(dummy);
774 neednl = FAL0;
775 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
776 level + 1);
777 quoteflt_reset(qf, origobuf);
779 if (rv < 0) {
780 jalter_unroll:
781 for (;; curr = mpsp) {
782 if ((mpsp = curr->outer) == NULL)
783 break;
784 ac_free(curr);
787 break;
791 mpsp = curr->outer;
792 if (mpsp == NULL)
793 break;
794 ac_free(curr);
795 curr = mpsp;
796 np = curr->mp->m_nextpart;
798 safe_signal(SIGHUP, ohsh);
799 safe_signal(SIGINT, oish);
800 safe_signal(SIGPIPE, opsh);
801 if (__sndalter_sig != 0)
802 n_raise(__sndalter_sig);
803 goto jleave;
805 /* FALLTHRU */
806 case MIME_MULTI:
807 case MIME_DIGEST:
808 switch (action) {
809 case SEND_TODISP:
810 case SEND_TODISP_ALL:
811 case SEND_QUOTE:
812 case SEND_QUOTE_ALL:
813 case SEND_TOFILE:
814 case SEND_TOPIPE:
815 case SEND_TOSRCH:
816 case SEND_DECRYPT:
817 jmulti:
818 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
819 ip->m_multipart != NULL &&
820 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
821 ip->m_multipart->m_nextpart == NULL) {
822 char const *x = _("[Missing multipart boundary - use \"show\" "
823 "to display the raw message]\n");
824 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
827 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
828 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
829 continue;
830 ispipe = FAL0;
831 switch (action) {
832 case SEND_TOFILE:
833 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
834 break;
835 stats = NULL;
836 /* TODO Always open multipart on /dev/null, it's a hack to be
837 * TODO able to dive into that structure, and still better
838 * TODO than asking the user for something stupid.
839 * TODO oh, wait, we did ask for a filename for this MIME mail,
840 * TODO and that outer container is useless anyway ;-P */
841 if (np->m_multipart != NULL) {
842 if ((obuf = Fopen("/dev/null", "w")) == NULL)
843 continue;
844 } else if ((obuf = newfile(np, UNVOLATILE(&ispipe))) == NULL)
845 continue;
846 if (!ispipe)
847 break;
848 if (sigsetjmp(_send_pipejmp, 1)) {
849 rv = -1;
850 goto jpipe_close;
852 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
853 break;
854 case SEND_TODISP:
855 case SEND_TODISP_ALL:
856 case SEND_QUOTE_ALL:
857 if (ip->m_mimecontent != MIME_MULTI &&
858 ip->m_mimecontent != MIME_ALTERNATIVE &&
859 ip->m_mimecontent != MIME_RELATED &&
860 ip->m_mimecontent != MIME_DIGEST)
861 break;
862 _print_part_info(obuf, np, doign, level, qf, stats);
863 break;
864 case SEND_MBOX:
865 case SEND_RFC822:
866 case SEND_SHOW:
867 case SEND_TOSRCH:
868 case SEND_QUOTE:
869 case SEND_DECRYPT:
870 case SEND_TOPIPE:
871 break;
874 quoteflt_flush(qf);
875 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
876 rv = -1;
877 quoteflt_reset(qf, origobuf);
879 if (action == SEND_QUOTE)
880 break;
881 if (action == SEND_TOFILE && obuf != origobuf) {
882 if (!ispipe)
883 Fclose(obuf);
884 else {
885 jpipe_close:
886 safe_signal(SIGPIPE, SIG_IGN);
887 Pclose(obuf, TRU1);
888 safe_signal(SIGPIPE, oldpipe);
892 goto jleave;
893 case SEND_MBOX:
894 case SEND_RFC822:
895 case SEND_SHOW:
896 break;
900 /* Copy out message body */
901 jcopyout:
902 if (doign == allignore && level == 0) /* skip final blank line */
903 --cnt;
904 switch (ip->m_mime_enc) {
905 case MIMEE_BIN:
906 case MIMEE_7B:
907 case MIMEE_8B:
908 convert = CONV_NONE;
909 break;
910 case MIMEE_QP:
911 convert = CONV_FROMQP;
912 break;
913 case MIMEE_B64:
914 switch (ip->m_mimecontent) {
915 case MIME_TEXT:
916 case MIME_TEXT_PLAIN:
917 case MIME_TEXT_HTML:
918 convert = CONV_FROMB64_T;
919 break;
920 default:
921 convert = CONV_FROMB64;
923 break;
924 default:
925 convert = CONV_NONE;
928 if (action == SEND_DECRYPT || action == SEND_MBOX ||
929 action == SEND_RFC822 || action == SEND_SHOW)
930 convert = CONV_NONE;
931 #ifdef HAVE_ICONV
932 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
933 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
934 action == SEND_TOSRCH) &&
935 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
936 ip->m_mimecontent == MIME_TEXT_HTML ||
937 ip->m_mimecontent == MIME_TEXT)) {
938 char const *tcs = charset_get_lc();
940 if (iconvd != (iconv_t)-1)
941 n_iconv_close(iconvd);
942 /* TODO Since Base64 has an odd 4:3 relation in between input
943 * TODO and output an input line may end with a partial
944 * TODO multibyte character; this is no problem at all unless
945 * TODO we send to the display or whatever, i.e., ensure
946 * TODO makeprint() or something; to avoid this trap, *force*
947 * TODO iconv(), in which case this layer will handle leftovers
948 * TODO correctly */
949 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
950 asccasecmp(charset_get_7bit(), ip->m_charset))) {
951 iconvd = n_iconv_open(tcs, ip->m_charset);
953 * TODO errors should DEFINETELY not be scrolled away!
954 * TODO what about an error buffer (think old shsp(1)),
955 * TODO re-dump errors since last snapshot when the
956 * TODO command loop enters again? i.e., at least print
957 * TODO "There were errors ?" before the next prompt,
958 * TODO so that the user can look at the error buffer?
960 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
961 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
962 /*rv = 1; goto jleave;*/
966 #endif
968 if (pipecomm != NULL && (action == SEND_TODISP ||
969 action == SEND_TODISP_ALL || action == SEND_QUOTE ||
970 action == SEND_QUOTE_ALL)) {
971 qbuf = obuf;
972 pbuf = _pipefile(pipecomm, ip, UNVOLATILE(&qbuf),
973 (action == SEND_QUOTE || action == SEND_QUOTE_ALL), !ispipe);
974 if (pbuf == NULL) {
975 #ifdef HAVE_ICONV
976 if (iconvd != (iconv_t)-1)
977 n_iconv_close(iconvd);
978 #endif
979 rv = -1;
980 goto jleave;
982 action = SEND_TOPIPE;
983 if (pbuf != qbuf) {
984 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
985 if (sigsetjmp(_send_pipejmp, 1))
986 goto jend;
988 } else
989 pbuf = qbuf = obuf;
992 bool_t volatile eof;
993 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
994 ui64_t *save_stats = stats;
996 if (pbuf != origobuf) {
997 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
998 stats = NULL;
1000 eof = FAL0;
1001 rest.s = NULL;
1002 rest.l = 0;
1004 if (pbuf == qbuf) {
1005 __sendp_sig = 0;
1006 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1007 if (sigsetjmp(__sendp_actjmp, 1)) {
1008 if (rest.s != NULL)
1009 free(rest.s);
1010 free(line);
1011 #ifdef HAVE_ICONV
1012 if (iconvd != (iconv_t)-1)
1013 n_iconv_close(iconvd);
1014 #endif
1015 safe_signal(SIGPIPE, __sendp_opipe);
1016 n_raise(__sendp_sig);
1020 quoteflt_reset(qf, pbuf);
1021 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1022 joutln:
1023 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1024 ferror(pbuf)) {
1025 rv = -1; /* XXX Should bail away?! */
1026 break;
1029 if (!eof && rest.l != 0) {
1030 linelen = 0;
1031 eof = TRU1;
1032 action |= _TD_EOF;
1033 goto joutln;
1035 if (pbuf == qbuf)
1036 safe_signal(SIGPIPE, __sendp_opipe);
1038 quoteflt_flush(qf);
1039 if (rest.s != NULL)
1040 free(rest.s);
1042 if (pbuf != origobuf) {
1043 qf->qf_pfix_len = save_qf_pfix_len;
1044 stats = save_stats;
1048 jend:
1049 if (line != NULL)
1050 free(line);
1051 if (pbuf != qbuf) {
1052 safe_signal(SIGPIPE, SIG_IGN);
1053 Pclose(pbuf, ispipe);
1054 safe_signal(SIGPIPE, oldpipe);
1055 if (qbuf != NULL && qbuf != obuf)
1056 pipecpy(qbuf, obuf, origobuf, qf, stats);
1058 #ifdef HAVE_ICONV
1059 if (iconvd != (iconv_t)-1)
1060 n_iconv_close(iconvd);
1061 #endif
1062 jleave:
1063 NYD_LEAVE;
1064 return rv;
1067 static FILE *
1068 newfile(struct mimepart *ip, int *ispipe)
1070 struct str in, out;
1071 char *f;
1072 FILE *fp;
1073 NYD_ENTER;
1075 f = ip->m_filename;
1076 *ispipe = 0;
1078 if (f != NULL && f != (char*)-1) {
1079 in.s = f;
1080 in.l = strlen(f);
1081 makeprint(&in, &out);
1082 out.l = delctrl(out.s, out.l);
1083 f = savestrbuf(out.s, out.l);
1084 free(out.s);
1087 /* In interactive mode, let user perform all kind of expansions as desired,
1088 * and offer |SHELL-SPEC pipe targets, too */
1089 if (options & OPT_INTERACTIVE) {
1090 struct str prompt;
1091 char *f2, *f3;
1093 /* TODO Generic function which asks for filename.
1094 * TODO If the current part is the first textpart the target
1095 * TODO is implicit from outer `write' etc! */
1096 /* I18N: Filename input prompt with file type indication */
1097 str_concat_csvl(&prompt, _("Enter filename for part "),
1098 (ip->m_partstring != NULL) ? ip->m_partstring : _("?"),
1099 _(" ("), ip->m_ct_type_plain, _("): "), NULL);
1100 jgetname:
1101 f2 = n_input_cp_addhist(prompt.s, ((f != (char*)-1 && f != NULL)
1102 ? fexpand_nshell_quote(f) : NULL), TRU1);
1103 if (f2 == NULL || *f2 == '\0') {
1104 if (options & OPT_D_V)
1105 n_err(_("... skipping this\n"));
1106 fp = NULL;
1107 goto jleave;
1108 } else if (*f2 == '|')
1109 /* Pipes are expanded by the shell */
1110 f = f2;
1111 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NSHELL)) == NULL)
1112 /* (Error message written by fexpand()) */
1113 goto jgetname;
1114 else
1115 f = f3;
1118 if (f == NULL || f == (char*)-1 || *f == '\0')
1119 fp = NULL;
1120 else if (options & OPT_INTERACTIVE) {
1121 if (*f == '|') {
1122 char const *cp;
1123 cp = ok_vlook(SHELL);
1124 if (cp == NULL)
1125 cp = XSHELL;
1126 fp = Popen(f + 1, "w", cp, NULL, 1);
1127 if (!(*ispipe = (fp != NULL)))
1128 n_perr(f, 0);
1129 } else if ((fp = Fopen(f, "w")) == NULL)
1130 n_err(_("Cannot open \"%s\"\n"), f);
1131 } else {
1132 /* Be very picky in non-interactive mode: actively disallow pipes,
1133 * prevent directory separators, and any filename member that would
1134 * become expanded by the shell if the name would be echo(1)ed */
1135 if(anyof(f, "/" n_SHEXP_MAGIC_PATH_CHARS)){
1136 char c;
1138 for(out.s = salloc((strlen(f) * 3) +1), out.l = 0; (c = *f++) != '\0';)
1139 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
1140 out.s[out.l++] = '%';
1141 mime_char_to_hexseq(&out.s[out.l], c);
1142 out.l += 2;
1143 }else
1144 out.s[out.l++] = c;
1145 out.s[out.l] = '\0';
1146 f = out.s;
1149 /* Avoid overwriting of existing files */
1150 while((fp = Fopen(f, "wx")) == NULL){
1151 int e;
1153 if((e = errno) != EEXIST){
1154 n_err(_("Cannot open \"%s\": %s\n"), f, strerror(e));
1155 break;
1158 if(ip->m_partstring != NULL)
1159 f = savecatsep(f, '#', ip->m_partstring);
1160 else
1161 f = savecat(f, "#.");
1164 jleave:
1165 NYD_LEAVE;
1166 return fp;
1169 static void
1170 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1171 ui64_t *stats)
1173 char *line = NULL; /* TODO line pool */
1174 size_t linesize = 0, linelen, cnt;
1175 ssize_t all_sz, sz;
1176 NYD_ENTER;
1178 fflush(pipebuf);
1179 rewind(pipebuf);
1180 cnt = fsize(pipebuf);
1181 all_sz = 0;
1183 quoteflt_reset(qf, outbuf);
1184 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1185 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1186 break;
1187 all_sz += sz;
1189 if ((sz = quoteflt_flush(qf)) > 0)
1190 all_sz += sz;
1191 if (line)
1192 free(line);
1194 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1195 *stats += all_sz;
1196 Fclose(pipebuf);
1197 NYD_LEAVE;
1200 static void
1201 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1202 ui64_t *stats)
1204 char statout[3], *cp = statout;
1205 NYD_ENTER;
1207 if (mp->m_flag & MREAD)
1208 *cp++ = 'R';
1209 if (!(mp->m_flag & MNEW))
1210 *cp++ = 'O';
1211 *cp = 0;
1212 if (statout[0]) {
1213 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1214 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1215 if (i > 0 && stats != NULL)
1216 *stats += i;
1218 NYD_LEAVE;
1221 static void
1222 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1223 ui64_t *stats)
1225 char xstatout[4];
1226 char *xp = xstatout;
1227 NYD_ENTER;
1229 if (mp->m_flag & MFLAGGED)
1230 *xp++ = 'F';
1231 if (mp->m_flag & MANSWERED)
1232 *xp++ = 'A';
1233 if (mp->m_flag & MDRAFTED)
1234 *xp++ = 'T';
1235 *xp = 0;
1236 if (xstatout[0]) {
1237 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1238 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1239 if (i > 0 && stats != NULL)
1240 *stats += i;
1242 NYD_LEAVE;
1245 static void
1246 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1248 char const *froma, *date, *nl;
1249 int i;
1250 NYD_ENTER;
1252 if (ip != NULL && ip->m_from != NULL) {
1253 froma = ip->m_from;
1254 date = fakedate(ip->m_time);
1255 nl = "\n";
1256 } else {
1257 froma = myname;
1258 date = time_current.tc_ctime;
1259 nl = "";
1262 colour_put(fp, COLOURSPEC_FROM_);
1263 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1264 colour_reset(fp);
1265 if (i > 0 && stats != NULL)
1266 *stats += i;
1267 NYD_LEAVE;
1270 FL int
1271 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1272 char const *prefix, enum sendaction action, ui64_t *stats)
1274 struct quoteflt qf;
1275 size_t cnt, sz, i;
1276 FILE *ibuf;
1277 enum mime_parse_flags mpf;
1278 struct mimepart *ip;
1279 int rv = -1, c;
1280 NYD_ENTER;
1282 time_current_update(&time_current, TRU1);
1284 if (mp == dot && action != SEND_TOSRCH)
1285 pstate |= PS_DID_PRINT_DOT;
1286 if (stats != NULL)
1287 *stats = 0;
1288 quoteflt_init(&qf, prefix);
1290 /* First line is the From_ line, so no headers there to worry about */
1291 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1292 goto jleave;
1294 cnt = mp->m_size;
1295 sz = 0;
1297 struct str const *cpre, *csuf;
1298 #ifdef HAVE_COLOUR
1299 cpre = colour_get(COLOURSPEC_FROM_);
1300 csuf = colour_get(COLOURSPEC_RESET);
1301 #else
1302 cpre = csuf = NULL;
1303 #endif
1304 if (mp->m_flag & MNOFROM) {
1305 if (doign != allignore && doign != fwdignore && action != SEND_RFC822)
1306 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1307 (cpre != NULL ? cpre->s : ""),
1308 (int)qf.qf_pfix_len, (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
1309 fakefrom(mp), fakedate(mp->m_time),
1310 (csuf != NULL ? csuf->s : ""));
1311 } else if (doign != allignore && doign != fwdignore &&
1312 action != SEND_RFC822) {
1313 if (qf.qf_pfix_len > 0) {
1314 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1315 if (i != qf.qf_pfix_len)
1316 goto jleave;
1317 sz += i;
1319 #ifdef HAVE_COLOUR
1320 if (cpre != NULL) {
1321 fputs(cpre->s, obuf);
1322 cpre = (struct str const*)0x1;
1324 #endif
1326 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1327 #ifdef HAVE_COLOUR
1328 if (c == '\n' && csuf != NULL) {
1329 cpre = (struct str const*)0x1;
1330 fputs(csuf->s, obuf);
1332 #endif
1333 putc(c, obuf);
1334 ++sz;
1335 --cnt;
1336 if (c == '\n')
1337 break;
1340 #ifdef HAVE_COLOUR
1341 if (csuf != NULL && cpre != (struct str const*)0x1)
1342 fputs(csuf->s, obuf);
1343 #endif
1344 } else {
1345 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1346 --cnt;
1347 if (c == '\n')
1348 break;
1352 if (sz > 0 && stats != NULL)
1353 *stats += sz;
1355 mpf = MIME_PARSE_NONE;
1356 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1357 mpf |= MIME_PARSE_DECRYPT | MIME_PARSE_PARTS;
1358 if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1359 goto jleave;
1361 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1362 jleave:
1363 quoteflt_destroy(&qf);
1364 NYD_LEAVE;
1365 return rv;
1368 /* s-it-mode */