Add MIME_{SIGNED,ENCRYPTED} for multipart/{signed,encrypted}..
[s-mailx.git] / send.c
blob3b841a24cd0f0552350d188a2c987613f0c26e8d
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message content preparation (sendmp()).
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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 static sigjmp_buf _send_pipejmp;
44 /* Going for user display, print Part: info string */
45 static void _print_part_info(FILE *obuf, struct mimepart const *mpp,
46 struct n_ignore const *doitp, int level,
47 struct quoteflt *qf, ui64_t *stats);
49 /* Create a pipe; if mpp is not NULL, place some NAILENV_* environment
50 * variables accordingly */
51 static FILE * _pipefile(struct mime_handler *mhp,
52 struct mimepart const *mpp, FILE **qbuf,
53 char const *tmpname, int term_infd);
55 /* Call mime_write() as approbiate and adjust statistics */
56 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
57 enum conversion convert, enum sendaction action,
58 struct quoteflt *qf, ui64_t *stats, struct str *outrest,
59 struct str *inrest);
61 /* SIGPIPE handler */
62 static void _send_onpipe(int signo);
64 /* Send one part */
65 static int sendpart(struct message *zmp, struct mimepart *ip,
66 FILE *obuf, struct n_ignore const *doitp,
67 struct quoteflt *qf, enum sendaction action,
68 ui64_t *stats, int level);
70 /* Get a file for an attachment */
71 static FILE * newfile(struct mimepart *ip, bool_t volatile *ispipe);
73 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
74 struct quoteflt *qf, ui64_t *stats);
76 /* Output a reasonable looking status field */
77 static void statusput(const struct message *mp, FILE *obuf,
78 struct quoteflt *qf, ui64_t *stats);
79 static void xstatusput(const struct message *mp, FILE *obuf,
80 struct quoteflt *qf, ui64_t *stats);
82 static void put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats);
84 static void
85 _print_part_info(FILE *obuf, struct mimepart const *mpp, /* TODO strtofmt.. */
86 struct n_ignore const *doitp, int level, struct quoteflt *qf, ui64_t *stats)
88 char buf[64];
89 struct str ti = {NULL, 0}, to;
90 struct str const *cpre, *csuf;
91 char const *cp;
92 NYD2_ENTER;
94 #ifdef HAVE_COLOUR
96 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_PARTINFO,
97 NULL);
98 if ((cpre = n_colour_pen_to_str(cpen)) != NULL)
99 csuf = n_colour_reset_to_str();
100 else
101 csuf = NULL;
103 #else
104 cpre = csuf = NULL;
105 #endif
107 /* Take care of "99.99", i.e., 5 */
108 if ((cp = mpp->m_partstring) == NULL || cp[0] == '\0')
109 cp = "?";
110 if (level || (cp[0] != '1' && cp[1] == '\0'))
111 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
112 if (cpre != NULL)
113 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
114 _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
115 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
117 to.l = snprintf(buf, sizeof buf, " %" PRIuZ "/%" PRIuZ " ",
118 (uiz_t)mpp->m_lines, (uiz_t)mpp->m_size);
119 _out(buf, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
121 if ((cp = mpp->m_ct_type_usr_ovwr) != NULL)
122 _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
123 else
124 cp = mpp->m_ct_type_plain;
125 if ((to.l = strlen(cp)) > 30 && is_asccaseprefix(cp, "application/")) {
126 size_t const al = sizeof("appl../") -1, fl = sizeof("application/") -1;
127 size_t i = to.l - fl;
128 char *x = salloc(al + i +1);
130 memcpy(x, "appl../", al);
131 memcpy(x + al, cp + fl, i +1);
132 cp = x;
133 to.l = al + i;
135 _out(cp, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
137 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_ct_enc) != NULL) {
138 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
139 if (to.l > 25 && !asccasecmp(cp, "quoted-printable"))
140 cp = "qu.-pr.";
141 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
144 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_charset) != NULL) {
145 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
146 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
149 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
150 if (csuf != NULL)
151 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
152 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
154 if (n_ignore_is_ign(doitp, "content-disposition", 19) &&
155 mpp->m_filename != NULL && *mpp->m_filename != '\0') {
156 makeprint(n_str_add_cp(&ti, mpp->m_filename), &to);
157 free(ti.s);
158 to.l = delctrl(to.s, to.l);
160 if (cpre != NULL)
161 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
162 NULL, NULL);
163 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
164 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
165 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
166 if (csuf != NULL)
167 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
168 NULL, NULL);
169 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
171 free(to.s);
173 NYD2_LEAVE;
176 static FILE *
177 _pipefile(struct mime_handler *mhp, struct mimepart const *mpp, FILE **qbuf,
178 char const *tmpname, int term_infd)
180 struct str s;
181 char const *env_addon[8], *cp, *sh;
182 FILE *rbuf;
183 NYD_ENTER;
185 rbuf = *qbuf;
187 if (mhp->mh_flags & MIME_HDL_ISQUOTE) {
188 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
189 NULL) {
190 n_perr(_("tmpfile"), 0);
191 *qbuf = rbuf;
195 if ((mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF) {
196 union {int (*ptf)(void); char const *sh;} u;
198 fflush(*qbuf);
199 if (*qbuf != stdout) /* xxx never? v15: it'll be a filter anyway */
200 fflush(stdout);
202 u.ptf = mhp->mh_ptf;
203 if((rbuf = Popen((char*)-1, "W", u.sh, NULL, fileno(*qbuf))) == NULL)
204 goto jerror;
205 goto jleave;
208 /* NAIL_FILENAME */
209 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
210 cp = n_empty;
211 env_addon[0] = str_concat_csvl(&s, NAILENV_FILENAME, "=", cp, NULL)->s;
213 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
214 * TODO a file wherever he wants! *Do* create a zero-size temporary file
215 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
216 * TODO the pipe returns? Like this we *can* verify path/name issues! */
217 env_addon[1] = str_concat_csvl(&s, NAILENV_FILENAME_GENERATED, "=",
218 getrandstring(n_MIN(NAME_MAX / 4, 16)), NULL)->s;
220 /* NAIL_CONTENT{,_EVIDENCE} */
221 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
222 cp = n_empty;
223 env_addon[2] = str_concat_csvl(&s, NAILENV_CONTENT, "=", cp, NULL)->s;
225 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
226 cp = mpp->m_ct_type_usr_ovwr;
227 env_addon[3] = str_concat_csvl(&s, NAILENV_CONTENT_EVIDENCE, "=", cp,
228 NULL)->s;
230 cp = ok_vlook(TMPDIR);
231 env_addon[4] = str_concat_csvl(&s, NAILENV_TMPDIR, "=", cp, NULL)->s;
232 env_addon[5] = str_concat_csvl(&s, "TMPDIR", "=", cp, NULL)->s;
234 env_addon[6] = NULL;
236 /* NAIL_FILENAME_TEMPORARY? */
237 if (tmpname != NULL) {
238 env_addon[6] = str_concat_csvl(&s, NAILENV_FILENAME_TEMPORARY, "=",
239 tmpname, NULL)->s;
240 env_addon[7] = NULL;
243 sh = ok_vlook(SHELL);
245 if (mhp->mh_flags & MIME_HDL_NEEDSTERM) {
246 sigset_t nset;
247 int pid;
249 sigemptyset(&nset);
250 pid = run_command(sh, &nset, term_infd, COMMAND_FD_PASS, "-c",
251 mhp->mh_shell_cmd, NULL, env_addon);
252 rbuf = (pid < 0) ? NULL : (FILE*)-1;
253 } else {
254 rbuf = Popen(mhp->mh_shell_cmd, "W", sh, env_addon,
255 (mhp->mh_flags & MIME_HDL_ASYNC ? -1 : fileno(*qbuf)));
256 jerror:
257 if (rbuf == NULL)
258 n_err(_("Cannot run MIME type handler: %s: %s\n"),
259 mhp->mh_msg, strerror(errno));
260 else {
261 fflush(*qbuf);
262 if (*qbuf != stdout)
263 fflush(stdout);
266 jleave:
267 NYD_LEAVE;
268 return rbuf;
271 SINLINE ssize_t
272 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
273 sendaction action, struct quoteflt *qf, ui64_t *stats, struct str *outrest,
274 struct str *inrest)
276 ssize_t sz = 0, n;
277 int flags;
278 NYD_ENTER;
280 #if 0
281 Well ... it turns out to not work like that since of course a valid
282 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
283 after an empty line has been seen, which cannot be detected that easily
284 right here!
285 ifdef HAVE_DEBUG /* TODO assert legacy */
286 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
287 * TODO other input situations handle RFC 4155 OR, if newly generated,
288 * TODO enforce quoted-printable if there is From_, as "required" by
289 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
290 * TODO if it may happen in this path, we should just treat decryption
291 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
292 if (action == SEND_MBOX || action == SEND_DECRYPT)
293 assert(!is_head(buf, len, FAL0));
294 #else
295 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
296 is_head(buf, len, FAL0)) {
297 putc('>', fp);
298 ++sz;
300 #endif
302 flags = ((int)action & _TD_EOF);
303 action &= ~_TD_EOF;
304 n = mime_write(buf, len, fp,
305 action == SEND_MBOX ? CONV_NONE : convert,
306 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
307 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
308 ? TD_ISPR | TD_ICONV
309 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
310 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
311 qf, outrest, inrest);
312 if (n < 0)
313 sz = n;
314 else if (n > 0) {
315 sz += n;
316 if (stats != NULL)
317 *stats += sz;
319 NYD_LEAVE;
320 return sz;
323 static void
324 _send_onpipe(int signo)
326 NYD_X; /* Signal handler */
327 n_UNUSED(signo);
328 siglongjmp(_send_pipejmp, 1);
331 static sigjmp_buf __sendp_actjmp; /* TODO someday.. */
332 static int __sendp_sig; /* TODO someday.. */
333 static sighandler_type __sendp_opipe;
334 static void
335 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
337 NYD_X; /* Signal handler */
338 __sendp_sig = sig;
339 siglongjmp(__sendp_actjmp, 1);
342 static int
343 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
344 struct n_ignore const *doitp, struct quoteflt *qf,
345 enum sendaction volatile action, ui64_t * volatile stats, int level)
347 int volatile rv = 0;
348 struct mime_handler mh;
349 struct str outrest, inrest;
350 char *line = NULL, *cp, *cp2, *start;
351 char const * volatile tmpname = NULL;
352 size_t linesize = 0, linelen, cnt;
353 int volatile term_infd;
354 int dostat, c;
355 struct mimepart *volatile np;
356 FILE * volatile ibuf = NULL, * volatile pbuf = obuf,
357 * volatile qbuf = obuf, *origobuf = obuf;
358 enum conversion volatile convert;
359 sighandler_type volatile oldpipe = SIG_DFL;
360 NYD_ENTER;
362 n_UNINIT(term_infd, 0);
363 n_UNINIT(cnt, 0);
365 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
366 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
367 goto jskip;
369 dostat = 0;
370 if (level == 0) {
371 if (doitp != NULL) {
372 if (!n_ignore_is_ign(doitp, "status", 6))
373 dostat |= 1;
374 if (!n_ignore_is_ign(doitp, "x-status", 8))
375 dostat |= 2;
376 } else
377 dostat = 3;
379 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
380 rv = -1;
381 goto jleave;
383 cnt = ip->m_size;
385 if (ip->m_mimecontent == MIME_DISCARD)
386 goto jskip;
388 if (!(ip->m_flag & MNOFROM))
389 while (cnt && (c = getc(ibuf)) != EOF) {
390 cnt--;
391 if (c == '\n')
392 break;
394 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
395 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
396 action == SEND_TOSRCH)
397 ? CONV_FROMHDR : CONV_NONE;
399 /* Work the headers */
400 quoteflt_reset(qf, obuf);
401 /* C99 */{
402 enum {
403 HPS_NONE = 0,
404 HPS_IN_FIELD = 1<<0,
405 HPS_IGNORE = 1<<1,
406 HPS_ISENC_1 = 1<<2,
407 HPS_ISENC_2 = 1<<3
408 } hps = HPS_NONE;
409 size_t lineno = 0;
411 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
412 ++lineno;
413 if (line[0] == '\n') {
414 /* If line is blank, we've reached end of headers, so force out
415 * status: field and note that we are no longer in header fields */
416 if (dostat & 1)
417 statusput(zmp, obuf, qf, stats);
418 if (dostat & 2)
419 xstatusput(zmp, obuf, qf, stats);
420 if (doitp != n_IGNORE_ALL)
421 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
422 break;
425 hps &= ~HPS_ISENC_1;
426 if ((hps & HPS_IN_FIELD) && blankchar(line[0])) {
427 /* If this line is a continuation (SP / HT) of a previous header
428 * field, determine if the start of the line is a MIME encoded word */
429 if (hps & HPS_ISENC_2) {
430 for (cp = line; blankchar(*cp); ++cp)
432 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
433 cp[0] == '=' && cp[1] == '?')
434 hps |= HPS_ISENC_1;
436 } else {
437 /* Pick up the header field if we have one */
438 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
440 cp2 = cp;
441 while (spacechar(*cp))
442 ++cp;
443 if (cp[0] != ':') {
444 if (lineno != 1)
445 n_err(_("Malformed message: headers and body not separated "
446 "(with empty line)\n"));
447 /* Not a header line, force out status: This happens in uucp style
448 * mail where there are no headers at all */
449 if (level == 0 /*&& lineno == 1*/) {
450 if (dostat & 1)
451 statusput(zmp, obuf, qf, stats);
452 if (dostat & 2)
453 xstatusput(zmp, obuf, qf, stats);
455 if (doitp != n_IGNORE_ALL)
456 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL,NULL);
457 break;
460 /* If it is an ignored field and we care about such things, skip it.
461 * Misuse dostat also for another bit xxx use a bitenum + for more */
462 if (ok_blook(keep_content_length))
463 dostat |= 1 << 2;
464 c = *cp2;
465 *cp2 = 0; /* temporarily null terminate */
466 if ((doitp != NULL &&
467 n_ignore_is_ign(doitp, line, PTR2SIZE(cp2 - line))) ||
468 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
469 (!asccasecmp(line, "content-length") ||
470 !asccasecmp(line, "lines"))))
471 hps |= HPS_IGNORE;
472 else if (!asccasecmp(line, "status")) {
473 /* If field is "status," go compute and print real Status: field */
474 if (dostat & 1) {
475 statusput(zmp, obuf, qf, stats);
476 dostat &= ~1;
477 hps |= HPS_IGNORE;
479 } else if (!asccasecmp(line, "x-status")) {
480 /* If field is "status," go compute and print real Status: field */
481 if (dostat & 2) {
482 xstatusput(zmp, obuf, qf, stats);
483 dostat &= ~2;
484 hps |= HPS_IGNORE;
486 } else {
487 hps &= ~HPS_IGNORE;
488 /* For colourization we need the complete line, so save it */
489 /* XXX This is all temporary (colour belongs into backend), so
490 * XXX use tmpname as a temporary storage in the meanwhile */
491 #ifdef HAVE_COLOUR
492 if (pstate & PS_COLOUR_ACTIVE)
493 tmpname = savestrbuf(line, PTR2SIZE(cp2 - line));
494 #endif
496 *cp2 = c;
497 dostat &= ~(1 << 2);
498 hps |= HPS_IN_FIELD;
501 /* Determine if the end of the line is a MIME encoded word */
502 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
503 * TODO with header follow lines, and it should be up to the backend
504 * TODO what happens and what not, i.e., it doesn't matter whether it's
505 * TODO a MIME-encoded word or not, as long as a single separating space
506 * TODO remains in between lines (the MIME stuff will correctly remove
507 * TODO whitespace in between multiple adjacent encoded words) */
508 hps &= ~HPS_ISENC_2;
509 if (cnt && (c = getc(ibuf)) != EOF) {
510 if (blankchar(c)) {
511 cp = line + linelen - 1;
512 if (linelen > 0 && *cp == '\n')
513 --cp;
514 while (cp >= line && whitechar(*cp))
515 --cp;
516 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
517 hps |= HPS_ISENC_2;
519 ungetc(c, ibuf);
522 if (!(hps & HPS_IGNORE)) {
523 size_t len = linelen;
524 start = line;
525 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
526 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
527 action == SEND_TOSRCH) {
528 /* Strip blank characters if two MIME-encoded words follow on
529 * continuing lines */
530 if (hps & HPS_ISENC_1)
531 while (len > 0 && blankchar(*start)) {
532 ++start;
533 --len;
535 if (hps & HPS_ISENC_2)
536 if (len > 0 && start[len - 1] == '\n')
537 --len;
538 while (len > 0 && blankchar(start[len - 1]))
539 --len;
541 #ifdef HAVE_COLOUR
543 bool_t colour_stripped = FAL0;
544 if (tmpname != NULL) {
545 n_colour_put(obuf, n_COLOUR_ID_VIEW_HEADER, tmpname);
546 if (len > 0 && start[len - 1] == '\n') {
547 colour_stripped = TRU1;
548 --len;
551 #endif
552 _out(start, len, obuf, convert, action, qf, stats, NULL,NULL);
553 #ifdef HAVE_COLOUR
554 if (tmpname != NULL) {
555 n_colour_reset(obuf);
556 if (colour_stripped)
557 putc('\n', obuf);
560 #endif
561 if (ferror(obuf)) {
562 free(line);
563 rv = -1;
564 goto jleave;
568 } /* C99 */
569 quoteflt_flush(qf);
570 free(line);
571 line = NULL;
572 tmpname = NULL;
574 jskip:
575 memset(&mh, 0, sizeof mh);
577 switch (ip->m_mimecontent) {
578 case MIME_822:
579 switch (action) {
580 case SEND_TODISP:
581 case SEND_TODISP_ALL:
582 case SEND_QUOTE:
583 case SEND_QUOTE_ALL:
584 if (ok_blook(rfc822_body_from_)) {
585 if (qf->qf_pfix_len > 0) {
586 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
587 qf->qf_pfix_len, obuf);
588 if (i == qf->qf_pfix_len && stats != NULL)
589 *stats += i;
591 put_from_(obuf, ip->m_multipart, stats);
593 /* FALLTHRU */
594 case SEND_TOSRCH:
595 case SEND_DECRYPT:
596 goto jmulti;
597 case SEND_TOFILE:
598 case SEND_TOPIPE:
599 if (ok_blook(rfc822_body_from_))
600 put_from_(obuf, ip->m_multipart, stats);
601 /* FALLTHRU */
602 case SEND_MBOX:
603 case SEND_RFC822:
604 case SEND_SHOW:
605 break;
607 break;
608 case MIME_TEXT_HTML:
609 case MIME_TEXT:
610 case MIME_TEXT_PLAIN:
611 switch (action) {
612 case SEND_TODISP:
613 case SEND_TODISP_ALL:
614 case SEND_QUOTE:
615 case SEND_QUOTE_ALL:
616 switch (mime_type_handler(&mh, ip, action)) {
617 case MIME_HDL_MSG:
618 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
619 stats, NULL, NULL);
620 /* We would print this as plain text, so better force going home */
621 goto jleave;
622 default:
623 break;
625 /* FALLTRHU */
626 default:
627 break;
629 break;
630 case MIME_DISCARD:
631 if (action != SEND_DECRYPT)
632 goto jleave;
633 break;
634 case MIME_PKCS7:
635 if (action != SEND_MBOX && action != SEND_RFC822 &&
636 action != SEND_SHOW && ip->m_multipart != NULL)
637 goto jmulti;
638 /* FALLTHRU */
639 default:
640 switch (action) {
641 case SEND_TODISP:
642 case SEND_TODISP_ALL:
643 case SEND_QUOTE:
644 case SEND_QUOTE_ALL:
645 switch (mime_type_handler(&mh, ip, action)) {
646 case MIME_HDL_MSG:
647 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
648 stats, NULL, NULL);
649 /* We would print this as plain text, so better force going home */
650 goto jleave;
651 case MIME_HDL_CMD:
652 /* FIXME WE NEED TO DO THAT IF WE ARE THE ONLY MAIL
653 * FIXME CONTENT !! */
654 case MIME_HDL_TEXT:
655 break;
656 default:
657 case MIME_HDL_NULL:
658 if (level == 0 && cnt) {
659 char const *x = _("[-- Binary content --]\n");
660 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
661 NULL,NULL);
663 goto jleave;
665 break;
666 case SEND_TOFILE:
667 case SEND_TOPIPE:
668 case SEND_TOSRCH:
669 case SEND_DECRYPT:
670 case SEND_MBOX:
671 case SEND_RFC822:
672 case SEND_SHOW:
673 break;
675 break;
676 case MIME_ALTERNATIVE:
677 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
678 !ok_blook(print_alternatives)) {
679 /* XXX This (a) should not remain (b) should be own fun
680 * TODO (despite the fact that v15 will do this completely differently
681 * TODO by having an action-specific "manager" that will traverse the
682 * TODO parsed MIME tree and decide for each part whether it'll be
683 * TODO displayed or not *before* we walk the tree for doing action */
684 struct mpstack {
685 struct mpstack *outer;
686 struct mimepart *mp;
687 } outermost, * volatile curr, * volatile mpsp;
688 bool_t volatile neednl, hadpart;
689 struct n_sigman smalter;
691 (curr = &outermost)->outer = NULL;
692 curr->mp = ip;
693 neednl = hadpart = FAL0;
695 n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
696 case 0:
697 break;
698 default:
699 rv = -1;
700 goto jalter_leave;
703 for (np = ip->m_multipart;;) {
704 jalter_redo:
705 for (; np != NULL; np = np->m_nextpart) {
706 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
707 if (neednl)
708 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats,
709 NULL, NULL);
710 _print_part_info(obuf, np, doitp, level, qf, stats);
712 neednl = TRU1;
714 switch (np->m_mimecontent) {
715 case MIME_ALTERNATIVE:
716 case MIME_RELATED:
717 case MIME_DIGEST:
718 case MIME_SIGNED:
719 case MIME_ENCRYPTED:
720 case MIME_MULTI:
721 mpsp = salloc(sizeof *mpsp);
722 mpsp->outer = curr;
723 mpsp->mp = np->m_multipart;
724 curr->mp = np;
725 curr = mpsp;
726 np = mpsp->mp;
727 neednl = FAL0;
728 goto jalter_redo;
729 default:
730 if (hadpart)
731 break;
732 switch (mime_type_handler(&mh, np, action)) {
733 default:
734 mh.mh_flags = MIME_HDL_NULL;
735 continue; /* break; break; */
736 case MIME_HDL_PTF:
737 if (!ok_blook(mime_alternative_favour_rich)) {/* TODO */
738 struct mimepart *x = np;
740 while ((x = x->m_nextpart) != NULL) {
741 struct mime_handler mhx;
743 if (x->m_mimecontent == MIME_TEXT_PLAIN ||
744 mime_type_handler(&mhx, x, action) ==
745 MIME_HDL_TEXT)
746 break;
748 if (x != NULL)
749 continue; /* break; break; */
750 goto jalter_plain;
752 /* FALLTHRU */
753 case MIME_HDL_TEXT:
754 break;
756 /* FALLTHRU */
757 case MIME_TEXT_PLAIN:
758 if (hadpart)
759 break;
760 if (ok_blook(mime_alternative_favour_rich)) { /* TODO */
761 struct mimepart *x = np;
763 /* TODO twice TODO, we should dive into /related and
764 * TODO check whether that has rich parts! */
765 while ((x = x->m_nextpart) != NULL) {
766 struct mime_handler mhx;
768 switch (mime_type_handler(&mhx, x, action)) {
769 case MIME_HDL_PTF:
770 break;
771 default:
772 continue;
774 break;
776 if (x != NULL)
777 continue; /* break; break; */
779 jalter_plain:
780 quoteflt_flush(qf);
781 if (action == SEND_QUOTE && hadpart) {
782 struct quoteflt *dummy = quoteflt_dummy();
783 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
784 NULL,NULL);
785 quoteflt_flush(dummy);
787 hadpart = TRU1;
788 neednl = FAL0;
789 rv = sendpart(zmp, np, obuf, doitp, qf, action, stats,
790 level + 1);
791 quoteflt_reset(qf, origobuf);
793 if (rv < 0)
794 curr = &outermost; /* Cause overall loop termination */
795 break;
799 mpsp = curr->outer;
800 if (mpsp == NULL)
801 break;
802 curr = mpsp;
803 np = curr->mp->m_nextpart;
805 jalter_leave:
806 n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
807 goto jleave;
809 /* FALLTHRU */
810 case MIME_RELATED:
811 case MIME_DIGEST:
812 case MIME_SIGNED:
813 case MIME_ENCRYPTED:
814 case MIME_MULTI:
815 switch (action) {
816 case SEND_TODISP:
817 case SEND_TODISP_ALL:
818 case SEND_QUOTE:
819 case SEND_QUOTE_ALL:
820 case SEND_TOFILE:
821 case SEND_TOPIPE:
822 case SEND_TOSRCH:
823 case SEND_DECRYPT:
824 jmulti:
825 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
826 ip->m_multipart != NULL &&
827 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
828 ip->m_multipart->m_nextpart == NULL) {
829 char const *x = _("[Missing multipart boundary - use show "
830 "to display the raw message]\n");
831 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
832 NULL,NULL);
835 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
836 bool_t volatile ispipe;
838 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
839 continue;
841 ispipe = FAL0;
842 switch (action) {
843 case SEND_TOFILE:
844 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
845 break;
846 stats = NULL;
847 /* TODO Always open multipart on /dev/null, it's a hack to be
848 * TODO able to dive into that structure, and still better
849 * TODO than asking the user for something stupid.
850 * TODO oh, wait, we did ask for a filename for this MIME mail,
851 * TODO and that outer container is useless anyway ;-P */
852 if (np->m_multipart != NULL) {
853 if ((obuf = Fopen("/dev/null", "w")) == NULL)
854 continue;
855 } else if ((obuf = newfile(np, &ispipe)) == NULL)
856 continue;
857 if (!ispipe)
858 break;
859 if (sigsetjmp(_send_pipejmp, 1)) {
860 rv = -1;
861 goto jpipe_close;
863 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
864 break;
865 case SEND_TODISP:
866 case SEND_TODISP_ALL:
867 if (ip->m_mimecontent != MIME_ALTERNATIVE &&
868 ip->m_mimecontent != MIME_RELATED &&
869 ip->m_mimecontent != MIME_DIGEST &&
870 ip->m_mimecontent != MIME_SIGNED &&
871 ip->m_mimecontent != MIME_ENCRYPTED &&
872 ip->m_mimecontent != MIME_MULTI)
873 break;
874 _print_part_info(obuf, np, doitp, level, qf, stats);
875 break;
876 case SEND_QUOTE:
877 case SEND_QUOTE_ALL:
878 case SEND_MBOX:
879 case SEND_RFC822:
880 case SEND_SHOW:
881 case SEND_TOSRCH:
882 case SEND_DECRYPT:
883 case SEND_TOPIPE:
884 break;
887 quoteflt_flush(qf);
888 if ((action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
889 np->m_multipart == NULL && ip->m_parent != NULL) {
890 struct quoteflt *dummy = quoteflt_dummy();
891 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
892 NULL,NULL);
893 quoteflt_flush(dummy);
895 if (sendpart(zmp, np, obuf, doitp, qf, action, stats, level+1) < 0)
896 rv = -1;
897 quoteflt_reset(qf, origobuf);
899 if (action == SEND_QUOTE) {
900 if (ip->m_mimecontent != MIME_RELATED)
901 break;
903 if (action == SEND_TOFILE && obuf != origobuf) {
904 if (!ispipe)
905 Fclose(obuf);
906 else {
907 jpipe_close:
908 safe_signal(SIGPIPE, SIG_IGN);
909 Pclose(obuf, TRU1);
910 safe_signal(SIGPIPE, oldpipe);
914 goto jleave;
915 case SEND_MBOX:
916 case SEND_RFC822:
917 case SEND_SHOW:
918 break;
920 break;
923 /* Copy out message body */
924 if (doitp == n_IGNORE_ALL && level == 0) /* skip final blank line */
925 --cnt;
926 switch (ip->m_mime_enc) {
927 case MIMEE_BIN:
928 case MIMEE_7B:
929 case MIMEE_8B:
930 convert = CONV_NONE;
931 break;
932 case MIMEE_QP:
933 convert = CONV_FROMQP;
934 break;
935 case MIMEE_B64:
936 switch (ip->m_mimecontent) {
937 case MIME_TEXT:
938 case MIME_TEXT_PLAIN:
939 case MIME_TEXT_HTML:
940 convert = CONV_FROMB64_T;
941 break;
942 default:
943 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
944 case MIME_HDL_TEXT:
945 case MIME_HDL_PTF:
946 convert = CONV_FROMB64_T;
947 break;
948 default:
949 convert = CONV_FROMB64;
950 break;
952 break;
954 break;
955 default:
956 convert = CONV_NONE;
959 if (action == SEND_DECRYPT || action == SEND_MBOX ||
960 action == SEND_RFC822 || action == SEND_SHOW)
961 convert = CONV_NONE;
962 #ifdef HAVE_ICONV
963 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
964 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
965 action == SEND_TOSRCH) &&
966 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
967 ip->m_mimecontent == MIME_TEXT_HTML ||
968 ip->m_mimecontent == MIME_TEXT ||
969 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_TEXT ||
970 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF)) {
971 char const *tcs = charset_get_lc();
973 if (iconvd != (iconv_t)-1)
974 n_iconv_close(iconvd);
975 /* TODO Since Base64 has an odd 4:3 relation in between input
976 * TODO and output an input line may end with a partial
977 * TODO multibyte character; this is no problem at all unless
978 * TODO we send to the display or whatever, i.e., ensure
979 * TODO makeprint() or something; to avoid this trap, *force*
980 * TODO iconv(), in which case this layer will handle leftovers
981 * TODO correctly. It's a pre-v15 we-have-filters hack */
982 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
983 asccasecmp(charset_get_7bit(), ip->m_charset))) {
984 iconvd = n_iconv_open(tcs, ip->m_charset);
985 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
986 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
987 /*rv = 1; goto jleave;*/
991 #endif
993 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
994 case MIME_HDL_CMD:
995 case MIME_HDL_PTF:
996 tmpname = NULL;
997 qbuf = obuf;
999 term_infd = COMMAND_FD_PASS;
1000 if (mh.mh_flags & (MIME_HDL_TMPF | MIME_HDL_NEEDSTERM)) {
1001 enum oflags of;
1003 of = OF_RDWR | OF_REGISTER;
1004 if (!(mh.mh_flags & MIME_HDL_TMPF)) {
1005 term_infd = 0;
1006 mh.mh_flags |= MIME_HDL_TMPF_FILL;
1007 of |= OF_UNLINK;
1008 } else if (mh.mh_flags & MIME_HDL_TMPF_UNLINK)
1009 of |= OF_REGISTER_UNLINK;
1011 if ((pbuf = Ftmp((mh.mh_flags & MIME_HDL_TMPF ? &cp : NULL),
1012 (mh.mh_flags & MIME_HDL_TMPF_FILL ? "mimehdlfill" : "mimehdl"),
1013 of)) == NULL)
1014 goto jesend;
1016 if (mh.mh_flags & MIME_HDL_TMPF) {
1017 tmpname = savestr(cp);
1018 Ftmp_free(&cp);
1021 if (mh.mh_flags & MIME_HDL_TMPF_FILL) {
1022 if (term_infd == 0)
1023 term_infd = fileno(pbuf);
1024 goto jsend;
1028 jpipe_for_real:
1029 pbuf = _pipefile(&mh, ip, n_UNVOLATILE(&qbuf), tmpname, term_infd);
1030 if (pbuf == NULL) {
1031 jesend:
1032 pbuf = qbuf = NULL;
1033 rv = -1;
1034 goto jend;
1035 } else if ((mh.mh_flags & MIME_HDL_NEEDSTERM) && pbuf == (FILE*)-1) {
1036 pbuf = qbuf = NULL;
1037 goto jend;
1039 tmpname = NULL;
1040 action = SEND_TOPIPE;
1041 if (pbuf != qbuf) {
1042 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1043 if (sigsetjmp(_send_pipejmp, 1))
1044 goto jend;
1046 break;
1048 default:
1049 mh.mh_flags = MIME_HDL_NULL;
1050 pbuf = qbuf = obuf;
1051 break;
1054 jsend:
1056 bool_t volatile eof;
1057 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1058 ui64_t *save_stats = stats;
1060 if (pbuf != origobuf) {
1061 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1062 stats = NULL;
1064 eof = FAL0;
1065 outrest.s = inrest.s = NULL;
1066 outrest.l = inrest.l = 0;
1068 if (pbuf == qbuf) {
1069 __sendp_sig = 0;
1070 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1071 if (sigsetjmp(__sendp_actjmp, 1)) {
1072 if (outrest.s != NULL)
1073 free(outrest.s);
1074 if (inrest.s != NULL)
1075 free(inrest.s);
1076 free(line);
1077 #ifdef HAVE_ICONV
1078 if (iconvd != (iconv_t)-1)
1079 n_iconv_close(iconvd);
1080 #endif
1081 safe_signal(SIGPIPE, __sendp_opipe);
1082 n_raise(__sendp_sig);
1086 quoteflt_reset(qf, pbuf);
1087 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1088 joutln:
1089 if (_out(line, linelen, pbuf, convert, action, qf, stats, &outrest,
1090 (action & _TD_EOF ? NULL : &inrest)) < 0 || ferror(pbuf)) {
1091 rv = -1; /* XXX Should bail away?! */
1092 break;
1095 if(eof <= FAL0 && rv >= 0 && (outrest.l != 0 || inrest.l != 0)){
1096 linelen = 0;
1097 if(eof || inrest.l == 0)
1098 action |= _TD_EOF;
1099 eof = eof ? TRU1 : TRUM1;
1100 goto joutln;
1103 /* TODO HACK: when sending to the display we yet get fooled if a message
1104 * TODO doesn't end in a newline, because of our input/output 1:1.
1105 * TODO This should be handled automatically by a display filter, then */
1106 if(rv >= 0 && !qf->qf_nl_last &&
1107 (action == SEND_TODISP || action == SEND_TODISP_ALL))
1108 rv = quoteflt_push(qf, "\n", 1);
1110 quoteflt_flush(qf);
1112 if (rv >= 0 && (mh.mh_flags & MIME_HDL_TMPF_FILL)) {
1113 mh.mh_flags &= ~MIME_HDL_TMPF_FILL;
1114 fflush(pbuf);
1115 really_rewind(pbuf);
1116 /* Don't Fclose() the Ftmp() thing due to OF_REGISTER_UNLINK++ */
1117 goto jpipe_for_real;
1120 if (pbuf == qbuf)
1121 safe_signal(SIGPIPE, __sendp_opipe);
1123 if (outrest.s != NULL)
1124 free(outrest.s);
1125 if (inrest.s != NULL)
1126 free(inrest.s);
1128 if (pbuf != origobuf) {
1129 qf->qf_pfix_len = save_qf_pfix_len;
1130 stats = save_stats;
1134 jend:
1135 if (line != NULL)
1136 free(line);
1137 if (pbuf != qbuf) {
1138 safe_signal(SIGPIPE, SIG_IGN);
1139 Pclose(pbuf, !(mh.mh_flags & MIME_HDL_ASYNC));
1140 safe_signal(SIGPIPE, oldpipe);
1141 if (rv >= 0 && qbuf != NULL && qbuf != obuf)
1142 pipecpy(qbuf, obuf, origobuf, qf, stats);
1144 #ifdef HAVE_ICONV
1145 if (iconvd != (iconv_t)-1)
1146 n_iconv_close(iconvd);
1147 #endif
1148 jleave:
1149 NYD_LEAVE;
1150 return rv;
1153 static FILE *
1154 newfile(struct mimepart *ip, bool_t volatile *ispipe)
1156 struct str in, out;
1157 char *f;
1158 FILE *fp;
1159 NYD_ENTER;
1161 f = ip->m_filename;
1162 *ispipe = FAL0;
1164 if (f != NULL && f != (char*)-1) {
1165 in.s = f;
1166 in.l = strlen(f);
1167 makeprint(&in, &out);
1168 out.l = delctrl(out.s, out.l);
1169 f = savestrbuf(out.s, out.l);
1170 free(out.s);
1173 /* In interactive mode, let user perform all kind of expansions as desired,
1174 * and offer |SHELL-SPEC pipe targets, too */
1175 if (options & OPT_INTERACTIVE) {
1176 struct str prompt;
1177 struct n_string shou, *shoup;
1178 char *f2, *f3;
1180 shoup = n_string_creat_auto(&shou);
1182 /* TODO Generic function which asks for filename.
1183 * TODO If the current part is the first textpart the target
1184 * TODO is implicit from outer `write' etc! */
1185 /* I18N: Filename input prompt with file type indication */
1186 str_concat_csvl(&prompt, _("Enter filename for part "),
1187 (ip->m_partstring != NULL) ? ip->m_partstring : _("?"),
1188 _(" ("), ip->m_ct_type_plain, _("): "), NULL);
1189 jgetname:
1190 f2 = n_lex_input_cp(n_LEXINPUT_CTX_DEFAULT | n_LEXINPUT_HIST_ADD,
1191 prompt.s, ((f != (char*)-1 && f != NULL)
1192 ? n_shexp_quote_cp(f, FAL0) : NULL));
1193 if(f2 != NULL){
1194 in.s = n_UNCONST(f2);
1195 in.l = UIZ_MAX;
1196 if((n_shexp_parse_token(shoup, &in, n_SHEXP_PARSE_TRUNC |
1197 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
1198 n_SHEXP_PARSE_IGNORE_EMPTY) &
1199 (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT |
1200 n_SHEXP_STATE_ERR_MASK)
1201 ) != (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT))
1202 goto jgetname;
1203 f2 = n_string_cp(shoup);
1205 if (f2 == NULL || *f2 == '\0') {
1206 if (options & OPT_D_V)
1207 n_err(_("... skipping this\n"));
1208 n_string_gut(shoup);
1209 fp = NULL;
1210 goto jleave;
1213 if (*f2 == '|')
1214 /* Pipes are expanded by the shell */
1215 f = f2;
1216 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1217 /* (Error message written by fexpand()) */
1218 goto jgetname;
1219 else
1220 f = f3;
1222 n_string_gut(shoup);
1225 if (f == NULL || f == (char*)-1 || *f == '\0')
1226 fp = NULL;
1227 else if (options & OPT_INTERACTIVE) {
1228 if (*f == '|') {
1229 fp = Popen(&f[1], "w", ok_vlook(SHELL), NULL, 1);
1230 if (!(*ispipe = (fp != NULL)))
1231 n_perr(f, 0);
1232 } else if ((fp = Fopen(f, "w")) == NULL)
1233 n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
1234 } else {
1235 /* Be very picky in non-interactive mode: actively disallow pipes,
1236 * prevent directory separators, and any filename member that would
1237 * become expanded by the shell if the name would be echo(1)ed */
1238 if(anyof(f, "/" n_SHEXP_MAGIC_PATH_CHARS)){
1239 char c;
1241 for(out.s = salloc((strlen(f) * 3) +1), out.l = 0; (c = *f++) != '\0';)
1242 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
1243 out.s[out.l++] = '%';
1244 n_c_to_hex_base16(&out.s[out.l], c);
1245 out.l += 2;
1246 }else
1247 out.s[out.l++] = c;
1248 out.s[out.l] = '\0';
1249 f = out.s;
1252 /* Avoid overwriting of existing files */
1253 while((fp = Fopen(f, "wx")) == NULL){
1254 int e;
1256 if((e = errno) != EEXIST){
1257 n_err(_("Cannot open %s: %s\n"),
1258 n_shexp_quote_cp(f, FAL0), strerror(e));
1259 break;
1262 if(ip->m_partstring != NULL)
1263 f = savecatsep(f, '#', ip->m_partstring);
1264 else
1265 f = savecat(f, "#.");
1268 jleave:
1269 NYD_LEAVE;
1270 return fp;
1273 static void
1274 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1275 ui64_t *stats)
1277 char *line = NULL; /* TODO line pool */
1278 size_t linesize = 0, linelen, cnt;
1279 ssize_t all_sz, sz;
1280 NYD_ENTER;
1282 fflush(pipebuf);
1283 rewind(pipebuf);
1284 cnt = (size_t)fsize(pipebuf);
1285 all_sz = 0;
1287 quoteflt_reset(qf, outbuf);
1288 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1289 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1290 break;
1291 all_sz += sz;
1293 if ((sz = quoteflt_flush(qf)) > 0)
1294 all_sz += sz;
1295 if (line)
1296 free(line);
1298 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1299 *stats += all_sz;
1300 Fclose(pipebuf);
1301 NYD_LEAVE;
1304 static void
1305 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1306 ui64_t *stats)
1308 char statout[3], *cp = statout;
1309 NYD_ENTER;
1311 if (mp->m_flag & MREAD)
1312 *cp++ = 'R';
1313 if (!(mp->m_flag & MNEW))
1314 *cp++ = 'O';
1315 *cp = 0;
1316 if (statout[0]) {
1317 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1318 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1319 if (i > 0 && stats != NULL)
1320 *stats += i;
1322 NYD_LEAVE;
1325 static void
1326 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1327 ui64_t *stats)
1329 char xstatout[4];
1330 char *xp = xstatout;
1331 NYD_ENTER;
1333 if (mp->m_flag & MFLAGGED)
1334 *xp++ = 'F';
1335 if (mp->m_flag & MANSWERED)
1336 *xp++ = 'A';
1337 if (mp->m_flag & MDRAFTED)
1338 *xp++ = 'T';
1339 *xp = 0;
1340 if (xstatout[0]) {
1341 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1342 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1343 if (i > 0 && stats != NULL)
1344 *stats += i;
1346 NYD_LEAVE;
1349 static void
1350 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1352 char const *froma, *date, *nl;
1353 int i;
1354 NYD_ENTER;
1356 if (ip != NULL && ip->m_from != NULL) {
1357 froma = ip->m_from;
1358 date = fakedate(ip->m_time);
1359 nl = "\n";
1360 } else {
1361 froma = myname;
1362 date = time_current.tc_ctime;
1363 nl = n_empty;
1366 n_COLOUR( n_colour_put(fp, n_COLOUR_ID_VIEW_FROM_, NULL); )
1367 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1368 n_COLOUR( n_colour_reset(fp); )
1369 if (i > 0 && stats != NULL)
1370 *stats += i;
1371 NYD_LEAVE;
1374 FL int
1375 sendmp(struct message *mp, FILE *obuf, struct n_ignore const *doitp,
1376 char const *prefix, enum sendaction action, ui64_t *stats)
1378 struct quoteflt qf;
1379 size_t cnt, sz, i;
1380 FILE *ibuf;
1381 enum mime_parse_flags mpf;
1382 struct mimepart *ip;
1383 int rv = -1, c;
1384 NYD_ENTER;
1386 time_current_update(&time_current, TRU1);
1388 if (mp == dot && action != SEND_TOSRCH)
1389 pstate |= PS_DID_PRINT_DOT;
1390 if (stats != NULL)
1391 *stats = 0;
1392 quoteflt_init(&qf, prefix);
1394 /* First line is the From_ line, so no headers there to worry about */
1395 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1396 goto jleave;
1398 cnt = mp->m_size;
1399 sz = 0;
1401 bool_t nozap;
1402 char const *cpre = n_empty, *csuf = n_empty;
1403 #ifdef HAVE_COLOUR
1404 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_FROM_,NULL);
1405 struct str const *sp = n_colour_pen_to_str(cpen);
1407 if (sp != NULL) {
1408 cpre = sp->s;
1409 sp = n_colour_reset_to_str();
1410 if (sp != NULL)
1411 csuf = sp->s;
1413 #endif
1415 nozap = (doitp != n_IGNORE_ALL && doitp != n_IGNORE_FWD &&
1416 action != SEND_RFC822 &&
1417 !n_ignore_is_ign(doitp, "from_", sizeof("from_") -1));
1418 if (mp->m_flag & MNOFROM) {
1419 if (nozap)
1420 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1421 cpre, (int)qf.qf_pfix_len,
1422 (qf.qf_pfix_len != 0 ? qf.qf_pfix : n_empty), fakefrom(mp),
1423 fakedate(mp->m_time), csuf);
1424 } else if (nozap) {
1425 if (qf.qf_pfix_len > 0) {
1426 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1427 if (i != qf.qf_pfix_len)
1428 goto jleave;
1429 sz += i;
1431 #ifdef HAVE_COLOUR
1432 if (cpre != NULL) {
1433 fputs(cpre, obuf);
1434 cpre = (char const*)0x1;
1436 #endif
1438 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1439 #ifdef HAVE_COLOUR
1440 if (c == '\n' && csuf != NULL) {
1441 cpre = (char const*)0x1;
1442 fputs(csuf, obuf);
1444 #endif
1445 putc(c, obuf);
1446 ++sz;
1447 --cnt;
1448 if (c == '\n')
1449 break;
1452 #ifdef HAVE_COLOUR
1453 if (csuf != NULL && cpre != (char const*)0x1)
1454 fputs(csuf, obuf);
1455 #endif
1456 } else {
1457 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1458 --cnt;
1459 if (c == '\n')
1460 break;
1464 if (sz > 0 && stats != NULL)
1465 *stats += sz;
1467 mpf = MIME_PARSE_NONE;
1468 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1469 mpf |= MIME_PARSE_DECRYPT | MIME_PARSE_PARTS;
1470 if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1471 goto jleave;
1473 rv = sendpart(mp, ip, obuf, doitp, &qf, action, stats, 0);
1474 jleave:
1475 quoteflt_destroy(&qf);
1476 NYD_LEAVE;
1477 return rv;
1480 /* s-it-mode */