More or less completely rewrite mime_enc.c..
[s-mailx.git] / send.c
blob3263bf56048f478813c801f4a5c5f3850c922706
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 ignoretab *doign, 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 ignoretab *doign,
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 ignoretab *doign, 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 (is_ign("content-disposition", 19, doign) && mpp->m_filename != NULL &&
155 *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 = "";
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 = "";
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, NULL, 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 ignoretab *doign, 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 (doign != NULL) {
372 if (!is_ign("status", 6, doign))
373 dostat |= 1;
374 if (!is_ign("x-status", 8, doign))
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 (doign != allignore)
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 (doign != allignore)
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 ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
467 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
468 (!asccasecmp(line, "content-length") ||
469 !asccasecmp(line, "lines"))))
470 hps |= HPS_IGNORE;
471 else if (!asccasecmp(line, "status")) {
472 /* If field is "status," go compute and print real Status: field */
473 if (dostat & 1) {
474 statusput(zmp, obuf, qf, stats);
475 dostat &= ~1;
476 hps |= HPS_IGNORE;
478 } else if (!asccasecmp(line, "x-status")) {
479 /* If field is "status," go compute and print real Status: field */
480 if (dostat & 2) {
481 xstatusput(zmp, obuf, qf, stats);
482 dostat &= ~2;
483 hps |= HPS_IGNORE;
485 } else {
486 hps &= ~HPS_IGNORE;
487 /* For colourization we need the complete line, so save it */
488 /* XXX This is all temporary (colour belongs into backend), so
489 * XXX use tmpname as a temporary storage in the meanwhile */
490 #ifdef HAVE_COLOUR
491 if (pstate & PS_COLOUR_ACTIVE)
492 tmpname = savestrbuf(line, PTR2SIZE(cp2 - line));
493 #endif
495 *cp2 = c;
496 dostat &= ~(1 << 2);
497 hps |= HPS_IN_FIELD;
500 /* Determine if the end of the line is a MIME encoded word */
501 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
502 * TODO with header follow lines, and it should be up to the backend
503 * TODO what happens and what not, i.e., it doesn't matter whether it's
504 * TODO a MIME-encoded word or not, as long as a single separating space
505 * TODO remains in between lines (the MIME stuff will correctly remove
506 * TODO whitespace in between multiple adjacent encoded words) */
507 hps &= ~HPS_ISENC_2;
508 if (cnt && (c = getc(ibuf)) != EOF) {
509 if (blankchar(c)) {
510 cp = line + linelen - 1;
511 if (linelen > 0 && *cp == '\n')
512 --cp;
513 while (cp >= line && whitechar(*cp))
514 --cp;
515 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
516 hps |= HPS_ISENC_2;
518 ungetc(c, ibuf);
521 if (!(hps & HPS_IGNORE)) {
522 size_t len = linelen;
523 start = line;
524 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
525 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
526 action == SEND_TOSRCH) {
527 /* Strip blank characters if two MIME-encoded words follow on
528 * continuing lines */
529 if (hps & HPS_ISENC_1)
530 while (len > 0 && blankchar(*start)) {
531 ++start;
532 --len;
534 if (hps & HPS_ISENC_2)
535 if (len > 0 && start[len - 1] == '\n')
536 --len;
537 while (len > 0 && blankchar(start[len - 1]))
538 --len;
540 #ifdef HAVE_COLOUR
542 bool_t colour_stripped = FAL0;
543 if (tmpname != NULL) {
544 n_colour_put(obuf, n_COLOUR_ID_VIEW_HEADER, tmpname);
545 if (len > 0 && start[len - 1] == '\n') {
546 colour_stripped = TRU1;
547 --len;
550 #endif
551 _out(start, len, obuf, convert, action, qf, stats, NULL,NULL);
552 #ifdef HAVE_COLOUR
553 if (tmpname != NULL) {
554 n_colour_reset(obuf);
555 if (colour_stripped)
556 putc('\n', obuf);
559 #endif
560 if (ferror(obuf)) {
561 free(line);
562 rv = -1;
563 goto jleave;
567 } /* C99 */
568 quoteflt_flush(qf);
569 free(line);
570 line = NULL;
571 tmpname = NULL;
573 jskip:
574 memset(&mh, 0, sizeof mh);
576 switch (ip->m_mimecontent) {
577 case MIME_822:
578 switch (action) {
579 case SEND_TODISP:
580 case SEND_TODISP_ALL:
581 case SEND_QUOTE:
582 case SEND_QUOTE_ALL:
583 if (ok_blook(rfc822_body_from_)) {
584 if (qf->qf_pfix_len > 0) {
585 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
586 qf->qf_pfix_len, obuf);
587 if (i == qf->qf_pfix_len && stats != NULL)
588 *stats += i;
590 put_from_(obuf, ip->m_multipart, stats);
592 /* FALLTHRU */
593 case SEND_TOSRCH:
594 case SEND_DECRYPT:
595 goto jmulti;
596 case SEND_TOFILE:
597 case SEND_TOPIPE:
598 if (ok_blook(rfc822_body_from_))
599 put_from_(obuf, ip->m_multipart, stats);
600 /* FALLTHRU */
601 case SEND_MBOX:
602 case SEND_RFC822:
603 case SEND_SHOW:
604 break;
606 break;
607 case MIME_TEXT_HTML:
608 case MIME_TEXT:
609 case MIME_TEXT_PLAIN:
610 switch (action) {
611 case SEND_TODISP:
612 case SEND_TODISP_ALL:
613 case SEND_QUOTE:
614 case SEND_QUOTE_ALL:
615 switch (mime_type_handler(&mh, ip, action)) {
616 case MIME_HDL_MSG:
617 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
618 stats, NULL, NULL);
619 /* We would print this as plain text, so better force going home */
620 goto jleave;
621 default:
622 break;
624 /* FALLTRHU */
625 default:
626 break;
628 break;
629 case MIME_DISCARD:
630 if (action != SEND_DECRYPT)
631 goto jleave;
632 break;
633 case MIME_PKCS7:
634 if (action != SEND_MBOX && action != SEND_RFC822 &&
635 action != SEND_SHOW && ip->m_multipart != NULL)
636 goto jmulti;
637 /* FALLTHRU */
638 default:
639 switch (action) {
640 case SEND_TODISP:
641 case SEND_TODISP_ALL:
642 case SEND_QUOTE:
643 case SEND_QUOTE_ALL:
644 switch (mime_type_handler(&mh, ip, action)) {
645 case MIME_HDL_MSG:
646 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
647 stats, NULL, NULL);
648 /* We would print this as plain text, so better force going home */
649 goto jleave;
650 case MIME_HDL_CMD:
651 /* FIXME WE NEED TO DO THAT IF WE ARE THE ONLY MAIL
652 * FIXME CONTENT !! */
653 case MIME_HDL_TEXT:
654 break;
655 default:
656 case MIME_HDL_NULL:
657 if (level == 0 && cnt) {
658 char const *x = _("[-- Binary content --]\n");
659 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
660 NULL,NULL);
662 goto jleave;
664 break;
665 case SEND_TOFILE:
666 case SEND_TOPIPE:
667 case SEND_TOSRCH:
668 case SEND_DECRYPT:
669 case SEND_MBOX:
670 case SEND_RFC822:
671 case SEND_SHOW:
672 break;
674 break;
675 case MIME_ALTERNATIVE:
676 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
677 !ok_blook(print_alternatives)) {
678 /* XXX This (a) should not remain (b) should be own fun
679 * TODO (despite the fact that v15 will do this completely differently
680 * TODO by having an action-specific "manager" that will traverse the
681 * TODO parsed MIME tree and decide for each part whether it'll be
682 * TODO displayed or not *before* we walk the tree for doing action */
683 struct mpstack {
684 struct mpstack *outer;
685 struct mimepart *mp;
686 } outermost, * volatile curr, * volatile mpsp;
687 bool_t volatile neednl, hadpart;
688 struct n_sigman smalter;
690 (curr = &outermost)->outer = NULL;
691 curr->mp = ip;
692 neednl = hadpart = FAL0;
694 n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
695 case 0:
696 break;
697 default:
698 rv = -1;
699 goto jalter_leave;
702 for (np = ip->m_multipart;;) {
703 jalter_redo:
704 for (; np != NULL; np = np->m_nextpart) {
705 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
706 if (neednl)
707 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats,
708 NULL, NULL);
709 _print_part_info(obuf, np, doign, level, qf, stats);
711 neednl = TRU1;
713 switch (np->m_mimecontent) {
714 case MIME_ALTERNATIVE:
715 case MIME_RELATED:
716 case MIME_MULTI:
717 case MIME_DIGEST:
718 mpsp = salloc(sizeof *mpsp);
719 mpsp->outer = curr;
720 mpsp->mp = np->m_multipart;
721 curr->mp = np;
722 curr = mpsp;
723 np = mpsp->mp;
724 neednl = FAL0;
725 goto jalter_redo;
726 default:
727 if (hadpart)
728 break;
729 switch (mime_type_handler(&mh, np, action)) {
730 default:
731 mh.mh_flags = MIME_HDL_NULL;
732 continue; /* break; break; */
733 case MIME_HDL_PTF:
734 if (!ok_blook(mime_alternative_favour_rich)) {/* TODO */
735 struct mimepart *x = np;
737 while ((x = x->m_nextpart) != NULL) {
738 struct mime_handler mhx;
740 if (x->m_mimecontent == MIME_TEXT_PLAIN ||
741 mime_type_handler(&mhx, x, action) ==
742 MIME_HDL_TEXT)
743 break;
745 if (x != NULL)
746 continue; /* break; break; */
747 goto jalter_plain;
749 /* FALLTHRU */
750 case MIME_HDL_TEXT:
751 break;
753 /* FALLTHRU */
754 case MIME_TEXT_PLAIN:
755 if (hadpart)
756 break;
757 if (ok_blook(mime_alternative_favour_rich)) { /* TODO */
758 struct mimepart *x = np;
760 /* TODO twice TODO, we should dive into /related and
761 * TODO check whether that has rich parts! */
762 while ((x = x->m_nextpart) != NULL) {
763 struct mime_handler mhx;
765 switch (mime_type_handler(&mhx, x, action)) {
766 case MIME_HDL_PTF:
767 break;
768 default:
769 continue;
771 break;
773 if (x != NULL)
774 continue; /* break; break; */
776 jalter_plain:
777 quoteflt_flush(qf);
778 if (action == SEND_QUOTE && hadpart) {
779 struct quoteflt *dummy = quoteflt_dummy();
780 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
781 NULL,NULL);
782 quoteflt_flush(dummy);
784 hadpart = TRU1;
785 neednl = FAL0;
786 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
787 level + 1);
788 quoteflt_reset(qf, origobuf);
790 if (rv < 0)
791 curr = &outermost; /* Cause overall loop termination */
792 break;
796 mpsp = curr->outer;
797 if (mpsp == NULL)
798 break;
799 curr = mpsp;
800 np = curr->mp->m_nextpart;
802 jalter_leave:
803 n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
804 goto jleave;
806 /* FALLTHRU */
807 case MIME_MULTI:
808 case MIME_DIGEST:
809 case MIME_RELATED:
810 switch (action) {
811 case SEND_TODISP:
812 case SEND_TODISP_ALL:
813 case SEND_QUOTE:
814 case SEND_QUOTE_ALL:
815 case SEND_TOFILE:
816 case SEND_TOPIPE:
817 case SEND_TOSRCH:
818 case SEND_DECRYPT:
819 jmulti:
820 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
821 ip->m_multipart != NULL &&
822 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
823 ip->m_multipart->m_nextpart == NULL) {
824 char const *x = _("[Missing multipart boundary - use show "
825 "to display the raw message]\n");
826 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
827 NULL,NULL);
830 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
831 bool_t volatile ispipe;
833 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
834 continue;
836 ispipe = FAL0;
837 switch (action) {
838 case SEND_TOFILE:
839 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
840 break;
841 stats = NULL;
842 /* TODO Always open multipart on /dev/null, it's a hack to be
843 * TODO able to dive into that structure, and still better
844 * TODO than asking the user for something stupid.
845 * TODO oh, wait, we did ask for a filename for this MIME mail,
846 * TODO and that outer container is useless anyway ;-P */
847 if (np->m_multipart != NULL) {
848 if ((obuf = Fopen("/dev/null", "w")) == NULL)
849 continue;
850 } else if ((obuf = newfile(np, &ispipe)) == NULL)
851 continue;
852 if (!ispipe)
853 break;
854 if (sigsetjmp(_send_pipejmp, 1)) {
855 rv = -1;
856 goto jpipe_close;
858 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
859 break;
860 case SEND_TODISP:
861 case SEND_TODISP_ALL:
862 if (ip->m_mimecontent != MIME_ALTERNATIVE &&
863 ip->m_mimecontent != MIME_RELATED &&
864 ip->m_mimecontent != MIME_DIGEST &&
865 ip->m_mimecontent != MIME_MULTI)
866 break;
867 _print_part_info(obuf, np, doign, level, qf, stats);
868 break;
869 case SEND_QUOTE:
870 case SEND_QUOTE_ALL:
871 case SEND_MBOX:
872 case SEND_RFC822:
873 case SEND_SHOW:
874 case SEND_TOSRCH:
875 case SEND_DECRYPT:
876 case SEND_TOPIPE:
877 break;
880 quoteflt_flush(qf);
881 if ((action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
882 np->m_multipart == NULL && ip->m_parent != NULL) {
883 struct quoteflt *dummy = quoteflt_dummy();
884 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
885 NULL,NULL);
886 quoteflt_flush(dummy);
888 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
889 rv = -1;
890 quoteflt_reset(qf, origobuf);
892 if (action == SEND_QUOTE) {
893 if (ip->m_mimecontent != MIME_RELATED)
894 break;
896 if (action == SEND_TOFILE && obuf != origobuf) {
897 if (!ispipe)
898 Fclose(obuf);
899 else {
900 jpipe_close:
901 safe_signal(SIGPIPE, SIG_IGN);
902 Pclose(obuf, TRU1);
903 safe_signal(SIGPIPE, oldpipe);
907 goto jleave;
908 case SEND_MBOX:
909 case SEND_RFC822:
910 case SEND_SHOW:
911 break;
913 break;
916 /* Copy out message body */
917 if (doign == allignore && level == 0) /* skip final blank line */
918 --cnt;
919 switch (ip->m_mime_enc) {
920 case MIMEE_BIN:
921 case MIMEE_7B:
922 case MIMEE_8B:
923 convert = CONV_NONE;
924 break;
925 case MIMEE_QP:
926 convert = CONV_FROMQP;
927 break;
928 case MIMEE_B64:
929 switch (ip->m_mimecontent) {
930 case MIME_TEXT:
931 case MIME_TEXT_PLAIN:
932 case MIME_TEXT_HTML:
933 convert = CONV_FROMB64_T;
934 break;
935 default:
936 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
937 case MIME_HDL_TEXT:
938 case MIME_HDL_PTF:
939 convert = CONV_FROMB64_T;
940 break;
941 default:
942 convert = CONV_FROMB64;
943 break;
945 break;
947 break;
948 default:
949 convert = CONV_NONE;
952 if (action == SEND_DECRYPT || action == SEND_MBOX ||
953 action == SEND_RFC822 || action == SEND_SHOW)
954 convert = CONV_NONE;
955 #ifdef HAVE_ICONV
956 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
957 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
958 action == SEND_TOSRCH) &&
959 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
960 ip->m_mimecontent == MIME_TEXT_HTML ||
961 ip->m_mimecontent == MIME_TEXT ||
962 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_TEXT ||
963 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF)) {
964 char const *tcs = charset_get_lc();
966 if (iconvd != (iconv_t)-1)
967 n_iconv_close(iconvd);
968 /* TODO Since Base64 has an odd 4:3 relation in between input
969 * TODO and output an input line may end with a partial
970 * TODO multibyte character; this is no problem at all unless
971 * TODO we send to the display or whatever, i.e., ensure
972 * TODO makeprint() or something; to avoid this trap, *force*
973 * TODO iconv(), in which case this layer will handle leftovers
974 * TODO correctly. It's a pre-v15 we-have-filters hack */
975 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
976 asccasecmp(charset_get_7bit(), ip->m_charset))) {
977 iconvd = n_iconv_open(tcs, ip->m_charset);
978 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
979 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
980 /*rv = 1; goto jleave;*/
984 #endif
986 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
987 case MIME_HDL_CMD:
988 case MIME_HDL_PTF:
989 tmpname = NULL;
990 qbuf = obuf;
992 term_infd = COMMAND_FD_PASS;
993 if (mh.mh_flags & (MIME_HDL_TMPF | MIME_HDL_NEEDSTERM)) {
994 enum oflags of;
996 of = OF_RDWR | OF_REGISTER;
997 if (!(mh.mh_flags & MIME_HDL_TMPF)) {
998 term_infd = 0;
999 mh.mh_flags |= MIME_HDL_TMPF_FILL;
1000 of |= OF_UNLINK;
1001 } else if (mh.mh_flags & MIME_HDL_TMPF_UNLINK)
1002 of |= OF_REGISTER_UNLINK;
1004 if ((pbuf = Ftmp((mh.mh_flags & MIME_HDL_TMPF ? &cp : NULL),
1005 (mh.mh_flags & MIME_HDL_TMPF_FILL ? "mimehdlfill" : "mimehdl"),
1006 of)) == NULL)
1007 goto jesend;
1009 if (mh.mh_flags & MIME_HDL_TMPF) {
1010 tmpname = savestr(cp);
1011 Ftmp_free(&cp);
1014 if (mh.mh_flags & MIME_HDL_TMPF_FILL) {
1015 if (term_infd == 0)
1016 term_infd = fileno(pbuf);
1017 goto jsend;
1021 jpipe_for_real:
1022 pbuf = _pipefile(&mh, ip, n_UNVOLATILE(&qbuf), tmpname, term_infd);
1023 if (pbuf == NULL) {
1024 jesend:
1025 pbuf = qbuf = NULL;
1026 rv = -1;
1027 goto jend;
1028 } else if ((mh.mh_flags & MIME_HDL_NEEDSTERM) && pbuf == (FILE*)-1) {
1029 pbuf = qbuf = NULL;
1030 goto jend;
1032 tmpname = NULL;
1033 action = SEND_TOPIPE;
1034 if (pbuf != qbuf) {
1035 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1036 if (sigsetjmp(_send_pipejmp, 1))
1037 goto jend;
1039 break;
1041 default:
1042 mh.mh_flags = MIME_HDL_NULL;
1043 pbuf = qbuf = obuf;
1044 break;
1047 jsend:
1049 bool_t volatile eof;
1050 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1051 ui64_t *save_stats = stats;
1053 if (pbuf != origobuf) {
1054 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1055 stats = NULL;
1057 eof = FAL0;
1058 outrest.s = inrest.s = NULL;
1059 outrest.l = inrest.l = 0;
1061 if (pbuf == qbuf) {
1062 __sendp_sig = 0;
1063 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1064 if (sigsetjmp(__sendp_actjmp, 1)) {
1065 if (outrest.s != NULL)
1066 free(outrest.s);
1067 if (inrest.s != NULL)
1068 free(inrest.s);
1069 free(line);
1070 #ifdef HAVE_ICONV
1071 if (iconvd != (iconv_t)-1)
1072 n_iconv_close(iconvd);
1073 #endif
1074 safe_signal(SIGPIPE, __sendp_opipe);
1075 n_raise(__sendp_sig);
1079 quoteflt_reset(qf, pbuf);
1080 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1081 joutln:
1082 if (_out(line, linelen, pbuf, convert, action, qf, stats, &outrest,
1083 (action & _TD_EOF ? NULL : &inrest)) < 0 || ferror(pbuf)) {
1084 rv = -1; /* XXX Should bail away?! */
1085 break;
1088 if(eof <= FAL0 && rv >= 0 && (outrest.l != 0 || inrest.l != 0)){
1089 linelen = 0;
1090 if(eof || inrest.l == 0)
1091 action |= _TD_EOF;
1092 eof = eof ? TRU1 : TRUM1;
1093 goto joutln;
1096 /* TODO HACK: when sending to the display we yet get fooled if a message
1097 * TODO doesn't end in a newline, because of our input/output 1:1.
1098 * TODO This should be handled automatically by a display filter, then */
1099 if(rv >= 0 && !qf->qf_nl_last &&
1100 (action == SEND_TODISP || action == SEND_TODISP_ALL))
1101 rv = quoteflt_push(qf, "\n", 1);
1103 quoteflt_flush(qf);
1105 if (rv >= 0 && (mh.mh_flags & MIME_HDL_TMPF_FILL)) {
1106 mh.mh_flags &= ~MIME_HDL_TMPF_FILL;
1107 fflush(pbuf);
1108 really_rewind(pbuf);
1109 /* Don't Fclose() the Ftmp() thing due to OF_REGISTER_UNLINK++ */
1110 goto jpipe_for_real;
1113 if (pbuf == qbuf)
1114 safe_signal(SIGPIPE, __sendp_opipe);
1116 if (outrest.s != NULL)
1117 free(outrest.s);
1118 if (inrest.s != NULL)
1119 free(inrest.s);
1121 if (pbuf != origobuf) {
1122 qf->qf_pfix_len = save_qf_pfix_len;
1123 stats = save_stats;
1127 jend:
1128 if (line != NULL)
1129 free(line);
1130 if (pbuf != qbuf) {
1131 safe_signal(SIGPIPE, SIG_IGN);
1132 Pclose(pbuf, !(mh.mh_flags & MIME_HDL_ASYNC));
1133 safe_signal(SIGPIPE, oldpipe);
1134 if (rv >= 0 && qbuf != NULL && qbuf != obuf)
1135 pipecpy(qbuf, obuf, origobuf, qf, stats);
1137 #ifdef HAVE_ICONV
1138 if (iconvd != (iconv_t)-1)
1139 n_iconv_close(iconvd);
1140 #endif
1141 jleave:
1142 NYD_LEAVE;
1143 return rv;
1146 static FILE *
1147 newfile(struct mimepart *ip, bool_t volatile *ispipe)
1149 struct str in, out;
1150 char *f;
1151 FILE *fp;
1152 NYD_ENTER;
1154 f = ip->m_filename;
1155 *ispipe = FAL0;
1157 if (f != NULL && f != (char*)-1) {
1158 in.s = f;
1159 in.l = strlen(f);
1160 makeprint(&in, &out);
1161 out.l = delctrl(out.s, out.l);
1162 f = savestrbuf(out.s, out.l);
1163 free(out.s);
1166 /* In interactive mode, let user perform all kind of expansions as desired,
1167 * and offer |SHELL-SPEC pipe targets, too */
1168 if (options & OPT_INTERACTIVE) {
1169 struct str prompt;
1170 struct n_string shou, *shoup;
1171 char *f2, *f3;
1173 shoup = n_string_creat_auto(&shou);
1175 /* TODO Generic function which asks for filename.
1176 * TODO If the current part is the first textpart the target
1177 * TODO is implicit from outer `write' etc! */
1178 /* I18N: Filename input prompt with file type indication */
1179 str_concat_csvl(&prompt, _("Enter filename for part "),
1180 (ip->m_partstring != NULL) ? ip->m_partstring : _("?"),
1181 _(" ("), ip->m_ct_type_plain, _("): "), NULL);
1182 jgetname:
1183 f2 = n_lex_input_cp((n_LEXINPUT_CTX_BASE | n_LEXINPUT_HIST_ADD),
1184 prompt.s, ((f != (char*)-1 && f != NULL)
1185 ? n_shexp_quote_cp(f, FAL0) : NULL));
1186 if(f2 != NULL){
1187 in.s = n_UNCONST(f2);
1188 in.l = UIZ_MAX;
1189 if((n_shexp_parse_token(shoup, &in, n_SHEXP_PARSE_TRUNC |
1190 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
1191 n_SHEXP_PARSE_IGNORE_EMPTY) &
1192 (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) !=
1193 n_SHEXP_STATE_OUTPUT)
1194 goto jgetname;
1195 if(in.l != 0)
1196 goto jgetname;
1197 f2 = n_string_cp(shoup);
1199 if (f2 == NULL || *f2 == '\0') {
1200 if (options & OPT_D_V)
1201 n_err(_("... skipping this\n"));
1202 n_string_gut(shoup);
1203 fp = NULL;
1204 goto jleave;
1207 if (*f2 == '|')
1208 /* Pipes are expanded by the shell */
1209 f = f2;
1210 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1211 /* (Error message written by fexpand()) */
1212 goto jgetname;
1213 else
1214 f = f3;
1216 n_string_gut(shoup);
1219 if (f == NULL || f == (char*)-1 || *f == '\0')
1220 fp = NULL;
1221 else if (options & OPT_INTERACTIVE) {
1222 if (*f == '|') {
1223 fp = Popen(&f[1], "w", ok_vlook(SHELL), NULL, 1);
1224 if (!(*ispipe = (fp != NULL)))
1225 n_perr(f, 0);
1226 } else if ((fp = Fopen(f, "w")) == NULL)
1227 n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
1228 } else {
1229 /* Be very picky in non-interactive mode: actively disallow pipes,
1230 * prevent directory separators, and any filename member that would
1231 * become expanded by the shell if the name would be echo(1)ed */
1232 if(anyof(f, "/" n_SHEXP_MAGIC_PATH_CHARS)){
1233 char c;
1235 for(out.s = salloc((strlen(f) * 3) +1), out.l = 0; (c = *f++) != '\0';)
1236 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
1237 out.s[out.l++] = '%';
1238 n_c_to_hex_base16(&out.s[out.l], c);
1239 out.l += 2;
1240 }else
1241 out.s[out.l++] = c;
1242 out.s[out.l] = '\0';
1243 f = out.s;
1246 /* Avoid overwriting of existing files */
1247 while((fp = Fopen(f, "wx")) == NULL){
1248 int e;
1250 if((e = errno) != EEXIST){
1251 n_err(_("Cannot open %s: %s\n"),
1252 n_shexp_quote_cp(f, FAL0), strerror(e));
1253 break;
1256 if(ip->m_partstring != NULL)
1257 f = savecatsep(f, '#', ip->m_partstring);
1258 else
1259 f = savecat(f, "#.");
1262 jleave:
1263 NYD_LEAVE;
1264 return fp;
1267 static void
1268 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1269 ui64_t *stats)
1271 char *line = NULL; /* TODO line pool */
1272 size_t linesize = 0, linelen, cnt;
1273 ssize_t all_sz, sz;
1274 NYD_ENTER;
1276 fflush(pipebuf);
1277 rewind(pipebuf);
1278 cnt = (size_t)fsize(pipebuf);
1279 all_sz = 0;
1281 quoteflt_reset(qf, outbuf);
1282 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1283 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1284 break;
1285 all_sz += sz;
1287 if ((sz = quoteflt_flush(qf)) > 0)
1288 all_sz += sz;
1289 if (line)
1290 free(line);
1292 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1293 *stats += all_sz;
1294 Fclose(pipebuf);
1295 NYD_LEAVE;
1298 static void
1299 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1300 ui64_t *stats)
1302 char statout[3], *cp = statout;
1303 NYD_ENTER;
1305 if (mp->m_flag & MREAD)
1306 *cp++ = 'R';
1307 if (!(mp->m_flag & MNEW))
1308 *cp++ = 'O';
1309 *cp = 0;
1310 if (statout[0]) {
1311 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1312 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1313 if (i > 0 && stats != NULL)
1314 *stats += i;
1316 NYD_LEAVE;
1319 static void
1320 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1321 ui64_t *stats)
1323 char xstatout[4];
1324 char *xp = xstatout;
1325 NYD_ENTER;
1327 if (mp->m_flag & MFLAGGED)
1328 *xp++ = 'F';
1329 if (mp->m_flag & MANSWERED)
1330 *xp++ = 'A';
1331 if (mp->m_flag & MDRAFTED)
1332 *xp++ = 'T';
1333 *xp = 0;
1334 if (xstatout[0]) {
1335 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1336 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1337 if (i > 0 && stats != NULL)
1338 *stats += i;
1340 NYD_LEAVE;
1343 static void
1344 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1346 char const *froma, *date, *nl;
1347 int i;
1348 NYD_ENTER;
1350 if (ip != NULL && ip->m_from != NULL) {
1351 froma = ip->m_from;
1352 date = fakedate(ip->m_time);
1353 nl = "\n";
1354 } else {
1355 froma = myname;
1356 date = time_current.tc_ctime;
1357 nl = "";
1360 n_COLOUR( n_colour_put(fp, n_COLOUR_ID_VIEW_FROM_, NULL); )
1361 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1362 n_COLOUR( n_colour_reset(fp); )
1363 if (i > 0 && stats != NULL)
1364 *stats += i;
1365 NYD_LEAVE;
1368 FL int
1369 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1370 char const *prefix, enum sendaction action, ui64_t *stats)
1372 struct quoteflt qf;
1373 size_t cnt, sz, i;
1374 FILE *ibuf;
1375 enum mime_parse_flags mpf;
1376 struct mimepart *ip;
1377 int rv = -1, c;
1378 NYD_ENTER;
1380 time_current_update(&time_current, TRU1);
1382 if (mp == dot && action != SEND_TOSRCH)
1383 pstate |= PS_DID_PRINT_DOT;
1384 if (stats != NULL)
1385 *stats = 0;
1386 quoteflt_init(&qf, prefix);
1388 /* First line is the From_ line, so no headers there to worry about */
1389 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1390 goto jleave;
1392 cnt = mp->m_size;
1393 sz = 0;
1395 bool_t nozap;
1396 char const *cpre = "", *csuf = "";
1397 #ifdef HAVE_COLOUR
1398 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_FROM_,NULL);
1399 struct str const *sp = n_colour_pen_to_str(cpen);
1401 if (sp != NULL) {
1402 cpre = sp->s;
1403 sp = n_colour_reset_to_str();
1404 if (sp != NULL)
1405 csuf = sp->s;
1407 #endif
1409 nozap = (doign != allignore && doign != fwdignore && action != SEND_RFC822 &&
1410 !is_ign("from_", sizeof("from_") -1, doign));
1411 if (mp->m_flag & MNOFROM) {
1412 if (nozap)
1413 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1414 cpre, (int)qf.qf_pfix_len,
1415 (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""), fakefrom(mp),
1416 fakedate(mp->m_time), csuf);
1417 } else if (nozap) {
1418 if (qf.qf_pfix_len > 0) {
1419 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1420 if (i != qf.qf_pfix_len)
1421 goto jleave;
1422 sz += i;
1424 #ifdef HAVE_COLOUR
1425 if (cpre != NULL) {
1426 fputs(cpre, obuf);
1427 cpre = (char const*)0x1;
1429 #endif
1431 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1432 #ifdef HAVE_COLOUR
1433 if (c == '\n' && csuf != NULL) {
1434 cpre = (char const*)0x1;
1435 fputs(csuf, obuf);
1437 #endif
1438 putc(c, obuf);
1439 ++sz;
1440 --cnt;
1441 if (c == '\n')
1442 break;
1445 #ifdef HAVE_COLOUR
1446 if (csuf != NULL && cpre != (char const*)0x1)
1447 fputs(csuf, obuf);
1448 #endif
1449 } else {
1450 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1451 --cnt;
1452 if (c == '\n')
1453 break;
1457 if (sz > 0 && stats != NULL)
1458 *stats += sz;
1460 mpf = MIME_PARSE_NONE;
1461 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1462 mpf |= MIME_PARSE_DECRYPT | MIME_PARSE_PARTS;
1463 if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1464 goto jleave;
1466 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1467 jleave:
1468 quoteflt_destroy(&qf);
1469 NYD_LEAVE;
1470 return rv;
1473 /* s-it-mode */