-s, ~s: normalize NL/CR characters (Debian #419840)
[s-mailx.git] / send.c
blob140adef64322cfdf4671d9837772e9995c0affa8
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') || (cp[0] == '1' && /* TODO */
111 cp[1] == '.' && cp[2] != '1')) /* TODO code should not look like so */
112 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
114 /* Part id, content-type, encoding, charset */
115 if (cpre != NULL)
116 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
117 _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
118 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,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,NULL);
124 if ((cp = mpp->m_ct_type_usr_ovwr) != NULL)
125 _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,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,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,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,NULL);
147 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_charset) != NULL) {
148 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
149 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
152 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
153 if (csuf != NULL)
154 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
155 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
157 /* */
158 if (mpp->m_content_info & CI_MIME_ERRORS) {
159 if (cpre != NULL)
160 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
161 NULL, NULL);
162 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
164 ti.l = strlen(ti.s = n_UNCONST(_("Defective MIME structure")));
165 makeprint(&ti, &to);
166 ti.s = NULL; /* Not allocated! */
167 to.l = delctrl(to.s, to.l);
168 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
169 free(to.s);
171 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
172 if (csuf != NULL)
173 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
174 NULL, NULL);
175 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
178 /* Filename */
179 if (n_ignore_is_ign(doitp, "content-disposition", 19) &&
180 mpp->m_filename != NULL && *mpp->m_filename != '\0') {
181 if (cpre != NULL)
182 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
183 NULL, NULL);
184 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
186 ti.l = 0;
187 makeprint(n_str_add_cp(&ti, mpp->m_filename), &to);
188 to.l = delctrl(to.s, to.l);
189 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
190 free(to.s);
192 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
193 if (csuf != NULL)
194 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
195 NULL, NULL);
196 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
199 if (ti.s != NULL)
200 free(ti.s);
201 NYD2_LEAVE;
204 static FILE *
205 _pipefile(struct mime_handler *mhp, struct mimepart const *mpp, FILE **qbuf,
206 char const *tmpname, int term_infd)
208 struct str s;
209 char const *env_addon[8], *cp, *sh;
210 FILE *rbuf;
211 NYD_ENTER;
213 rbuf = *qbuf;
215 if (mhp->mh_flags & MIME_HDL_ISQUOTE) {
216 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
217 NULL) {
218 n_perr(_("tmpfile"), 0);
219 *qbuf = rbuf;
223 if ((mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF) {
224 union {int (*ptf)(void); char const *sh;} u;
226 fflush(*qbuf);
227 if (*qbuf != stdout) /* xxx never? v15: it'll be a filter anyway */
228 fflush(stdout);
230 u.ptf = mhp->mh_ptf;
231 if((rbuf = Popen((char*)-1, "W", u.sh, NULL, fileno(*qbuf))) == NULL)
232 goto jerror;
233 goto jleave;
236 /* NAIL_FILENAME */
237 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
238 cp = n_empty;
239 env_addon[0] = str_concat_csvl(&s, NAILENV_FILENAME, "=", cp, NULL)->s;
241 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
242 * TODO a file wherever he wants! *Do* create a zero-size temporary file
243 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
244 * TODO the pipe returns? Like this we *can* verify path/name issues! */
245 env_addon[1] = str_concat_csvl(&s, NAILENV_FILENAME_GENERATED, "=",
246 getrandstring(n_MIN(NAME_MAX / 4, 16)), NULL)->s;
248 /* NAIL_CONTENT{,_EVIDENCE} */
249 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
250 cp = n_empty;
251 env_addon[2] = str_concat_csvl(&s, NAILENV_CONTENT, "=", cp, NULL)->s;
253 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
254 cp = mpp->m_ct_type_usr_ovwr;
255 env_addon[3] = str_concat_csvl(&s, NAILENV_CONTENT_EVIDENCE, "=", cp,
256 NULL)->s;
258 cp = ok_vlook(TMPDIR);
259 env_addon[4] = str_concat_csvl(&s, NAILENV_TMPDIR, "=", cp, NULL)->s;
260 env_addon[5] = str_concat_csvl(&s, "TMPDIR", "=", cp, NULL)->s;
262 env_addon[6] = NULL;
264 /* NAIL_FILENAME_TEMPORARY? */
265 if (tmpname != NULL) {
266 env_addon[6] = str_concat_csvl(&s, NAILENV_FILENAME_TEMPORARY, "=",
267 tmpname, NULL)->s;
268 env_addon[7] = NULL;
271 sh = ok_vlook(SHELL);
273 if (mhp->mh_flags & MIME_HDL_NEEDSTERM) {
274 sigset_t nset;
275 int pid;
277 sigemptyset(&nset);
278 pid = run_command(sh, &nset, term_infd, COMMAND_FD_PASS, "-c",
279 mhp->mh_shell_cmd, NULL, env_addon);
280 rbuf = (pid < 0) ? NULL : (FILE*)-1;
281 } else {
282 rbuf = Popen(mhp->mh_shell_cmd, "W", sh, env_addon,
283 (mhp->mh_flags & MIME_HDL_ASYNC ? -1 : fileno(*qbuf)));
284 jerror:
285 if (rbuf == NULL)
286 n_err(_("Cannot run MIME type handler: %s: %s\n"),
287 mhp->mh_msg, strerror(errno));
288 else {
289 fflush(*qbuf);
290 if (*qbuf != stdout)
291 fflush(stdout);
294 jleave:
295 NYD_LEAVE;
296 return rbuf;
299 SINLINE ssize_t
300 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
301 sendaction action, struct quoteflt *qf, ui64_t *stats, struct str *outrest,
302 struct str *inrest)
304 ssize_t sz = 0, n;
305 int flags;
306 NYD_ENTER;
308 #if 0
309 Well ... it turns out to not work like that since of course a valid
310 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
311 after an empty line has been seen, which cannot be detected that easily
312 right here!
313 ifdef HAVE_DEBUG /* TODO assert legacy */
314 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
315 * TODO other input situations handle RFC 4155 OR, if newly generated,
316 * TODO enforce quoted-printable if there is From_, as "required" by
317 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
318 * TODO if it may happen in this path, we should just treat decryption
319 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
320 if (action == SEND_MBOX || action == SEND_DECRYPT)
321 assert(!is_head(buf, len, FAL0));
322 #else
323 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
324 is_head(buf, len, FAL0)) {
325 putc('>', fp);
326 ++sz;
328 #endif
330 flags = ((int)action & _TD_EOF);
331 action &= ~_TD_EOF;
332 n = mime_write(buf, len, fp,
333 action == SEND_MBOX ? CONV_NONE : convert,
334 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
335 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
336 ? TD_ISPR | TD_ICONV
337 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
338 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
339 qf, outrest, inrest);
340 if (n < 0)
341 sz = n;
342 else if (n > 0) {
343 sz += n;
344 if (stats != NULL)
345 *stats += sz;
347 NYD_LEAVE;
348 return sz;
351 static void
352 _send_onpipe(int signo)
354 NYD_X; /* Signal handler */
355 n_UNUSED(signo);
356 siglongjmp(_send_pipejmp, 1);
359 static sigjmp_buf __sendp_actjmp; /* TODO someday.. */
360 static int __sendp_sig; /* TODO someday.. */
361 static sighandler_type __sendp_opipe;
362 static void
363 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
365 NYD_X; /* Signal handler */
366 __sendp_sig = sig;
367 siglongjmp(__sendp_actjmp, 1);
370 static int
371 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
372 struct n_ignore const *doitp, struct quoteflt *qf,
373 enum sendaction volatile action, ui64_t * volatile stats, int level)
375 int volatile rv = 0;
376 struct mime_handler mh;
377 struct str outrest, inrest;
378 char *line = NULL, *cp, *cp2, *start;
379 char const * volatile tmpname = NULL;
380 size_t linesize = 0, linelen, cnt;
381 int volatile term_infd;
382 int dostat, c;
383 struct mimepart *volatile np;
384 FILE * volatile ibuf = NULL, * volatile pbuf = obuf,
385 * volatile qbuf = obuf, *origobuf = obuf;
386 enum conversion volatile convert;
387 sighandler_type volatile oldpipe = SIG_DFL;
388 NYD_ENTER;
390 n_UNINIT(term_infd, 0);
391 n_UNINIT(cnt, 0);
393 quoteflt_reset(qf, obuf);
395 if (ip->m_mimecontent == MIME_PKCS7) {
396 if (ip->m_multipart &&
397 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
398 goto jheaders_skip;
401 dostat = 0;
402 if (level == 0) {
403 if (doitp != NULL) {
404 if (!n_ignore_is_ign(doitp, "status", 6))
405 dostat |= 1;
406 if (!n_ignore_is_ign(doitp, "x-status", 8))
407 dostat |= 2;
408 } else
409 dostat = 3;
411 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
412 rv = -1;
413 goto jleave;
415 cnt = ip->m_size;
417 if (ip->m_mimecontent == MIME_DISCARD)
418 goto jheaders_skip;
420 if (!(ip->m_flag & MNOFROM))
421 while (cnt && (c = getc(ibuf)) != EOF) {
422 cnt--;
423 if (c == '\n')
424 break;
426 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
427 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
428 action == SEND_TOSRCH)
429 ? CONV_FROMHDR : CONV_NONE;
431 /* Work the headers */
432 /* C99 */{
433 enum {
434 HPS_NONE = 0,
435 HPS_IN_FIELD = 1<<0,
436 HPS_IGNORE = 1<<1,
437 HPS_ISENC_1 = 1<<2,
438 HPS_ISENC_2 = 1<<3
439 } hps = HPS_NONE;
440 size_t lineno = 0;
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 (doitp != n_IGNORE_ALL)
452 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
453 break;
456 hps &= ~HPS_ISENC_1;
457 if ((hps & HPS_IN_FIELD) && 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 (hps & HPS_ISENC_2) {
461 for (cp = line; blankchar(*cp); ++cp)
463 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
464 cp[0] == '=' && cp[1] == '?')
465 hps |= HPS_ISENC_1;
467 } else {
468 /* Pick up the header field if we have one */
469 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
471 cp2 = cp;
472 while (spacechar(*cp))
473 ++cp;
474 if (cp[0] != ':') {
475 if (lineno != 1)
476 n_err(_("Malformed message: headers and body not separated "
477 "(with empty line)\n"));
478 /* Not a header line, force out status: This happens in uucp style
479 * mail where there are no headers at all */
480 if (level == 0 /*&& lineno == 1*/) {
481 if (dostat & 1)
482 statusput(zmp, obuf, qf, stats);
483 if (dostat & 2)
484 xstatusput(zmp, obuf, qf, stats);
486 if (doitp != n_IGNORE_ALL)
487 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL,NULL);
488 break;
491 /* If it is an ignored field and we care about such things, skip it.
492 * Misuse dostat also for another bit xxx use a bitenum + for more */
493 if (ok_blook(keep_content_length))
494 dostat |= 1 << 2;
495 c = *cp2;
496 *cp2 = 0; /* temporarily null terminate */
497 if ((doitp != NULL &&
498 n_ignore_is_ign(doitp, line, PTR2SIZE(cp2 - line))) ||
499 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
500 (!asccasecmp(line, "content-length") ||
501 !asccasecmp(line, "lines"))))
502 hps |= HPS_IGNORE;
503 else if (!asccasecmp(line, "status")) {
504 /* If field is "status," go compute and print real Status: field */
505 if (dostat & 1) {
506 statusput(zmp, obuf, qf, stats);
507 dostat &= ~1;
508 hps |= HPS_IGNORE;
510 } else if (!asccasecmp(line, "x-status")) {
511 /* If field is "status," go compute and print real Status: field */
512 if (dostat & 2) {
513 xstatusput(zmp, obuf, qf, stats);
514 dostat &= ~2;
515 hps |= HPS_IGNORE;
517 } else {
518 hps &= ~HPS_IGNORE;
519 /* For colourization we need the complete line, so save it */
520 /* XXX This is all temporary (colour belongs into backend), so
521 * XXX use tmpname as a temporary storage in the meanwhile */
522 #ifdef HAVE_COLOUR
523 if (pstate & PS_COLOUR_ACTIVE)
524 tmpname = savestrbuf(line, PTR2SIZE(cp2 - line));
525 #endif
527 *cp2 = c;
528 dostat &= ~(1 << 2);
529 hps |= HPS_IN_FIELD;
532 /* Determine if the end of the line is a MIME encoded word */
533 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
534 * TODO with header follow lines, and it should be up to the backend
535 * TODO what happens and what not, i.e., it doesn't matter whether it's
536 * TODO a MIME-encoded word or not, as long as a single separating space
537 * TODO remains in between lines (the MIME stuff will correctly remove
538 * TODO whitespace in between multiple adjacent encoded words) */
539 hps &= ~HPS_ISENC_2;
540 if (cnt && (c = getc(ibuf)) != EOF) {
541 if (blankchar(c)) {
542 cp = line + linelen - 1;
543 if (linelen > 0 && *cp == '\n')
544 --cp;
545 while (cp >= line && whitechar(*cp))
546 --cp;
547 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
548 hps |= HPS_ISENC_2;
550 ungetc(c, ibuf);
553 if (!(hps & HPS_IGNORE)) {
554 size_t len = linelen;
555 start = line;
556 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
557 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
558 action == SEND_TOSRCH) {
559 /* Strip blank characters if two MIME-encoded words follow on
560 * continuing lines */
561 if (hps & HPS_ISENC_1)
562 while (len > 0 && blankchar(*start)) {
563 ++start;
564 --len;
566 if (hps & HPS_ISENC_2)
567 if (len > 0 && start[len - 1] == '\n')
568 --len;
569 while (len > 0 && blankchar(start[len - 1]))
570 --len;
572 #ifdef HAVE_COLOUR
574 bool_t colour_stripped = FAL0;
575 if (tmpname != NULL) {
576 n_colour_put(obuf, n_COLOUR_ID_VIEW_HEADER, tmpname);
577 if (len > 0 && start[len - 1] == '\n') {
578 colour_stripped = TRU1;
579 --len;
582 #endif
583 _out(start, len, obuf, convert, action, qf, stats, NULL,NULL);
584 #ifdef HAVE_COLOUR
585 if (tmpname != NULL) {
586 n_colour_reset(obuf);
587 if (colour_stripped)
588 putc('\n', obuf);
591 #endif
592 if (ferror(obuf)) {
593 free(line);
594 rv = -1;
595 goto jleave;
599 } /* C99 */
600 quoteflt_flush(qf);
601 free(line);
602 line = NULL;
603 tmpname = NULL;
605 jheaders_skip:
606 memset(&mh, 0, sizeof mh);
608 switch (ip->m_mimecontent) {
609 case MIME_822:
610 switch (action) {
611 case SEND_TODISP:
612 case SEND_TODISP_ALL:
613 case SEND_QUOTE:
614 case SEND_QUOTE_ALL:
615 if (ok_blook(rfc822_body_from_)) {
616 if (qf->qf_pfix_len > 0) {
617 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
618 qf->qf_pfix_len, obuf);
619 if (i == qf->qf_pfix_len && stats != NULL)
620 *stats += i;
622 put_from_(obuf, ip->m_multipart, stats);
624 /* FALLTHRU */
625 case SEND_TOSRCH:
626 case SEND_DECRYPT:
627 goto jmulti;
628 case SEND_TOFILE:
629 case SEND_TOPIPE:
630 if (ok_blook(rfc822_body_from_))
631 put_from_(obuf, ip->m_multipart, stats);
632 /* FALLTHRU */
633 case SEND_MBOX:
634 case SEND_RFC822:
635 case SEND_SHOW:
636 break;
638 break;
639 case MIME_TEXT_HTML:
640 case MIME_TEXT:
641 case MIME_TEXT_PLAIN:
642 switch (action) {
643 case SEND_TODISP:
644 case SEND_TODISP_ALL:
645 case SEND_QUOTE:
646 case SEND_QUOTE_ALL:
647 switch (mime_type_handler(&mh, ip, action)) {
648 case MIME_HDL_MSG:
649 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
650 stats, NULL, NULL);
651 /* We would print this as plain text, so better force going home */
652 goto jleave;
653 default:
654 break;
656 /* FALLTRHU */
657 default:
658 break;
660 break;
661 case MIME_DISCARD:
662 if (action != SEND_DECRYPT)
663 goto jleave;
664 break;
665 case MIME_PKCS7:
666 if (action != SEND_MBOX && action != SEND_RFC822 &&
667 action != SEND_SHOW && ip->m_multipart != NULL)
668 goto jmulti;
669 /* FALLTHRU */
670 default:
671 switch (action) {
672 case SEND_TODISP:
673 case SEND_TODISP_ALL:
674 case SEND_QUOTE:
675 case SEND_QUOTE_ALL:
676 switch (mime_type_handler(&mh, ip, action)) {
677 case MIME_HDL_MSG:
678 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
679 stats, NULL, NULL);
680 /* We would print this as plain text, so better force going home */
681 goto jleave;
682 case MIME_HDL_CMD:
683 /* FIXME WE NEED TO DO THAT IF WE ARE THE ONLY MAIL
684 * FIXME CONTENT !! */
685 case MIME_HDL_TEXT:
686 break;
687 default:
688 case MIME_HDL_NULL:
689 if (level == 0 && cnt) {
690 char const *x = _("[-- Binary content --]\n");
691 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
692 NULL,NULL);
694 goto jleave;
696 break;
697 case SEND_TOFILE:
698 case SEND_TOPIPE:
699 case SEND_TOSRCH:
700 case SEND_DECRYPT:
701 case SEND_MBOX:
702 case SEND_RFC822:
703 case SEND_SHOW:
704 break;
706 break;
707 case MIME_ALTERNATIVE:
708 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
709 !ok_blook(print_alternatives)) {
710 /* XXX This (a) should not remain (b) should be own fun
711 * TODO (despite the fact that v15 will do this completely differently
712 * TODO by having an action-specific "manager" that will traverse the
713 * TODO parsed MIME tree and decide for each part whether it'll be
714 * TODO displayed or not *before* we walk the tree for doing action */
715 struct mpstack {
716 struct mpstack *outer;
717 struct mimepart *mp;
718 } outermost, * volatile curr, * volatile mpsp;
719 bool_t volatile neednl, hadpart;
720 struct n_sigman smalter;
722 (curr = &outermost)->outer = NULL;
723 curr->mp = ip;
724 neednl = hadpart = FAL0;
726 n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
727 case 0:
728 break;
729 default:
730 rv = -1;
731 goto jalter_leave;
734 for (np = ip->m_multipart;;) {
735 jalter_redo:
736 for (; np != NULL; np = np->m_nextpart) {
737 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
738 if (neednl)
739 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats,
740 NULL, NULL);
741 _print_part_info(obuf, np, doitp, level, qf, stats);
743 neednl = TRU1;
745 switch (np->m_mimecontent) {
746 case MIME_ALTERNATIVE:
747 case MIME_RELATED:
748 case MIME_DIGEST:
749 case MIME_SIGNED:
750 case MIME_ENCRYPTED:
751 case MIME_MULTI:
752 mpsp = salloc(sizeof *mpsp);
753 mpsp->outer = curr;
754 mpsp->mp = np->m_multipart;
755 curr->mp = np;
756 curr = mpsp;
757 np = mpsp->mp;
758 neednl = FAL0;
759 goto jalter_redo;
760 default:
761 if (hadpart)
762 break;
763 switch (mime_type_handler(&mh, np, action)) {
764 default:
765 mh.mh_flags = MIME_HDL_NULL;
766 continue; /* break; break; */
767 case MIME_HDL_PTF:
768 if (!ok_blook(mime_alternative_favour_rich)) {/* TODO */
769 struct mimepart *x = np;
771 while ((x = x->m_nextpart) != NULL) {
772 struct mime_handler mhx;
774 if (x->m_mimecontent == MIME_TEXT_PLAIN ||
775 mime_type_handler(&mhx, x, action) ==
776 MIME_HDL_TEXT)
777 break;
779 if (x != NULL)
780 continue; /* break; break; */
781 goto jalter_plain;
783 /* FALLTHRU */
784 case MIME_HDL_TEXT:
785 break;
787 /* FALLTHRU */
788 case MIME_TEXT_PLAIN:
789 if (hadpart)
790 break;
791 if (ok_blook(mime_alternative_favour_rich)) { /* TODO */
792 struct mimepart *x = np;
794 /* TODO twice TODO, we should dive into /related and
795 * TODO check whether that has rich parts! */
796 while ((x = x->m_nextpart) != NULL) {
797 struct mime_handler mhx;
799 switch (mime_type_handler(&mhx, x, action)) {
800 case MIME_HDL_PTF:
801 break;
802 default:
803 continue;
805 break;
807 if (x != NULL)
808 continue; /* break; break; */
810 jalter_plain:
811 quoteflt_flush(qf);
812 if (action == SEND_QUOTE && hadpart) {
813 struct quoteflt *dummy = quoteflt_dummy();
814 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
815 NULL,NULL);
816 quoteflt_flush(dummy);
818 hadpart = TRU1;
819 neednl = FAL0;
820 rv = sendpart(zmp, np, obuf, doitp, qf, action, stats,
821 level + 1);
822 quoteflt_reset(qf, origobuf);
824 if (rv < 0)
825 curr = &outermost; /* Cause overall loop termination */
826 break;
830 mpsp = curr->outer;
831 if (mpsp == NULL)
832 break;
833 curr = mpsp;
834 np = curr->mp->m_nextpart;
836 jalter_leave:
837 n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
838 goto jleave;
840 /* FALLTHRU */
841 case MIME_RELATED:
842 case MIME_DIGEST:
843 case MIME_SIGNED:
844 case MIME_ENCRYPTED:
845 case MIME_MULTI:
846 switch (action) {
847 case SEND_TODISP:
848 case SEND_TODISP_ALL:
849 case SEND_QUOTE:
850 case SEND_QUOTE_ALL:
851 case SEND_TOFILE:
852 case SEND_TOPIPE:
853 case SEND_TOSRCH:
854 case SEND_DECRYPT:
855 jmulti:
856 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
857 ip->m_multipart != NULL &&
858 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
859 ip->m_multipart->m_nextpart == NULL) {
860 char const *x = _("[Missing multipart boundary - use show "
861 "to display the raw message]\n");
862 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
863 NULL,NULL);
866 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
867 bool_t volatile ispipe;
869 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
870 continue;
872 ispipe = FAL0;
873 switch (action) {
874 case SEND_TOFILE:
875 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
876 break;
877 stats = NULL;
878 /* TODO Always open multipart on /dev/null, it's a hack to be
879 * TODO able to dive into that structure, and still better
880 * TODO than asking the user for something stupid.
881 * TODO oh, wait, we did ask for a filename for this MIME mail,
882 * TODO and that outer container is useless anyway ;-P */
883 if (np->m_multipart != NULL) {
884 if ((obuf = Fopen("/dev/null", "w")) == NULL)
885 continue;
886 } else if ((obuf = newfile(np, &ispipe)) == NULL)
887 continue;
888 if (!ispipe)
889 break;
890 if (sigsetjmp(_send_pipejmp, 1)) {
891 rv = -1;
892 goto jpipe_close;
894 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
895 break;
896 case SEND_TODISP:
897 case SEND_TODISP_ALL:
898 if (ip->m_mimecontent != MIME_ALTERNATIVE &&
899 ip->m_mimecontent != MIME_RELATED &&
900 ip->m_mimecontent != MIME_DIGEST &&
901 ip->m_mimecontent != MIME_SIGNED &&
902 ip->m_mimecontent != MIME_ENCRYPTED &&
903 ip->m_mimecontent != MIME_MULTI)
904 break;
905 _print_part_info(obuf, np, doitp, level, qf, stats);
906 break;
907 case SEND_QUOTE:
908 case SEND_QUOTE_ALL:
909 case SEND_MBOX:
910 case SEND_RFC822:
911 case SEND_SHOW:
912 case SEND_TOSRCH:
913 case SEND_DECRYPT:
914 case SEND_TOPIPE:
915 break;
918 quoteflt_flush(qf);
919 if ((action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
920 np->m_multipart == NULL && ip->m_parent != NULL) {
921 struct quoteflt *dummy = quoteflt_dummy();
922 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
923 NULL,NULL);
924 quoteflt_flush(dummy);
926 if (sendpart(zmp, np, obuf, doitp, qf, action, stats, level+1) < 0)
927 rv = -1;
928 quoteflt_reset(qf, origobuf);
930 if (action == SEND_QUOTE) {
931 if (ip->m_mimecontent != MIME_RELATED)
932 break;
934 if (action == SEND_TOFILE && obuf != origobuf) {
935 if (!ispipe)
936 Fclose(obuf);
937 else {
938 jpipe_close:
939 safe_signal(SIGPIPE, SIG_IGN);
940 Pclose(obuf, TRU1);
941 safe_signal(SIGPIPE, oldpipe);
945 goto jleave;
946 case SEND_MBOX:
947 case SEND_RFC822:
948 case SEND_SHOW:
949 break;
951 break;
954 /* Copy out message body */
955 if (doitp == n_IGNORE_ALL && level == 0) /* skip final blank line */
956 --cnt;
957 switch (ip->m_mime_enc) {
958 case MIMEE_BIN:
959 case MIMEE_7B:
960 case MIMEE_8B:
961 convert = CONV_NONE;
962 break;
963 case MIMEE_QP:
964 convert = CONV_FROMQP;
965 break;
966 case MIMEE_B64:
967 switch (ip->m_mimecontent) {
968 case MIME_TEXT:
969 case MIME_TEXT_PLAIN:
970 case MIME_TEXT_HTML:
971 convert = CONV_FROMB64_T;
972 break;
973 default:
974 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
975 case MIME_HDL_TEXT:
976 case MIME_HDL_PTF:
977 convert = CONV_FROMB64_T;
978 break;
979 default:
980 convert = CONV_FROMB64;
981 break;
983 break;
985 break;
986 default:
987 convert = CONV_NONE;
990 if (action == SEND_DECRYPT || action == SEND_MBOX ||
991 action == SEND_RFC822 || action == SEND_SHOW)
992 convert = CONV_NONE;
993 #ifdef HAVE_ICONV
994 else if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
995 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
996 action == SEND_TOSRCH) &&
997 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
998 ip->m_mimecontent == MIME_TEXT_HTML ||
999 ip->m_mimecontent == MIME_TEXT ||
1000 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_TEXT ||
1001 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF)) {
1002 char const *tcs = charset_get_lc();
1004 if (iconvd != (iconv_t)-1)
1005 n_iconv_close(iconvd);
1006 /* TODO Since Base64 has an odd 4:3 relation in between input
1007 * TODO and output an input line may end with a partial
1008 * TODO multibyte character; this is no problem at all unless
1009 * TODO we send to the display or whatever, i.e., ensure
1010 * TODO makeprint() or something; to avoid this trap, *force*
1011 * TODO iconv(), in which case this layer will handle leftovers
1012 * TODO correctly. It's a pre-v15 we-have-filters hack */
1013 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
1014 asccasecmp(charset_get_7bit(), ip->m_charset))) {
1015 iconvd = n_iconv_open(tcs, ip->m_charset);
1016 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1017 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
1018 /*rv = 1; goto jleave;*/
1022 #endif
1024 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
1025 case MIME_HDL_CMD:
1026 case MIME_HDL_PTF:
1027 tmpname = NULL;
1028 qbuf = obuf;
1030 term_infd = COMMAND_FD_PASS;
1031 if (mh.mh_flags & (MIME_HDL_TMPF | MIME_HDL_NEEDSTERM)) {
1032 enum oflags of;
1034 of = OF_RDWR | OF_REGISTER;
1035 if (!(mh.mh_flags & MIME_HDL_TMPF)) {
1036 term_infd = 0;
1037 mh.mh_flags |= MIME_HDL_TMPF_FILL;
1038 of |= OF_UNLINK;
1039 } else if (mh.mh_flags & MIME_HDL_TMPF_UNLINK)
1040 of |= OF_REGISTER_UNLINK;
1042 if ((pbuf = Ftmp((mh.mh_flags & MIME_HDL_TMPF ? &cp : NULL),
1043 (mh.mh_flags & MIME_HDL_TMPF_FILL ? "mimehdlfill" : "mimehdl"),
1044 of)) == NULL)
1045 goto jesend;
1047 if (mh.mh_flags & MIME_HDL_TMPF) {
1048 tmpname = savestr(cp);
1049 Ftmp_free(&cp);
1052 if (mh.mh_flags & MIME_HDL_TMPF_FILL) {
1053 if (term_infd == 0)
1054 term_infd = fileno(pbuf);
1055 goto jsend;
1059 jpipe_for_real:
1060 pbuf = _pipefile(&mh, ip, n_UNVOLATILE(&qbuf), tmpname, term_infd);
1061 if (pbuf == NULL) {
1062 jesend:
1063 pbuf = qbuf = NULL;
1064 rv = -1;
1065 goto jend;
1066 } else if ((mh.mh_flags & MIME_HDL_NEEDSTERM) && pbuf == (FILE*)-1) {
1067 pbuf = qbuf = NULL;
1068 goto jend;
1070 tmpname = NULL;
1071 action = SEND_TOPIPE;
1072 if (pbuf != qbuf) {
1073 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1074 if (sigsetjmp(_send_pipejmp, 1))
1075 goto jend;
1077 break;
1079 default:
1080 mh.mh_flags = MIME_HDL_NULL;
1081 pbuf = qbuf = obuf;
1082 break;
1085 jsend:
1087 bool_t volatile eof;
1088 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1089 ui64_t *save_stats = stats;
1091 if (pbuf != origobuf) {
1092 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1093 stats = NULL;
1095 eof = FAL0;
1096 outrest.s = inrest.s = NULL;
1097 outrest.l = inrest.l = 0;
1099 if (pbuf == qbuf) {
1100 __sendp_sig = 0;
1101 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1102 if (sigsetjmp(__sendp_actjmp, 1)) {
1103 if (outrest.s != NULL)
1104 free(outrest.s);
1105 if (inrest.s != NULL)
1106 free(inrest.s);
1107 free(line);
1108 #ifdef HAVE_ICONV
1109 if (iconvd != (iconv_t)-1)
1110 n_iconv_close(iconvd);
1111 #endif
1112 safe_signal(SIGPIPE, __sendp_opipe);
1113 n_raise(__sendp_sig);
1117 quoteflt_reset(qf, pbuf);
1118 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1119 joutln:
1120 if (_out(line, linelen, pbuf, convert, action, qf, stats, &outrest,
1121 (action & _TD_EOF ? NULL : &inrest)) < 0 || ferror(pbuf)) {
1122 rv = -1; /* XXX Should bail away?! */
1123 break;
1126 if(eof <= FAL0 && rv >= 0 && (outrest.l != 0 || inrest.l != 0)){
1127 linelen = 0;
1128 if(eof || inrest.l == 0)
1129 action |= _TD_EOF;
1130 eof = eof ? TRU1 : TRUM1;
1131 goto joutln;
1134 /* TODO HACK: when sending to the display we yet get fooled if a message
1135 * TODO doesn't end in a newline, because of our input/output 1:1.
1136 * TODO This should be handled automatically by a display filter, then */
1137 if(rv >= 0 && !qf->qf_nl_last &&
1138 (action == SEND_TODISP || action == SEND_TODISP_ALL))
1139 rv = quoteflt_push(qf, "\n", 1);
1141 quoteflt_flush(qf);
1143 if (rv >= 0 && (mh.mh_flags & MIME_HDL_TMPF_FILL)) {
1144 mh.mh_flags &= ~MIME_HDL_TMPF_FILL;
1145 fflush(pbuf);
1146 really_rewind(pbuf);
1147 /* Don't Fclose() the Ftmp() thing due to OF_REGISTER_UNLINK++ */
1148 goto jpipe_for_real;
1151 if (pbuf == qbuf)
1152 safe_signal(SIGPIPE, __sendp_opipe);
1154 if (outrest.s != NULL)
1155 free(outrest.s);
1156 if (inrest.s != NULL)
1157 free(inrest.s);
1159 if (pbuf != origobuf) {
1160 qf->qf_pfix_len = save_qf_pfix_len;
1161 stats = save_stats;
1165 jend:
1166 if (line != NULL)
1167 free(line);
1168 if (pbuf != qbuf) {
1169 safe_signal(SIGPIPE, SIG_IGN);
1170 Pclose(pbuf, !(mh.mh_flags & MIME_HDL_ASYNC));
1171 safe_signal(SIGPIPE, oldpipe);
1172 if (rv >= 0 && qbuf != NULL && qbuf != obuf)
1173 pipecpy(qbuf, obuf, origobuf, qf, stats);
1175 #ifdef HAVE_ICONV
1176 if (iconvd != (iconv_t)-1)
1177 n_iconv_close(iconvd);
1178 #endif
1179 jleave:
1180 NYD_LEAVE;
1181 return rv;
1184 static FILE *
1185 newfile(struct mimepart *ip, bool_t volatile *ispipe)
1187 struct str in, out;
1188 char *f;
1189 FILE *fp;
1190 NYD_ENTER;
1192 f = ip->m_filename;
1193 *ispipe = FAL0;
1195 if (f != NULL && f != (char*)-1) {
1196 in.s = f;
1197 in.l = strlen(f);
1198 makeprint(&in, &out);
1199 out.l = delctrl(out.s, out.l);
1200 f = savestrbuf(out.s, out.l);
1201 free(out.s);
1204 /* In interactive mode, let user perform all kind of expansions as desired,
1205 * and offer |SHELL-SPEC pipe targets, too */
1206 if (options & OPT_INTERACTIVE) {
1207 struct str prompt;
1208 struct n_string shou, *shoup;
1209 char *f2, *f3;
1211 shoup = n_string_creat_auto(&shou);
1213 /* TODO Generic function which asks for filename.
1214 * TODO If the current part is the first textpart the target
1215 * TODO is implicit from outer `write' etc! */
1216 /* I18N: Filename input prompt with file type indication */
1217 str_concat_csvl(&prompt, _("Enter filename for part "),
1218 (ip->m_partstring != NULL) ? ip->m_partstring : _("?"),
1219 _(" ("), ip->m_ct_type_plain, _("): "), NULL);
1220 jgetname:
1221 f2 = n_lex_input_cp(n_LEXINPUT_CTX_DEFAULT | n_LEXINPUT_HIST_ADD,
1222 prompt.s, ((f != (char*)-1 && f != NULL)
1223 ? n_shexp_quote_cp(f, FAL0) : NULL));
1224 if(f2 != NULL){
1225 in.s = n_UNCONST(f2);
1226 in.l = UIZ_MAX;
1227 if((n_shexp_parse_token(shoup, &in, n_SHEXP_PARSE_TRUNC |
1228 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
1229 n_SHEXP_PARSE_IGNORE_EMPTY) &
1230 (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT |
1231 n_SHEXP_STATE_ERR_MASK)
1232 ) != (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT))
1233 goto jgetname;
1234 f2 = n_string_cp(shoup);
1236 if (f2 == NULL || *f2 == '\0') {
1237 if (options & OPT_D_V)
1238 n_err(_("... skipping this\n"));
1239 n_string_gut(shoup);
1240 fp = NULL;
1241 goto jleave;
1244 if (*f2 == '|')
1245 /* Pipes are expanded by the shell */
1246 f = f2;
1247 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1248 /* (Error message written by fexpand()) */
1249 goto jgetname;
1250 else
1251 f = f3;
1253 n_string_gut(shoup);
1256 if (f == NULL || f == (char*)-1 || *f == '\0')
1257 fp = NULL;
1258 else if (options & OPT_INTERACTIVE) {
1259 if (*f == '|') {
1260 fp = Popen(&f[1], "w", ok_vlook(SHELL), NULL, 1);
1261 if (!(*ispipe = (fp != NULL)))
1262 n_perr(f, 0);
1263 } else if ((fp = Fopen(f, "w")) == NULL)
1264 n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
1265 } else {
1266 /* Be very picky in non-interactive mode: actively disallow pipes,
1267 * prevent directory separators, and any filename member that would
1268 * become expanded by the shell if the name would be echo(1)ed */
1269 if(anyof(f, "/" n_SHEXP_MAGIC_PATH_CHARS)){
1270 char c;
1272 for(out.s = salloc((strlen(f) * 3) +1), out.l = 0; (c = *f++) != '\0';)
1273 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
1274 out.s[out.l++] = '%';
1275 n_c_to_hex_base16(&out.s[out.l], c);
1276 out.l += 2;
1277 }else
1278 out.s[out.l++] = c;
1279 out.s[out.l] = '\0';
1280 f = out.s;
1283 /* Avoid overwriting of existing files */
1284 while((fp = Fopen(f, "wx")) == NULL){
1285 int e;
1287 if((e = errno) != EEXIST){
1288 n_err(_("Cannot open %s: %s\n"),
1289 n_shexp_quote_cp(f, FAL0), strerror(e));
1290 break;
1293 if(ip->m_partstring != NULL)
1294 f = savecatsep(f, '#', ip->m_partstring);
1295 else
1296 f = savecat(f, "#.");
1299 jleave:
1300 NYD_LEAVE;
1301 return fp;
1304 static void
1305 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1306 ui64_t *stats)
1308 char *line = NULL; /* TODO line pool */
1309 size_t linesize = 0, linelen, cnt;
1310 ssize_t all_sz, sz;
1311 NYD_ENTER;
1313 fflush(pipebuf);
1314 rewind(pipebuf);
1315 cnt = (size_t)fsize(pipebuf);
1316 all_sz = 0;
1318 quoteflt_reset(qf, outbuf);
1319 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1320 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1321 break;
1322 all_sz += sz;
1324 if ((sz = quoteflt_flush(qf)) > 0)
1325 all_sz += sz;
1326 if (line)
1327 free(line);
1329 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1330 *stats += all_sz;
1331 Fclose(pipebuf);
1332 NYD_LEAVE;
1335 static void
1336 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1337 ui64_t *stats)
1339 char statout[3], *cp = statout;
1340 NYD_ENTER;
1342 if (mp->m_flag & MREAD)
1343 *cp++ = 'R';
1344 if (!(mp->m_flag & MNEW))
1345 *cp++ = 'O';
1346 *cp = 0;
1347 if (statout[0]) {
1348 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1349 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1350 if (i > 0 && stats != NULL)
1351 *stats += i;
1353 NYD_LEAVE;
1356 static void
1357 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1358 ui64_t *stats)
1360 char xstatout[4];
1361 char *xp = xstatout;
1362 NYD_ENTER;
1364 if (mp->m_flag & MFLAGGED)
1365 *xp++ = 'F';
1366 if (mp->m_flag & MANSWERED)
1367 *xp++ = 'A';
1368 if (mp->m_flag & MDRAFTED)
1369 *xp++ = 'T';
1370 *xp = 0;
1371 if (xstatout[0]) {
1372 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1373 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1374 if (i > 0 && stats != NULL)
1375 *stats += i;
1377 NYD_LEAVE;
1380 static void
1381 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1383 char const *froma, *date, *nl;
1384 int i;
1385 NYD_ENTER;
1387 if (ip != NULL && ip->m_from != NULL) {
1388 froma = ip->m_from;
1389 date = fakedate(ip->m_time);
1390 nl = "\n";
1391 } else {
1392 froma = myname;
1393 date = time_current.tc_ctime;
1394 nl = n_empty;
1397 n_COLOUR( n_colour_put(fp, n_COLOUR_ID_VIEW_FROM_, NULL); )
1398 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1399 n_COLOUR( n_colour_reset(fp); )
1400 if (i > 0 && stats != NULL)
1401 *stats += i;
1402 NYD_LEAVE;
1405 FL int
1406 sendmp(struct message *mp, FILE *obuf, struct n_ignore const *doitp,
1407 char const *prefix, enum sendaction action, ui64_t *stats)
1409 struct quoteflt qf;
1410 size_t cnt, sz, i;
1411 FILE *ibuf;
1412 enum mime_parse_flags mpf;
1413 struct mimepart *ip;
1414 int rv = -1, c;
1415 NYD_ENTER;
1417 time_current_update(&time_current, TRU1);
1419 if (mp == dot && action != SEND_TOSRCH)
1420 pstate |= PS_DID_PRINT_DOT;
1421 if (stats != NULL)
1422 *stats = 0;
1423 quoteflt_init(&qf, prefix);
1425 /* First line is the From_ line, so no headers there to worry about */
1426 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1427 goto jleave;
1429 cnt = mp->m_size;
1430 sz = 0;
1432 bool_t nozap;
1433 char const *cpre = n_empty, *csuf = n_empty;
1434 #ifdef HAVE_COLOUR
1435 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_FROM_,NULL);
1436 struct str const *sp = n_colour_pen_to_str(cpen);
1438 if (sp != NULL) {
1439 cpre = sp->s;
1440 sp = n_colour_reset_to_str();
1441 if (sp != NULL)
1442 csuf = sp->s;
1444 #endif
1446 nozap = (doitp != n_IGNORE_ALL && doitp != n_IGNORE_FWD &&
1447 action != SEND_RFC822 &&
1448 !n_ignore_is_ign(doitp, "from_", sizeof("from_") -1));
1449 if (mp->m_flag & MNOFROM) {
1450 if (nozap)
1451 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1452 cpre, (int)qf.qf_pfix_len,
1453 (qf.qf_pfix_len != 0 ? qf.qf_pfix : n_empty), fakefrom(mp),
1454 fakedate(mp->m_time), csuf);
1455 } else if (nozap) {
1456 if (qf.qf_pfix_len > 0) {
1457 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1458 if (i != qf.qf_pfix_len)
1459 goto jleave;
1460 sz += i;
1462 #ifdef HAVE_COLOUR
1463 if (cpre != NULL) {
1464 fputs(cpre, obuf);
1465 cpre = (char const*)0x1;
1467 #endif
1469 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1470 #ifdef HAVE_COLOUR
1471 if (c == '\n' && csuf != NULL) {
1472 cpre = (char const*)0x1;
1473 fputs(csuf, obuf);
1475 #endif
1476 putc(c, obuf);
1477 ++sz;
1478 --cnt;
1479 if (c == '\n')
1480 break;
1483 #ifdef HAVE_COLOUR
1484 if (csuf != NULL && cpre != (char const*)0x1)
1485 fputs(csuf, obuf);
1486 #endif
1487 } else {
1488 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1489 --cnt;
1490 if (c == '\n')
1491 break;
1495 if (sz > 0 && stats != NULL)
1496 *stats += sz;
1498 mpf = MIME_PARSE_NONE;
1499 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1500 mpf |= MIME_PARSE_PARTS | MIME_PARSE_DECRYPT;
1501 if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1502 goto jleave;
1504 rv = sendpart(mp, ip, obuf, doitp, &qf, action, stats, 0);
1505 jleave:
1506 quoteflt_destroy(&qf);
1507 NYD_LEAVE;
1508 return rv;
1511 /* s-it-mode */