Fix loop-stop when searching | pipe indicator for `source'+
[s-mailx.git] / send.c
blob4e6c39e031ca2a33717e26843daf3d244b6888fe
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 - 2017 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, 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("application/", cp)) {
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 to.l = delctrl(to.s, to.l);
167 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
168 free(to.s);
170 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
171 if (csuf != NULL)
172 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
173 NULL, NULL);
174 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
177 /* Content-Description */
178 if (n_ignore_is_ign(doitp, "content-description", 19) &&
179 (cp = mpp->m_content_description) != NULL && *cp != '\0') {
180 if (cpre != NULL)
181 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
182 NULL, NULL);
183 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
185 ti.l = strlen(ti.s = n_UNCONST(mpp->m_content_description));
186 mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV);
187 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
188 free(to.s);
190 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
191 if (csuf != NULL)
192 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
193 NULL, NULL);
194 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
197 /* Filename */
198 if (n_ignore_is_ign(doitp, "content-disposition", 19) &&
199 mpp->m_filename != NULL && *mpp->m_filename != '\0') {
200 if (cpre != NULL)
201 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
202 NULL, NULL);
203 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
205 ti.l = strlen(ti.s = mpp->m_filename);
206 makeprint(&ti, &to);
207 to.l = delctrl(to.s, to.l);
208 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
209 free(to.s);
211 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
212 if (csuf != NULL)
213 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
214 NULL, NULL);
215 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
217 NYD2_LEAVE;
220 static FILE *
221 _pipefile(struct mime_handler *mhp, struct mimepart const *mpp, FILE **qbuf,
222 char const *tmpname, int term_infd)
224 struct str s;
225 char const *env_addon[8], *cp, *sh;
226 FILE *rbuf;
227 NYD_ENTER;
229 rbuf = *qbuf;
231 if (mhp->mh_flags & MIME_HDL_ISQUOTE) {
232 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
233 NULL) {
234 n_perr(_("tmpfile"), 0);
235 *qbuf = rbuf;
239 if ((mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF) {
240 union {int (*ptf)(void); char const *sh;} u;
242 fflush(*qbuf);
243 if (*qbuf != stdout) /* xxx never? v15: it'll be a filter anyway */
244 fflush(stdout);
246 u.ptf = mhp->mh_ptf;
247 if((rbuf = Popen((char*)-1, "W", u.sh, NULL, fileno(*qbuf))) == NULL)
248 goto jerror;
249 goto jleave;
252 /* NAIL_FILENAME */
253 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
254 cp = n_empty;
255 env_addon[0] = str_concat_csvl(&s, NAILENV_FILENAME, "=", cp, NULL)->s;
257 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
258 * TODO a file wherever he wants! *Do* create a zero-size temporary file
259 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
260 * TODO the pipe returns? Like this we *can* verify path/name issues! */
261 env_addon[1] = str_concat_csvl(&s, NAILENV_FILENAME_GENERATED, "=",
262 getrandstring(n_MIN(NAME_MAX / 4, 16)), NULL)->s;
264 /* NAIL_CONTENT{,_EVIDENCE} */
265 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
266 cp = n_empty;
267 env_addon[2] = str_concat_csvl(&s, NAILENV_CONTENT, "=", cp, NULL)->s;
269 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
270 cp = mpp->m_ct_type_usr_ovwr;
271 env_addon[3] = str_concat_csvl(&s, NAILENV_CONTENT_EVIDENCE, "=", cp,
272 NULL)->s;
274 env_addon[4] = str_concat_csvl(&s, NAILENV_TMPDIR, /* TODO v15*/
275 "=", ok_vlook(TMPDIR), NULL)->s;
277 env_addon[5] = NULL;
279 /* NAIL_FILENAME_TEMPORARY? */
280 if (tmpname != NULL) {
281 env_addon[5] = str_concat_csvl(&s, NAILENV_FILENAME_TEMPORARY, "=",
282 tmpname, NULL)->s;
283 env_addon[6] = NULL;
286 sh = ok_vlook(SHELL);
288 if (mhp->mh_flags & MIME_HDL_NEEDSTERM) {
289 sigset_t nset;
290 int pid;
292 sigemptyset(&nset);
293 pid = run_command(sh, &nset, term_infd, COMMAND_FD_PASS, "-c",
294 mhp->mh_shell_cmd, NULL, env_addon);
295 rbuf = (pid < 0) ? NULL : (FILE*)-1;
296 } else {
297 rbuf = Popen(mhp->mh_shell_cmd, "W", sh, env_addon,
298 (mhp->mh_flags & MIME_HDL_ASYNC ? -1 : fileno(*qbuf)));
299 jerror:
300 if (rbuf == NULL)
301 n_err(_("Cannot run MIME type handler: %s: %s\n"),
302 mhp->mh_msg, strerror(errno));
303 else {
304 fflush(*qbuf);
305 if (*qbuf != stdout)
306 fflush(stdout);
309 jleave:
310 NYD_LEAVE;
311 return rbuf;
314 SINLINE ssize_t
315 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
316 sendaction action, struct quoteflt *qf, ui64_t *stats, struct str *outrest,
317 struct str *inrest)
319 ssize_t sz = 0, n;
320 int flags;
321 NYD_ENTER;
323 #if 0
324 Well ... it turns out to not work like that since of course a valid
325 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
326 after an empty line has been seen, which cannot be detected that easily
327 right here!
328 ifdef HAVE_DEBUG /* TODO assert legacy */
329 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
330 * TODO other input situations handle RFC 4155 OR, if newly generated,
331 * TODO enforce quoted-printable if there is From_, as "required" by
332 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
333 * TODO if it may happen in this path, we should just treat decryption
334 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
335 if (action == SEND_MBOX || action == SEND_DECRYPT)
336 assert(!is_head(buf, len, FAL0));
337 #else
338 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
339 is_head(buf, len, FAL0)) {
340 putc('>', fp);
341 ++sz;
343 #endif
345 flags = ((int)action & _TD_EOF);
346 action &= ~_TD_EOF;
347 n = mime_write(buf, len, fp,
348 action == SEND_MBOX ? CONV_NONE : convert,
349 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
350 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
351 ? TD_ISPR | TD_ICONV
352 : (action == SEND_TOSRCH || action == SEND_TOPIPE ||
353 action == SEND_TOFILE)
354 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
355 qf, outrest, inrest);
356 if (n < 0)
357 sz = n;
358 else if (n > 0) {
359 sz += n;
360 if (stats != NULL)
361 *stats += sz;
363 NYD_LEAVE;
364 return sz;
367 static void
368 _send_onpipe(int signo)
370 NYD_X; /* Signal handler */
371 n_UNUSED(signo);
372 siglongjmp(_send_pipejmp, 1);
375 static sigjmp_buf __sendp_actjmp; /* TODO someday.. */
376 static int __sendp_sig; /* TODO someday.. */
377 static sighandler_type __sendp_opipe;
378 static void
379 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
381 NYD_X; /* Signal handler */
382 __sendp_sig = sig;
383 siglongjmp(__sendp_actjmp, 1);
386 static int
387 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
388 struct n_ignore const *doitp, struct quoteflt *qf,
389 enum sendaction volatile action, ui64_t * volatile stats, int level)
391 int volatile rv = 0;
392 struct mime_handler mh;
393 struct str outrest, inrest;
394 char *line = NULL, *cp, *cp2, *start;
395 char const * volatile tmpname = NULL;
396 size_t linesize = 0, linelen, cnt;
397 int volatile term_infd;
398 int dostat, c;
399 struct mimepart *volatile np;
400 FILE * volatile ibuf = NULL, * volatile pbuf = obuf,
401 * volatile qbuf = obuf, *origobuf = obuf;
402 enum conversion volatile convert;
403 sighandler_type volatile oldpipe = SIG_DFL;
404 NYD_ENTER;
406 n_UNINIT(term_infd, 0);
407 n_UNINIT(cnt, 0);
409 quoteflt_reset(qf, obuf);
411 if (ip->m_mimecontent == MIME_PKCS7) {
412 if (ip->m_multipart &&
413 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
414 goto jheaders_skip;
417 dostat = 0;
418 if (level == 0) {
419 if (doitp != NULL) {
420 if (!n_ignore_is_ign(doitp, "status", 6))
421 dostat |= 1;
422 if (!n_ignore_is_ign(doitp, "x-status", 8))
423 dostat |= 2;
424 } else
425 dostat = 3;
427 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
428 rv = -1;
429 goto jleave;
431 cnt = ip->m_size;
433 if (ip->m_mimecontent == MIME_DISCARD)
434 goto jheaders_skip;
436 if (!(ip->m_flag & MNOFROM))
437 while (cnt && (c = getc(ibuf)) != EOF) {
438 cnt--;
439 if (c == '\n')
440 break;
442 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
443 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
444 action == SEND_TOSRCH)
445 ? CONV_FROMHDR : CONV_NONE;
447 /* Work the headers */
448 /* C99 */{
449 enum {
450 HPS_NONE = 0,
451 HPS_IN_FIELD = 1<<0,
452 HPS_IGNORE = 1<<1,
453 HPS_ISENC_1 = 1<<2,
454 HPS_ISENC_2 = 1<<3
455 } hps = HPS_NONE;
456 size_t lineno = 0;
458 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
459 ++lineno;
460 if (line[0] == '\n') {
461 /* If line is blank, we've reached end of headers, so force out
462 * status: field and note that we are no longer in header fields */
463 if (dostat & 1)
464 statusput(zmp, obuf, qf, stats);
465 if (dostat & 2)
466 xstatusput(zmp, obuf, qf, stats);
467 if (doitp != n_IGNORE_ALL)
468 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
469 break;
472 hps &= ~HPS_ISENC_1;
473 if ((hps & HPS_IN_FIELD) && blankchar(line[0])) {
474 /* If this line is a continuation (SP / HT) of a previous header
475 * field, determine if the start of the line is a MIME encoded word */
476 if (hps & HPS_ISENC_2) {
477 for (cp = line; blankchar(*cp); ++cp)
479 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
480 cp[0] == '=' && cp[1] == '?')
481 hps |= HPS_ISENC_1;
483 } else {
484 /* Pick up the header field if we have one */
485 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
487 cp2 = cp;
488 while (spacechar(*cp))
489 ++cp;
490 if (cp[0] != ':') {
491 if (lineno != 1)
492 n_err(_("Malformed message: headers and body not separated "
493 "(with empty line)\n"));
494 /* Not a header line, force out status: This happens in uucp style
495 * mail where there are no headers at all */
496 if (level == 0 /*&& lineno == 1*/) {
497 if (dostat & 1)
498 statusput(zmp, obuf, qf, stats);
499 if (dostat & 2)
500 xstatusput(zmp, obuf, qf, stats);
502 if (doitp != n_IGNORE_ALL)
503 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL,NULL);
504 break;
507 /* If it is an ignored field and we care about such things, skip it.
508 * Misuse dostat also for another bit xxx use a bitenum + for more */
509 if (ok_blook(keep_content_length))
510 dostat |= 1 << 2;
511 c = *cp2;
512 *cp2 = 0; /* temporarily null terminate */
513 if ((doitp != NULL &&
514 n_ignore_is_ign(doitp, line, PTR2SIZE(cp2 - line))) ||
515 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
516 (!asccasecmp(line, "content-length") ||
517 !asccasecmp(line, "lines"))))
518 hps |= HPS_IGNORE;
519 else if (!asccasecmp(line, "status")) {
520 /* If field is "status," go compute and print real Status: field */
521 if (dostat & 1) {
522 statusput(zmp, obuf, qf, stats);
523 dostat &= ~1;
524 hps |= HPS_IGNORE;
526 } else if (!asccasecmp(line, "x-status")) {
527 /* If field is "status," go compute and print real Status: field */
528 if (dostat & 2) {
529 xstatusput(zmp, obuf, qf, stats);
530 dostat &= ~2;
531 hps |= HPS_IGNORE;
533 } else {
534 hps &= ~HPS_IGNORE;
535 /* For colourization we need the complete line, so save it */
536 /* XXX This is all temporary (colour belongs into backend), so
537 * XXX use tmpname as a temporary storage in the meanwhile */
538 #ifdef HAVE_COLOUR
539 if (pstate & PS_COLOUR_ACTIVE)
540 tmpname = savestrbuf(line, PTR2SIZE(cp2 - line));
541 #endif
543 *cp2 = c;
544 dostat &= ~(1 << 2);
545 hps |= HPS_IN_FIELD;
548 /* Determine if the end of the line is a MIME encoded word */
549 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
550 * TODO with header follow lines, and it should be up to the backend
551 * TODO what happens and what not, i.e., it doesn't matter whether it's
552 * TODO a MIME-encoded word or not, as long as a single separating space
553 * TODO remains in between lines (the MIME stuff will correctly remove
554 * TODO whitespace in between multiple adjacent encoded words) */
555 hps &= ~HPS_ISENC_2;
556 if (cnt && (c = getc(ibuf)) != EOF) {
557 if (blankchar(c)) {
558 cp = line + linelen - 1;
559 if (linelen > 0 && *cp == '\n')
560 --cp;
561 while (cp >= line && whitechar(*cp))
562 --cp;
563 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
564 hps |= HPS_ISENC_2;
566 ungetc(c, ibuf);
569 if (!(hps & HPS_IGNORE)) {
570 size_t len = linelen;
571 start = line;
572 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
573 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
574 action == SEND_TOSRCH) {
575 /* Strip blank characters if two MIME-encoded words follow on
576 * continuing lines */
577 if (hps & HPS_ISENC_1)
578 while (len > 0 && blankchar(*start)) {
579 ++start;
580 --len;
582 if (hps & HPS_ISENC_2)
583 if (len > 0 && start[len - 1] == '\n')
584 --len;
585 while (len > 0 && blankchar(start[len - 1]))
586 --len;
588 #ifdef HAVE_COLOUR
590 bool_t colour_stripped = FAL0;
591 if (tmpname != NULL) {
592 n_colour_put(obuf, n_COLOUR_ID_VIEW_HEADER, tmpname);
593 if (len > 0 && start[len - 1] == '\n') {
594 colour_stripped = TRU1;
595 --len;
598 #endif
599 _out(start, len, obuf, convert, action, qf, stats, NULL,NULL);
600 #ifdef HAVE_COLOUR
601 if (tmpname != NULL) {
602 n_colour_reset(obuf);
603 if (colour_stripped)
604 putc('\n', obuf);
607 #endif
608 if (ferror(obuf)) {
609 free(line);
610 rv = -1;
611 goto jleave;
615 } /* C99 */
616 quoteflt_flush(qf);
617 free(line);
618 line = NULL;
619 tmpname = NULL;
621 jheaders_skip:
622 memset(&mh, 0, sizeof mh);
624 switch (ip->m_mimecontent) {
625 case MIME_822:
626 switch (action) {
627 case SEND_TODISP:
628 case SEND_TODISP_ALL:
629 case SEND_QUOTE:
630 case SEND_QUOTE_ALL:
631 if (ok_blook(rfc822_body_from_)) {
632 if (qf->qf_pfix_len > 0) {
633 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
634 qf->qf_pfix_len, obuf);
635 if (i == qf->qf_pfix_len && stats != NULL)
636 *stats += i;
638 put_from_(obuf, ip->m_multipart, stats);
640 /* FALLTHRU */
641 case SEND_TOSRCH:
642 case SEND_DECRYPT:
643 goto jmulti;
644 case SEND_TOFILE:
645 case SEND_TOPIPE:
646 if (ok_blook(rfc822_body_from_))
647 put_from_(obuf, ip->m_multipart, stats);
648 /* FALLTHRU */
649 case SEND_MBOX:
650 case SEND_RFC822:
651 case SEND_SHOW:
652 break;
654 break;
655 case MIME_TEXT_HTML:
656 case MIME_TEXT:
657 case MIME_TEXT_PLAIN:
658 switch (action) {
659 case SEND_TODISP:
660 case SEND_TODISP_ALL:
661 case SEND_QUOTE:
662 case SEND_QUOTE_ALL:
663 switch (mime_type_handler(&mh, ip, action)) {
664 case MIME_HDL_MSG:
665 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
666 stats, NULL, NULL);
667 /* We would print this as plain text, so better force going home */
668 goto jleave;
669 default:
670 break;
672 /* FALLTRHU */
673 default:
674 break;
676 break;
677 case MIME_DISCARD:
678 if (action != SEND_DECRYPT)
679 goto jleave;
680 break;
681 case MIME_PKCS7:
682 if (action != SEND_MBOX && action != SEND_RFC822 &&
683 action != SEND_SHOW && ip->m_multipart != NULL)
684 goto jmulti;
685 /* FALLTHRU */
686 default:
687 switch (action) {
688 case SEND_TODISP:
689 case SEND_TODISP_ALL:
690 case SEND_QUOTE:
691 case SEND_QUOTE_ALL:
692 switch (mime_type_handler(&mh, ip, action)) {
693 case MIME_HDL_MSG:
694 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
695 stats, NULL, NULL);
696 /* We would print this as plain text, so better force going home */
697 goto jleave;
698 case MIME_HDL_CMD:
699 /* FIXME WE NEED TO DO THAT IF WE ARE THE ONLY MAIL
700 * FIXME CONTENT !! */
701 case MIME_HDL_TEXT:
702 break;
703 default:
704 case MIME_HDL_NULL:
705 if (level == 0 && cnt) {
706 char const *x = _("[-- Binary content --]\n");
707 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
708 NULL,NULL);
710 goto jleave;
712 break;
713 case SEND_TOFILE:
714 case SEND_TOPIPE:
715 case SEND_TOSRCH:
716 case SEND_DECRYPT:
717 case SEND_MBOX:
718 case SEND_RFC822:
719 case SEND_SHOW:
720 break;
722 break;
723 case MIME_ALTERNATIVE:
724 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
725 !ok_blook(print_alternatives)) {
726 /* XXX This (a) should not remain (b) should be own fun
727 * TODO (despite the fact that v15 will do this completely differently
728 * TODO by having an action-specific "manager" that will traverse the
729 * TODO parsed MIME tree and decide for each part whether it'll be
730 * TODO displayed or not *before* we walk the tree for doing action */
731 struct mpstack {
732 struct mpstack *outer;
733 struct mimepart *mp;
734 } outermost, * volatile curr, * volatile mpsp;
735 bool_t volatile neednl, hadpart;
736 struct n_sigman smalter;
738 (curr = &outermost)->outer = NULL;
739 curr->mp = ip;
740 neednl = hadpart = FAL0;
742 n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
743 case 0:
744 break;
745 default:
746 rv = -1;
747 goto jalter_leave;
750 for (np = ip->m_multipart;;) {
751 jalter_redo:
752 for (; np != NULL; np = np->m_nextpart) {
753 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
754 if (neednl)
755 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats,
756 NULL, NULL);
757 _print_part_info(obuf, np, doitp, level, qf, stats);
759 neednl = TRU1;
761 switch (np->m_mimecontent) {
762 case MIME_ALTERNATIVE:
763 case MIME_RELATED:
764 case MIME_DIGEST:
765 case MIME_SIGNED:
766 case MIME_ENCRYPTED:
767 case MIME_MULTI:
768 mpsp = salloc(sizeof *mpsp);
769 mpsp->outer = curr;
770 mpsp->mp = np->m_multipart;
771 curr->mp = np;
772 curr = mpsp;
773 np = mpsp->mp;
774 neednl = FAL0;
775 goto jalter_redo;
776 default:
777 if (hadpart)
778 break;
779 switch (mime_type_handler(&mh, np, action)) {
780 default:
781 mh.mh_flags = MIME_HDL_NULL;
782 continue; /* break; break; */
783 case MIME_HDL_PTF:
784 if (!ok_blook(mime_alternative_favour_rich)) {/* TODO */
785 struct mimepart *x = np;
787 while ((x = x->m_nextpart) != NULL) {
788 struct mime_handler mhx;
790 if (x->m_mimecontent == MIME_TEXT_PLAIN ||
791 mime_type_handler(&mhx, x, action) ==
792 MIME_HDL_TEXT)
793 break;
795 if (x != NULL)
796 continue; /* break; break; */
797 goto jalter_plain;
799 /* FALLTHRU */
800 case MIME_HDL_TEXT:
801 break;
803 /* FALLTHRU */
804 case MIME_TEXT_PLAIN:
805 if (hadpart)
806 break;
807 if (ok_blook(mime_alternative_favour_rich)) { /* TODO */
808 struct mimepart *x = np;
810 /* TODO twice TODO, we should dive into /related and
811 * TODO check whether that has rich parts! */
812 while ((x = x->m_nextpart) != NULL) {
813 struct mime_handler mhx;
815 switch (mime_type_handler(&mhx, x, action)) {
816 case MIME_HDL_PTF:
817 break;
818 default:
819 continue;
821 break;
823 if (x != NULL)
824 continue; /* break; break; */
826 jalter_plain:
827 quoteflt_flush(qf);
828 if (action == SEND_QUOTE && hadpart) {
829 struct quoteflt *dummy = quoteflt_dummy();
830 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
831 NULL,NULL);
832 quoteflt_flush(dummy);
834 hadpart = TRU1;
835 neednl = FAL0;
836 rv = sendpart(zmp, np, obuf, doitp, qf, action, stats,
837 level + 1);
838 quoteflt_reset(qf, origobuf);
840 if (rv < 0)
841 curr = &outermost; /* Cause overall loop termination */
842 break;
846 mpsp = curr->outer;
847 if (mpsp == NULL)
848 break;
849 curr = mpsp;
850 np = curr->mp->m_nextpart;
852 jalter_leave:
853 n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
854 goto jleave;
856 /* FALLTHRU */
857 case MIME_RELATED:
858 case MIME_DIGEST:
859 case MIME_SIGNED:
860 case MIME_ENCRYPTED:
861 case MIME_MULTI:
862 switch (action) {
863 case SEND_TODISP:
864 case SEND_TODISP_ALL:
865 case SEND_QUOTE:
866 case SEND_QUOTE_ALL:
867 case SEND_TOFILE:
868 case SEND_TOPIPE:
869 case SEND_TOSRCH:
870 case SEND_DECRYPT:
871 jmulti:
872 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
873 ip->m_multipart != NULL &&
874 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
875 ip->m_multipart->m_nextpart == NULL) {
876 char const *x = _("[Missing multipart boundary - use show "
877 "to display the raw message]\n");
878 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
879 NULL,NULL);
882 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
883 bool_t volatile ispipe;
885 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
886 continue;
888 ispipe = FAL0;
889 switch (action) {
890 case SEND_TOFILE:
891 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
892 break;
893 stats = NULL;
894 /* TODO Always open multipart on /dev/null, it's a hack to be
895 * TODO able to dive into that structure, and still better
896 * TODO than asking the user for something stupid.
897 * TODO oh, wait, we did ask for a filename for this MIME mail,
898 * TODO and that outer container is useless anyway ;-P */
899 if (np->m_multipart != NULL) {
900 if ((obuf = Fopen("/dev/null", "w")) == NULL)
901 continue;
902 } else if ((obuf = newfile(np, &ispipe)) == NULL)
903 continue;
904 if (!ispipe)
905 break;
906 if (sigsetjmp(_send_pipejmp, 1)) {
907 rv = -1;
908 goto jpipe_close;
910 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
911 break;
912 case SEND_TODISP:
913 case SEND_TODISP_ALL:
914 if (ip->m_mimecontent != MIME_ALTERNATIVE &&
915 ip->m_mimecontent != MIME_RELATED &&
916 ip->m_mimecontent != MIME_DIGEST &&
917 ip->m_mimecontent != MIME_SIGNED &&
918 ip->m_mimecontent != MIME_ENCRYPTED &&
919 ip->m_mimecontent != MIME_MULTI)
920 break;
921 _print_part_info(obuf, np, doitp, level, qf, stats);
922 break;
923 case SEND_QUOTE:
924 case SEND_QUOTE_ALL:
925 case SEND_MBOX:
926 case SEND_RFC822:
927 case SEND_SHOW:
928 case SEND_TOSRCH:
929 case SEND_DECRYPT:
930 case SEND_TOPIPE:
931 break;
934 quoteflt_flush(qf);
935 if ((action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
936 np->m_multipart == NULL && ip->m_parent != NULL) {
937 struct quoteflt *dummy = quoteflt_dummy();
938 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
939 NULL,NULL);
940 quoteflt_flush(dummy);
942 if (sendpart(zmp, np, obuf, doitp, qf, action, stats, level+1) < 0)
943 rv = -1;
944 quoteflt_reset(qf, origobuf);
946 if (action == SEND_QUOTE) {
947 if (ip->m_mimecontent != MIME_RELATED)
948 break;
950 if (action == SEND_TOFILE && obuf != origobuf) {
951 if (!ispipe)
952 Fclose(obuf);
953 else {
954 jpipe_close:
955 safe_signal(SIGPIPE, SIG_IGN);
956 Pclose(obuf, TRU1);
957 safe_signal(SIGPIPE, oldpipe);
961 goto jleave;
962 case SEND_MBOX:
963 case SEND_RFC822:
964 case SEND_SHOW:
965 break;
967 break;
970 /* Copy out message body */
971 if (doitp == n_IGNORE_ALL && level == 0) /* skip final blank line */
972 --cnt;
973 switch (ip->m_mime_enc) {
974 case MIMEE_BIN:
975 case MIMEE_7B:
976 case MIMEE_8B:
977 convert = CONV_NONE;
978 break;
979 case MIMEE_QP:
980 convert = CONV_FROMQP;
981 break;
982 case MIMEE_B64:
983 switch (ip->m_mimecontent) {
984 case MIME_TEXT:
985 case MIME_TEXT_PLAIN:
986 case MIME_TEXT_HTML:
987 convert = CONV_FROMB64_T;
988 break;
989 default:
990 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
991 case MIME_HDL_TEXT:
992 case MIME_HDL_PTF:
993 convert = CONV_FROMB64_T;
994 break;
995 default:
996 convert = CONV_FROMB64;
997 break;
999 break;
1001 break;
1002 default:
1003 convert = CONV_NONE;
1006 /* TODO Unless we have filters, ensure iconvd==-1 so that mime.c:fwrite_td()
1007 * TODO cannot mess things up misusing outrest as line buffer */
1008 #ifdef HAVE_ICONV
1009 if (iconvd != (iconv_t)-1) {
1010 n_iconv_close(iconvd);
1011 iconvd = (iconv_t)-1;
1013 #endif
1015 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1016 action == SEND_RFC822 || action == SEND_SHOW)
1017 convert = CONV_NONE;
1018 #ifdef HAVE_ICONV
1019 else if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1020 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1021 action == SEND_TOSRCH || action == SEND_TOFILE) &&
1022 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1023 ip->m_mimecontent == MIME_TEXT_HTML ||
1024 ip->m_mimecontent == MIME_TEXT ||
1025 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_TEXT ||
1026 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF)) {
1027 char const *tcs;
1029 tcs = ok_vlook(ttycharset);
1030 if (asccasecmp(tcs, ip->m_charset) &&
1031 asccasecmp(ok_vlook(charset_7bit), ip->m_charset)) {
1032 iconvd = n_iconv_open(tcs, ip->m_charset);
1033 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1034 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
1035 /*rv = 1; goto jleave;*/
1039 #endif
1041 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
1042 case MIME_HDL_CMD:
1043 case MIME_HDL_PTF:
1044 tmpname = NULL;
1045 qbuf = obuf;
1047 term_infd = COMMAND_FD_PASS;
1048 if (mh.mh_flags & (MIME_HDL_TMPF | MIME_HDL_NEEDSTERM)) {
1049 enum oflags of;
1051 of = OF_RDWR | OF_REGISTER;
1052 if (!(mh.mh_flags & MIME_HDL_TMPF)) {
1053 term_infd = 0;
1054 mh.mh_flags |= MIME_HDL_TMPF_FILL;
1055 of |= OF_UNLINK;
1056 } else if (mh.mh_flags & MIME_HDL_TMPF_UNLINK)
1057 of |= OF_REGISTER_UNLINK;
1059 if ((pbuf = Ftmp((mh.mh_flags & MIME_HDL_TMPF ? &cp : NULL),
1060 (mh.mh_flags & MIME_HDL_TMPF_FILL ? "mimehdlfill" : "mimehdl"),
1061 of)) == NULL)
1062 goto jesend;
1064 if (mh.mh_flags & MIME_HDL_TMPF) {
1065 tmpname = savestr(cp);
1066 Ftmp_free(&cp);
1069 if (mh.mh_flags & MIME_HDL_TMPF_FILL) {
1070 if (term_infd == 0)
1071 term_infd = fileno(pbuf);
1072 goto jsend;
1076 jpipe_for_real:
1077 pbuf = _pipefile(&mh, ip, n_UNVOLATILE(&qbuf), tmpname, term_infd);
1078 if (pbuf == NULL) {
1079 jesend:
1080 pbuf = qbuf = NULL;
1081 rv = -1;
1082 goto jend;
1083 } else if ((mh.mh_flags & MIME_HDL_NEEDSTERM) && pbuf == (FILE*)-1) {
1084 pbuf = qbuf = NULL;
1085 goto jend;
1087 tmpname = NULL;
1088 action = SEND_TOPIPE;
1089 if (pbuf != qbuf) {
1090 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1091 if (sigsetjmp(_send_pipejmp, 1))
1092 goto jend;
1094 break;
1096 default:
1097 mh.mh_flags = MIME_HDL_NULL;
1098 pbuf = qbuf = obuf;
1099 break;
1102 jsend:
1104 bool_t volatile eof;
1105 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1106 ui64_t *save_stats = stats;
1108 if (pbuf != origobuf) {
1109 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1110 stats = NULL;
1112 eof = FAL0;
1113 outrest.s = inrest.s = NULL;
1114 outrest.l = inrest.l = 0;
1116 if (pbuf == qbuf) {
1117 __sendp_sig = 0;
1118 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1119 if (sigsetjmp(__sendp_actjmp, 1)) {
1120 if (outrest.s != NULL)
1121 free(outrest.s);
1122 if (inrest.s != NULL)
1123 free(inrest.s);
1124 free(line);
1125 #ifdef HAVE_ICONV
1126 if (iconvd != (iconv_t)-1)
1127 n_iconv_close(iconvd);
1128 #endif
1129 safe_signal(SIGPIPE, __sendp_opipe);
1130 n_raise(__sendp_sig);
1134 quoteflt_reset(qf, pbuf);
1135 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1136 joutln:
1137 if (_out(line, linelen, pbuf, convert, action, qf, stats, &outrest,
1138 (action & _TD_EOF ? NULL : &inrest)) < 0 || ferror(pbuf)) {
1139 rv = -1; /* XXX Should bail away?! */
1140 break;
1143 if(eof <= FAL0 && rv >= 0 && (outrest.l != 0 || inrest.l != 0)){
1144 linelen = 0;
1145 if(eof || inrest.l == 0)
1146 action |= _TD_EOF;
1147 eof = eof ? TRU1 : TRUM1;
1148 goto joutln;
1150 action &= ~_TD_EOF;
1152 /* TODO HACK: when sending to the display we yet get fooled if a message
1153 * TODO doesn't end in a newline, because of our input/output 1:1.
1154 * TODO This should be handled automatically by a display filter, then */
1155 if(rv >= 0 && !qf->qf_nl_last &&
1156 (action == SEND_TODISP || action == SEND_TODISP_ALL))
1157 rv = quoteflt_push(qf, "\n", 1);
1159 quoteflt_flush(qf);
1161 if (rv >= 0 && (mh.mh_flags & MIME_HDL_TMPF_FILL)) {
1162 mh.mh_flags &= ~MIME_HDL_TMPF_FILL;
1163 fflush(pbuf);
1164 really_rewind(pbuf);
1165 /* Don't Fclose() the Ftmp() thing due to OF_REGISTER_UNLINK++ */
1166 goto jpipe_for_real;
1169 if (pbuf == qbuf)
1170 safe_signal(SIGPIPE, __sendp_opipe);
1172 if (outrest.s != NULL)
1173 free(outrest.s);
1174 if (inrest.s != NULL)
1175 free(inrest.s);
1177 if (pbuf != origobuf) {
1178 qf->qf_pfix_len = save_qf_pfix_len;
1179 stats = save_stats;
1183 jend:
1184 if (line != NULL)
1185 free(line);
1186 if (pbuf != qbuf) {
1187 safe_signal(SIGPIPE, SIG_IGN);
1188 Pclose(pbuf, !(mh.mh_flags & MIME_HDL_ASYNC));
1189 safe_signal(SIGPIPE, oldpipe);
1190 if (rv >= 0 && qbuf != NULL && qbuf != obuf)
1191 pipecpy(qbuf, obuf, origobuf, qf, stats);
1193 #ifdef HAVE_ICONV
1194 if (iconvd != (iconv_t)-1)
1195 n_iconv_close(iconvd);
1196 #endif
1197 jleave:
1198 NYD_LEAVE;
1199 return rv;
1202 static FILE *
1203 newfile(struct mimepart *ip, bool_t volatile *ispipe)
1205 struct str in, out;
1206 char *f;
1207 FILE *fp;
1208 NYD_ENTER;
1210 f = ip->m_filename;
1211 *ispipe = FAL0;
1213 if (f != NULL && f != (char*)-1) {
1214 in.s = f;
1215 in.l = strlen(f);
1216 makeprint(&in, &out);
1217 out.l = delctrl(out.s, out.l);
1218 f = savestrbuf(out.s, out.l);
1219 free(out.s);
1222 /* In interactive mode, let user perform all kind of expansions as desired,
1223 * and offer |SHELL-SPEC pipe targets, too */
1224 if (options & OPT_INTERACTIVE) {
1225 struct str prompt;
1226 struct n_string shou, *shoup;
1227 char *f2, *f3;
1229 shoup = n_string_creat_auto(&shou);
1231 /* TODO Generic function which asks for filename.
1232 * TODO If the current part is the first textpart the target
1233 * TODO is implicit from outer `write' etc! */
1234 /* I18N: Filename input prompt with file type indication */
1235 str_concat_csvl(&prompt, _("Enter filename for part "),
1236 (ip->m_partstring != NULL) ? ip->m_partstring : _("?"),
1237 _(" ("), ip->m_ct_type_plain, _("): "), NULL);
1238 jgetname:
1239 f2 = n_lex_input_cp(n_LEXINPUT_CTX_DEFAULT | n_LEXINPUT_HIST_ADD,
1240 prompt.s, ((f != (char*)-1 && f != NULL)
1241 ? n_shexp_quote_cp(f, FAL0) : NULL));
1242 if(f2 != NULL){
1243 in.s = n_UNCONST(f2);
1244 in.l = UIZ_MAX;
1245 if((n_shexp_parse_token(shoup, &in, n_SHEXP_PARSE_TRUNC |
1246 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
1247 n_SHEXP_PARSE_IGNORE_EMPTY) &
1248 (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT |
1249 n_SHEXP_STATE_ERR_MASK)
1250 ) != (n_SHEXP_STATE_STOP | n_SHEXP_STATE_OUTPUT))
1251 goto jgetname;
1252 f2 = n_string_cp(shoup);
1254 if (f2 == NULL || *f2 == '\0') {
1255 if (options & OPT_D_V)
1256 n_err(_("... skipping this\n"));
1257 n_string_gut(shoup);
1258 fp = NULL;
1259 goto jleave;
1262 if (*f2 == '|')
1263 /* Pipes are expanded by the shell */
1264 f = f2;
1265 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1266 /* (Error message written by fexpand()) */
1267 goto jgetname;
1268 else
1269 f = f3;
1271 n_string_gut(shoup);
1274 if (f == NULL || f == (char*)-1 || *f == '\0')
1275 fp = NULL;
1276 else if (options & OPT_INTERACTIVE) {
1277 if (*f == '|') {
1278 fp = Popen(&f[1], "w", ok_vlook(SHELL), NULL, 1);
1279 if (!(*ispipe = (fp != NULL)))
1280 n_perr(f, 0);
1281 } else if ((fp = Fopen(f, "w")) == NULL)
1282 n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
1283 } else {
1284 /* Be very picky in non-interactive mode: actively disallow pipes,
1285 * prevent directory separators, and any filename member that would
1286 * become expanded by the shell if the name would be echo(1)ed */
1287 if(anyof(f, "/" n_SHEXP_MAGIC_PATH_CHARS)){
1288 char c;
1290 for(out.s = salloc((strlen(f) * 3) +1), out.l = 0; (c = *f++) != '\0';)
1291 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
1292 out.s[out.l++] = '%';
1293 n_c_to_hex_base16(&out.s[out.l], c);
1294 out.l += 2;
1295 }else
1296 out.s[out.l++] = c;
1297 out.s[out.l] = '\0';
1298 f = out.s;
1301 /* Avoid overwriting of existing files */
1302 while((fp = Fopen(f, "wx")) == NULL){
1303 int e;
1305 if((e = errno) != EEXIST){
1306 n_err(_("Cannot open %s: %s\n"),
1307 n_shexp_quote_cp(f, FAL0), strerror(e));
1308 break;
1311 if(ip->m_partstring != NULL)
1312 f = savecatsep(f, '#', ip->m_partstring);
1313 else
1314 f = savecat(f, "#.");
1317 jleave:
1318 NYD_LEAVE;
1319 return fp;
1322 static void
1323 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1324 ui64_t *stats)
1326 char *line = NULL; /* TODO line pool */
1327 size_t linesize = 0, linelen, cnt;
1328 ssize_t all_sz, sz;
1329 NYD_ENTER;
1331 fflush(pipebuf);
1332 rewind(pipebuf);
1333 cnt = (size_t)fsize(pipebuf);
1334 all_sz = 0;
1336 quoteflt_reset(qf, outbuf);
1337 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1338 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1339 break;
1340 all_sz += sz;
1342 if ((sz = quoteflt_flush(qf)) > 0)
1343 all_sz += sz;
1344 if (line)
1345 free(line);
1347 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1348 *stats += all_sz;
1349 Fclose(pipebuf);
1350 NYD_LEAVE;
1353 static void
1354 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1355 ui64_t *stats)
1357 char statout[3], *cp = statout;
1358 NYD_ENTER;
1360 if (mp->m_flag & MREAD)
1361 *cp++ = 'R';
1362 if (!(mp->m_flag & MNEW))
1363 *cp++ = 'O';
1364 *cp = 0;
1365 if (statout[0]) {
1366 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1367 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1368 if (i > 0 && stats != NULL)
1369 *stats += i;
1371 NYD_LEAVE;
1374 static void
1375 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1376 ui64_t *stats)
1378 char xstatout[4];
1379 char *xp = xstatout;
1380 NYD_ENTER;
1382 if (mp->m_flag & MFLAGGED)
1383 *xp++ = 'F';
1384 if (mp->m_flag & MANSWERED)
1385 *xp++ = 'A';
1386 if (mp->m_flag & MDRAFTED)
1387 *xp++ = 'T';
1388 *xp = 0;
1389 if (xstatout[0]) {
1390 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1391 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1392 if (i > 0 && stats != NULL)
1393 *stats += i;
1395 NYD_LEAVE;
1398 static void
1399 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1401 char const *froma, *date, *nl;
1402 int i;
1403 NYD_ENTER;
1405 if (ip != NULL && ip->m_from != NULL) {
1406 froma = ip->m_from;
1407 date = fakedate(ip->m_time);
1408 nl = "\n";
1409 } else {
1410 froma = ok_vlook(LOGNAME);
1411 date = time_current.tc_ctime;
1412 nl = n_empty;
1415 n_COLOUR( n_colour_put(fp, n_COLOUR_ID_VIEW_FROM_, NULL); )
1416 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1417 n_COLOUR( n_colour_reset(fp); )
1418 if (i > 0 && stats != NULL)
1419 *stats += i;
1420 NYD_LEAVE;
1423 FL int
1424 sendmp(struct message *mp, FILE *obuf, struct n_ignore const *doitp,
1425 char const *prefix, enum sendaction action, ui64_t *stats)
1427 struct quoteflt qf;
1428 size_t cnt, sz, i;
1429 FILE *ibuf;
1430 enum mime_parse_flags mpf;
1431 struct mimepart *ip;
1432 int rv = -1, c;
1433 NYD_ENTER;
1435 time_current_update(&time_current, TRU1);
1437 if (mp == dot && action != SEND_TOSRCH)
1438 pstate |= PS_DID_PRINT_DOT;
1439 if (stats != NULL)
1440 *stats = 0;
1441 quoteflt_init(&qf, prefix);
1443 /* First line is the From_ line, so no headers there to worry about */
1444 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1445 goto jleave;
1447 cnt = mp->m_size;
1448 sz = 0;
1450 bool_t nozap;
1451 char const *cpre = n_empty, *csuf = n_empty;
1452 #ifdef HAVE_COLOUR
1453 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_FROM_,NULL);
1454 struct str const *sp = n_colour_pen_to_str(cpen);
1456 if (sp != NULL) {
1457 cpre = sp->s;
1458 sp = n_colour_reset_to_str();
1459 if (sp != NULL)
1460 csuf = sp->s;
1462 #endif
1464 nozap = (doitp != n_IGNORE_ALL && doitp != n_IGNORE_FWD &&
1465 action != SEND_RFC822 &&
1466 !n_ignore_is_ign(doitp, "from_", sizeof("from_") -1));
1467 if (mp->m_flag & MNOFROM) {
1468 if (nozap)
1469 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1470 cpre, (int)qf.qf_pfix_len,
1471 (qf.qf_pfix_len != 0 ? qf.qf_pfix : n_empty), fakefrom(mp),
1472 fakedate(mp->m_time), csuf);
1473 } else if (nozap) {
1474 if (qf.qf_pfix_len > 0) {
1475 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1476 if (i != qf.qf_pfix_len)
1477 goto jleave;
1478 sz += i;
1480 #ifdef HAVE_COLOUR
1481 if (cpre != NULL) {
1482 fputs(cpre, obuf);
1483 cpre = (char const*)0x1;
1485 #endif
1487 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1488 #ifdef HAVE_COLOUR
1489 if (c == '\n' && csuf != NULL) {
1490 cpre = (char const*)0x1;
1491 fputs(csuf, obuf);
1493 #endif
1494 putc(c, obuf);
1495 ++sz;
1496 --cnt;
1497 if (c == '\n')
1498 break;
1501 #ifdef HAVE_COLOUR
1502 if (csuf != NULL && cpre != (char const*)0x1)
1503 fputs(csuf, obuf);
1504 #endif
1505 } else {
1506 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1507 --cnt;
1508 if (c == '\n')
1509 break;
1513 if (sz > 0 && stats != NULL)
1514 *stats += sz;
1516 mpf = MIME_PARSE_NONE;
1517 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1518 mpf |= MIME_PARSE_PARTS | MIME_PARSE_DECRYPT;
1519 if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1520 goto jleave;
1522 rv = sendpart(mp, ip, obuf, doitp, &qf, action, stats, 0);
1523 jleave:
1524 quoteflt_destroy(&qf);
1525 NYD_LEAVE;
1526 return rv;
1529 /* s-it-mode */