mk-release.inc: auto-append checksum file to announcement mail
[s-mailx.git] / send.c
blob4bf40632c62883101751734f79a958a4634668a7
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 *rest);
60 /* SIGPIPE handler */
61 static void _send_onpipe(int signo);
63 /* Send one part */
64 static int sendpart(struct message *zmp, struct mimepart *ip,
65 FILE *obuf, struct ignoretab *doign,
66 struct quoteflt *qf, enum sendaction action,
67 ui64_t *stats, int level);
69 /* Get a file for an attachment */
70 static FILE * newfile(struct mimepart *ip, bool_t volatile *ispipe);
72 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
73 struct quoteflt *qf, ui64_t *stats);
75 /* Output a reasonable looking status field */
76 static void statusput(const struct message *mp, FILE *obuf,
77 struct quoteflt *qf, ui64_t *stats);
78 static void xstatusput(const struct message *mp, FILE *obuf,
79 struct quoteflt *qf, ui64_t *stats);
81 static void put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats);
83 static void
84 _print_part_info(FILE *obuf, struct mimepart const *mpp, /* TODO strtofmt.. */
85 struct ignoretab *doign, int level, struct quoteflt *qf, ui64_t *stats)
87 char buf[64];
88 struct str ti = {NULL, 0}, to;
89 struct str const *cpre, *csuf;
90 char const *cp;
91 NYD2_ENTER;
93 #ifdef HAVE_COLOUR
95 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_PARTINFO,
96 NULL);
97 if ((cpre = n_colour_pen_to_str(cpen)) != NULL)
98 csuf = n_colour_reset_to_str();
99 else
100 csuf = NULL;
102 #else
103 cpre = csuf = NULL;
104 #endif
106 /* Take care of "99.99", i.e., 5 */
107 if ((cp = mpp->m_partstring) == NULL || cp[0] == '\0')
108 cp = "?";
109 if (level || (cp[0] != '1' && cp[1] == '\0'))
110 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
111 if (cpre != NULL)
112 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
113 _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
114 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
116 to.l = snprintf(buf, sizeof buf, " %" PRIuZ "/%" PRIuZ " ",
117 (uiz_t)mpp->m_lines, (uiz_t)mpp->m_size);
118 _out(buf, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
120 if ((cp = mpp->m_ct_type_usr_ovwr) != NULL)
121 _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
122 else
123 cp = mpp->m_ct_type_plain;
124 if ((to.l = strlen(cp)) > 30 && is_asccaseprefix(cp, "application/")) {
125 size_t const al = sizeof("appl../") -1, fl = sizeof("application/") -1;
126 size_t i = to.l - fl;
127 char *x = salloc(al + i +1);
129 memcpy(x, "appl../", al);
130 memcpy(x + al, cp + fl, i +1);
131 cp = x;
132 to.l = al + i;
134 _out(cp, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
136 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_ct_enc) != NULL) {
137 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
138 if (to.l > 25 && !asccasecmp(cp, "quoted-printable"))
139 cp = "qu.-pr.";
140 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
143 if (mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_charset) != NULL) {
144 _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
145 _out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
148 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
149 if (csuf != NULL)
150 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
151 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
153 if (is_ign("content-disposition", 19, doign) && mpp->m_filename != NULL &&
154 *mpp->m_filename != '\0') {
155 makeprint(n_str_add_cp(&ti, mpp->m_filename), &to);
156 free(ti.s);
157 to.l = delctrl(to.s, to.l);
159 if (cpre != NULL)
160 _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
161 _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
162 _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
163 _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
164 if (csuf != NULL)
165 _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
166 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
168 free(to.s);
170 NYD2_LEAVE;
173 static FILE *
174 _pipefile(struct mime_handler *mhp, struct mimepart const *mpp, FILE **qbuf,
175 char const *tmpname, int term_infd)
177 struct str s;
178 char const *env_addon[8], *cp, *sh;
179 FILE *rbuf;
180 NYD_ENTER;
182 rbuf = *qbuf;
184 if (mhp->mh_flags & MIME_HDL_ISQUOTE) {
185 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
186 NULL) {
187 n_perr(_("tmpfile"), 0);
188 *qbuf = rbuf;
192 if ((mhp->mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF) {
193 union {int (*ptf)(void); char const *sh;} u;
195 fflush(*qbuf);
196 if (*qbuf != stdout) /* xxx never? v15: it'll be a filter anyway */
197 fflush(stdout);
199 u.ptf = mhp->mh_ptf;
200 if((rbuf = Popen((char*)-1, "W", u.sh, NULL, fileno(*qbuf))) == NULL)
201 goto jerror;
202 goto jleave;
205 /* NAIL_FILENAME */
206 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
207 cp = "";
208 env_addon[0] = str_concat_csvl(&s, NAILENV_FILENAME, "=", cp, NULL)->s;
210 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
211 * TODO a file wherever he wants! *Do* create a zero-size temporary file
212 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
213 * TODO the pipe returns? Like this we *can* verify path/name issues! */
214 env_addon[1] = str_concat_csvl(&s, NAILENV_FILENAME_GENERATED, "=",
215 getrandstring(MIN(NAME_MAX / 4, 16)), NULL)->s;
217 /* NAIL_CONTENT{,_EVIDENCE} */
218 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
219 cp = "";
220 env_addon[2] = str_concat_csvl(&s, NAILENV_CONTENT, "=", cp, NULL)->s;
222 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
223 cp = mpp->m_ct_type_usr_ovwr;
224 env_addon[3] = str_concat_csvl(&s, NAILENV_CONTENT_EVIDENCE, "=", cp,
225 NULL)->s;
227 cp = ok_vlook(TMPDIR);
228 env_addon[4] = str_concat_csvl(&s, NAILENV_TMPDIR, "=", cp, NULL)->s;
229 env_addon[5] = str_concat_csvl(&s, "TMPDIR", "=", cp, NULL)->s;
231 env_addon[6] = NULL;
233 /* NAIL_FILENAME_TEMPORARY? */
234 if (tmpname != NULL) {
235 env_addon[6] = str_concat_csvl(&s, NAILENV_FILENAME_TEMPORARY, "=",
236 tmpname, NULL)->s;
237 env_addon[7] = NULL;
240 sh = ok_vlook(SHELL);
242 if (mhp->mh_flags & MIME_HDL_NEEDSTERM) {
243 sigset_t nset;
244 int pid;
246 sigemptyset(&nset);
247 pid = run_command(sh, NULL, term_infd, COMMAND_FD_PASS, "-c",
248 mhp->mh_shell_cmd, NULL, env_addon);
249 rbuf = (pid < 0) ? NULL : (FILE*)-1;
250 } else {
251 rbuf = Popen(mhp->mh_shell_cmd, "W", sh, env_addon,
252 (mhp->mh_flags & MIME_HDL_ASYNC ? -1 : fileno(*qbuf)));
253 jerror:
254 if (rbuf == NULL)
255 n_err(_("Cannot run MIME type handler: %s: %s\n"),
256 mhp->mh_msg, strerror(errno));
257 else {
258 fflush(*qbuf);
259 if (*qbuf != stdout)
260 fflush(stdout);
263 jleave:
264 NYD_LEAVE;
265 return rbuf;
268 SINLINE ssize_t
269 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
270 sendaction action, struct quoteflt *qf, ui64_t *stats, struct str *rest)
272 ssize_t sz = 0, n;
273 int flags;
274 NYD_ENTER;
276 #if 0
277 Well ... it turns out to not work like that since of course a valid
278 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
279 after an empty line has been seen, which cannot be detected that easily
280 right here!
281 ifdef HAVE_DEBUG /* TODO assert legacy */
282 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
283 * TODO other input situations handle RFC 4155 OR, if newly generated,
284 * TODO enforce quoted-printable if there is From_, as "required" by
285 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
286 * TODO if it may happen in this path, we should just treat decryption
287 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
288 if (action == SEND_MBOX || action == SEND_DECRYPT)
289 assert(!is_head(buf, len, FAL0));
290 #else
291 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
292 is_head(buf, len, FAL0)) {
293 putc('>', fp);
294 ++sz;
296 #endif
298 flags = ((int)action & _TD_EOF);
299 action &= ~_TD_EOF;
300 n = mime_write(buf, len, fp,
301 action == SEND_MBOX ? CONV_NONE : convert,
302 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
303 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
304 ? TD_ISPR | TD_ICONV
305 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
306 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
307 qf, rest);
308 if (n < 0)
309 sz = n;
310 else if (n > 0) {
311 sz += n;
312 if (stats != NULL)
313 *stats += sz;
315 NYD_LEAVE;
316 return sz;
319 static void
320 _send_onpipe(int signo)
322 NYD_X; /* Signal handler */
323 UNUSED(signo);
324 siglongjmp(_send_pipejmp, 1);
327 static sigjmp_buf __sendp_actjmp; /* TODO someday.. */
328 static int __sendp_sig; /* TODO someday.. */
329 static sighandler_type __sendp_opipe;
330 static void
331 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
333 NYD_X; /* Signal handler */
334 __sendp_sig = sig;
335 siglongjmp(__sendp_actjmp, 1);
338 static int
339 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
340 struct ignoretab *doign, struct quoteflt *qf,
341 enum sendaction volatile action, ui64_t * volatile stats, int level)
343 int volatile rv = 0;
344 struct mime_handler mh;
345 struct str rest;
346 char *line = NULL, *cp, *cp2, *start;
347 char const * volatile tmpname = NULL;
348 size_t linesize = 0, linelen, cnt;
349 int volatile term_infd;
350 int dostat, c;
351 struct mimepart *volatile np;
352 FILE * volatile ibuf = NULL, * volatile pbuf = obuf,
353 * volatile qbuf = obuf, *origobuf = obuf;
354 enum conversion volatile convert;
355 sighandler_type volatile oldpipe = SIG_DFL;
356 NYD_ENTER;
358 UNINIT(term_infd, 0);
359 UNINIT(cnt, 0);
361 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
362 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
363 goto jskip;
365 dostat = 0;
366 if (level == 0) {
367 if (doign != NULL) {
368 if (!is_ign("status", 6, doign))
369 dostat |= 1;
370 if (!is_ign("x-status", 8, doign))
371 dostat |= 2;
372 } else
373 dostat = 3;
375 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
376 rv = -1;
377 goto jleave;
379 cnt = ip->m_size;
381 if (ip->m_mimecontent == MIME_DISCARD)
382 goto jskip;
384 if (!(ip->m_flag & MNOFROM))
385 while (cnt && (c = getc(ibuf)) != EOF) {
386 cnt--;
387 if (c == '\n')
388 break;
390 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
391 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
392 action == SEND_TOSRCH)
393 ? CONV_FROMHDR : CONV_NONE;
395 /* Work the headers */
396 quoteflt_reset(qf, obuf);
397 /* C99 */{
398 enum {
399 HPS_NONE = 0,
400 HPS_IN_FIELD = 1<<0,
401 HPS_IGNORE = 1<<1,
402 HPS_ISENC_1 = 1<<2,
403 HPS_ISENC_2 = 1<<3
404 } hps = HPS_NONE;
405 size_t lineno = 0;
407 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
408 ++lineno;
409 if (line[0] == '\n') {
410 /* If line is blank, we've reached end of headers, so force out
411 * status: field and note that we are no longer in header fields */
412 if (dostat & 1)
413 statusput(zmp, obuf, qf, stats);
414 if (dostat & 2)
415 xstatusput(zmp, obuf, qf, stats);
416 if (doign != allignore)
417 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
418 break;
421 hps &= ~HPS_ISENC_1;
422 if ((hps & HPS_IN_FIELD) && blankchar(line[0])) {
423 /* If this line is a continuation (SP / HT) of a previous header
424 * field, determine if the start of the line is a MIME encoded word */
425 if (hps & HPS_ISENC_2) {
426 for (cp = line; blankchar(*cp); ++cp)
428 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
429 cp[0] == '=' && cp[1] == '?')
430 hps |= HPS_ISENC_1;
432 } else {
433 /* Pick up the header field if we have one */
434 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
436 cp2 = cp;
437 while (spacechar(*cp))
438 ++cp;
439 if (cp[0] != ':') {
440 if (lineno != 1)
441 n_err(_("Malformed message: headers and body not separated "
442 "(with empty line)\n"));
443 /* Not a header line, force out status: This happens in uucp style
444 * mail where there are no headers at all */
445 if (level == 0 /*&& lineno == 1*/) {
446 if (dostat & 1)
447 statusput(zmp, obuf, qf, stats);
448 if (dostat & 2)
449 xstatusput(zmp, obuf, qf, stats);
451 if (doign != allignore)
452 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
453 break;
456 /* If it is an ignored field and we care about such things, skip it.
457 * Misuse dostat also for another bit xxx use a bitenum + for more */
458 if (ok_blook(keep_content_length))
459 dostat |= 1 << 2;
460 c = *cp2;
461 *cp2 = 0; /* temporarily null terminate */
462 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
463 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
464 (!asccasecmp(line, "content-length") ||
465 !asccasecmp(line, "lines"))))
466 hps |= HPS_IGNORE;
467 else if (!asccasecmp(line, "status")) {
468 /* If field is "status," go compute and print real Status: field */
469 if (dostat & 1) {
470 statusput(zmp, obuf, qf, stats);
471 dostat &= ~1;
472 hps |= HPS_IGNORE;
474 } else if (!asccasecmp(line, "x-status")) {
475 /* If field is "status," go compute and print real Status: field */
476 if (dostat & 2) {
477 xstatusput(zmp, obuf, qf, stats);
478 dostat &= ~2;
479 hps |= HPS_IGNORE;
481 } else {
482 hps &= ~HPS_IGNORE;
483 /* For colourization we need the complete line, so save it */
484 /* XXX This is all temporary (colour belongs into backend), so
485 * XXX use tmpname as a temporary storage in the meanwhile */
486 #ifdef HAVE_COLOUR
487 if (pstate & PS_COLOUR_ACTIVE)
488 tmpname = savestrbuf(line, PTR2SIZE(cp2 - line));
489 #endif
491 *cp2 = c;
492 dostat &= ~(1 << 2);
493 hps |= HPS_IN_FIELD;
496 /* Determine if the end of the line is a MIME encoded word */
497 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
498 * TODO with header follow lines, and it should be up to the backend
499 * TODO what happens and what not, i.e., it doesn't matter whether it's
500 * TODO a MIME-encoded word or not, as long as a single separating space
501 * TODO remains in between lines (the MIME stuff will correctly remove
502 * TODO whitespace in between multiple adjacent encoded words) */
503 hps &= ~HPS_ISENC_2;
504 if (cnt && (c = getc(ibuf)) != EOF) {
505 if (blankchar(c)) {
506 cp = line + linelen - 1;
507 if (linelen > 0 && *cp == '\n')
508 --cp;
509 while (cp >= line && whitechar(*cp))
510 --cp;
511 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
512 hps |= HPS_ISENC_2;
514 ungetc(c, ibuf);
517 if (!(hps & HPS_IGNORE)) {
518 size_t len = linelen;
519 start = line;
520 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
521 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
522 action == SEND_TOSRCH) {
523 /* Strip blank characters if two MIME-encoded words follow on
524 * continuing lines */
525 if (hps & HPS_ISENC_1)
526 while (len > 0 && blankchar(*start)) {
527 ++start;
528 --len;
530 if (hps & HPS_ISENC_2)
531 if (len > 0 && start[len - 1] == '\n')
532 --len;
533 while (len > 0 && blankchar(start[len - 1]))
534 --len;
536 #ifdef HAVE_COLOUR
538 bool_t colour_stripped = FAL0;
539 if (tmpname != NULL) {
540 n_colour_put(obuf, n_COLOUR_ID_VIEW_HEADER, tmpname);
541 if (len > 0 && start[len - 1] == '\n') {
542 colour_stripped = TRU1;
543 --len;
546 #endif
547 _out(start, len, obuf, convert, action, qf, stats, NULL);
548 #ifdef HAVE_COLOUR
549 if (tmpname != NULL) {
550 n_colour_reset(obuf);
551 if (colour_stripped)
552 putc('\n', obuf);
555 #endif
556 if (ferror(obuf)) {
557 free(line);
558 rv = -1;
559 goto jleave;
563 } /* C99 */
564 quoteflt_flush(qf);
565 free(line);
566 line = NULL;
567 tmpname = NULL;
569 jskip:
570 memset(&mh, 0, sizeof mh);
572 switch (ip->m_mimecontent) {
573 case MIME_822:
574 switch (action) {
575 case SEND_TODISP:
576 case SEND_TODISP_ALL:
577 case SEND_QUOTE:
578 case SEND_QUOTE_ALL:
579 if (ok_blook(rfc822_body_from_)) {
580 if (qf->qf_pfix_len > 0) {
581 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
582 qf->qf_pfix_len, obuf);
583 if (i == qf->qf_pfix_len && stats != NULL)
584 *stats += i;
586 put_from_(obuf, ip->m_multipart, stats);
588 /* FALLTHRU */
589 case SEND_TOSRCH:
590 case SEND_DECRYPT:
591 goto jmulti;
592 case SEND_TOFILE:
593 case SEND_TOPIPE:
594 if (ok_blook(rfc822_body_from_))
595 put_from_(obuf, ip->m_multipart, stats);
596 /* FALLTHRU */
597 case SEND_MBOX:
598 case SEND_RFC822:
599 case SEND_SHOW:
600 break;
602 break;
603 case MIME_TEXT_HTML:
604 case MIME_TEXT:
605 case MIME_TEXT_PLAIN:
606 switch (action) {
607 case SEND_TODISP:
608 case SEND_TODISP_ALL:
609 case SEND_QUOTE:
610 case SEND_QUOTE_ALL:
611 switch (mime_type_handler(&mh, ip, action)) {
612 case MIME_HDL_MSG:
613 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
614 stats, NULL);
615 /* We would print this as plain text, so better force going home */
616 goto jleave;
617 default:
618 break;
620 /* FALLTRHU */
621 default:
622 break;
624 break;
625 case MIME_DISCARD:
626 if (action != SEND_DECRYPT)
627 goto jleave;
628 break;
629 case MIME_PKCS7:
630 if (action != SEND_MBOX && action != SEND_RFC822 &&
631 action != SEND_SHOW && ip->m_multipart != NULL)
632 goto jmulti;
633 /* FALLTHRU */
634 default:
635 switch (action) {
636 case SEND_TODISP:
637 case SEND_TODISP_ALL:
638 case SEND_QUOTE:
639 case SEND_QUOTE_ALL:
640 switch (mime_type_handler(&mh, ip, action)) {
641 case MIME_HDL_MSG:
642 _out(mh.mh_msg.s, mh.mh_msg.l, obuf, CONV_NONE, SEND_MBOX, qf,
643 stats, NULL);
644 /* We would print this as plain text, so better force going home */
645 goto jleave;
646 case MIME_HDL_CMD:
647 /* FIXME WE NEED TO DO THAT IF WE ARE THE ONLY MAIL
648 * FIXME CONTENT !! */
649 case MIME_HDL_TEXT:
650 break;
651 default:
652 case MIME_HDL_NULL:
653 if (level == 0 && cnt) {
654 char const *x = _("[-- Binary content --]\n");
655 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
657 goto jleave;
659 break;
660 case SEND_TOFILE:
661 case SEND_TOPIPE:
662 case SEND_TOSRCH:
663 case SEND_DECRYPT:
664 case SEND_MBOX:
665 case SEND_RFC822:
666 case SEND_SHOW:
667 break;
669 break;
670 case MIME_ALTERNATIVE:
671 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
672 !ok_blook(print_alternatives)) {
673 /* XXX This (a) should not remain (b) should be own fun
674 * TODO (despite the fact that v15 will do this completely differently
675 * TODO by having an action-specific "manager" that will traverse the
676 * TODO parsed MIME tree and decide for each part whether it'll be
677 * TODO displayed or not *before* we walk the tree for doing action */
678 struct mpstack {
679 struct mpstack *outer;
680 struct mimepart *mp;
681 } outermost, * volatile curr, * volatile mpsp;
682 bool_t volatile neednl, hadpart;
683 struct n_sigman smalter;
685 (curr = &outermost)->outer = NULL;
686 curr->mp = ip;
687 neednl = hadpart = FAL0;
689 n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
690 case 0:
691 break;
692 default:
693 rv = -1;
694 goto jalter_leave;
697 for (np = ip->m_multipart;;) {
698 jalter_redo:
699 for (; np != NULL; np = np->m_nextpart) {
700 if (action != SEND_QUOTE && np->m_ct_type_plain != NULL) {
701 if (neednl)
702 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
703 _print_part_info(obuf, np, doign, level, qf, stats);
705 neednl = TRU1;
707 switch (np->m_mimecontent) {
708 case MIME_ALTERNATIVE:
709 case MIME_RELATED:
710 case MIME_MULTI:
711 case MIME_DIGEST:
712 mpsp = salloc(sizeof *mpsp);
713 mpsp->outer = curr;
714 mpsp->mp = np->m_multipart;
715 curr->mp = np;
716 curr = mpsp;
717 np = mpsp->mp;
718 neednl = FAL0;
719 goto jalter_redo;
720 default:
721 if (hadpart)
722 break;
723 switch (mime_type_handler(&mh, np, action)) {
724 default:
725 mh.mh_flags = MIME_HDL_NULL;
726 continue; /* break; break; */
727 case MIME_HDL_PTF:
728 if (!ok_blook(mime_alternative_favour_rich)) {/* TODO */
729 struct mimepart *x = np;
731 while ((x = x->m_nextpart) != NULL) {
732 struct mime_handler mhx;
734 if (x->m_mimecontent == MIME_TEXT_PLAIN ||
735 mime_type_handler(&mhx, x, action) ==
736 MIME_HDL_TEXT)
737 break;
739 if (x != NULL)
740 continue; /* break; break; */
741 goto jalter_plain;
743 /* FALLTHRU */
744 case MIME_HDL_TEXT:
745 break;
747 /* FALLTHRU */
748 case MIME_TEXT_PLAIN:
749 if (hadpart)
750 break;
751 if (ok_blook(mime_alternative_favour_rich)) { /* TODO */
752 struct mimepart *x = np;
754 /* TODO twice TODO, we should dive into /related and
755 * TODO check whether that has rich parts! */
756 while ((x = x->m_nextpart) != NULL) {
757 struct mime_handler mhx;
759 switch (mime_type_handler(&mhx, x, action)) {
760 case MIME_HDL_PTF:
761 break;
762 default:
763 continue;
765 break;
767 if (x != NULL)
768 continue; /* break; break; */
770 jalter_plain:
771 quoteflt_flush(qf);
772 if (action == SEND_QUOTE && hadpart) {
773 struct quoteflt *dummy = quoteflt_dummy();
774 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
775 NULL);
776 quoteflt_flush(dummy);
778 hadpart = TRU1;
779 neednl = FAL0;
780 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
781 level + 1);
782 quoteflt_reset(qf, origobuf);
784 if (rv < 0)
785 curr = &outermost; /* Cause overall loop termination */
786 break;
790 mpsp = curr->outer;
791 if (mpsp == NULL)
792 break;
793 curr = mpsp;
794 np = curr->mp->m_nextpart;
796 jalter_leave:
797 n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
798 goto jleave;
800 /* FALLTHRU */
801 case MIME_MULTI:
802 case MIME_DIGEST:
803 case MIME_RELATED:
804 switch (action) {
805 case SEND_TODISP:
806 case SEND_TODISP_ALL:
807 case SEND_QUOTE:
808 case SEND_QUOTE_ALL:
809 case SEND_TOFILE:
810 case SEND_TOPIPE:
811 case SEND_TOSRCH:
812 case SEND_DECRYPT:
813 jmulti:
814 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
815 ip->m_multipart != NULL &&
816 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
817 ip->m_multipart->m_nextpart == NULL) {
818 char const *x = _("[Missing multipart boundary - use show "
819 "to display the raw message]\n");
820 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
823 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
824 bool_t volatile ispipe;
826 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
827 continue;
829 ispipe = FAL0;
830 switch (action) {
831 case SEND_TOFILE:
832 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
833 break;
834 stats = NULL;
835 /* TODO Always open multipart on /dev/null, it's a hack to be
836 * TODO able to dive into that structure, and still better
837 * TODO than asking the user for something stupid.
838 * TODO oh, wait, we did ask for a filename for this MIME mail,
839 * TODO and that outer container is useless anyway ;-P */
840 if (np->m_multipart != NULL) {
841 if ((obuf = Fopen("/dev/null", "w")) == NULL)
842 continue;
843 } else if ((obuf = newfile(np, &ispipe)) == NULL)
844 continue;
845 if (!ispipe)
846 break;
847 if (sigsetjmp(_send_pipejmp, 1)) {
848 rv = -1;
849 goto jpipe_close;
851 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
852 break;
853 case SEND_TODISP:
854 case SEND_TODISP_ALL:
855 if (ip->m_mimecontent != MIME_ALTERNATIVE &&
856 ip->m_mimecontent != MIME_RELATED &&
857 ip->m_mimecontent != MIME_DIGEST &&
858 ip->m_mimecontent != MIME_MULTI)
859 break;
860 _print_part_info(obuf, np, doign, level, qf, stats);
861 break;
862 case SEND_QUOTE:
863 case SEND_QUOTE_ALL:
864 case SEND_MBOX:
865 case SEND_RFC822:
866 case SEND_SHOW:
867 case SEND_TOSRCH:
868 case SEND_DECRYPT:
869 case SEND_TOPIPE:
870 break;
873 quoteflt_flush(qf);
874 if ((action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
875 np->m_multipart == NULL && ip->m_parent != NULL) {
876 struct quoteflt *dummy = quoteflt_dummy();
877 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, dummy, stats,
878 NULL);
879 quoteflt_flush(dummy);
881 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
882 rv = -1;
883 quoteflt_reset(qf, origobuf);
885 if (action == SEND_QUOTE) {
886 if (ip->m_mimecontent != MIME_RELATED)
887 break;
889 if (action == SEND_TOFILE && obuf != origobuf) {
890 if (!ispipe)
891 Fclose(obuf);
892 else {
893 jpipe_close:
894 safe_signal(SIGPIPE, SIG_IGN);
895 Pclose(obuf, TRU1);
896 safe_signal(SIGPIPE, oldpipe);
900 goto jleave;
901 case SEND_MBOX:
902 case SEND_RFC822:
903 case SEND_SHOW:
904 break;
906 break;
909 /* Copy out message body */
910 if (doign == allignore && level == 0) /* skip final blank line */
911 --cnt;
912 switch (ip->m_mime_enc) {
913 case MIMEE_BIN:
914 case MIMEE_7B:
915 case MIMEE_8B:
916 convert = CONV_NONE;
917 break;
918 case MIMEE_QP:
919 convert = CONV_FROMQP;
920 break;
921 case MIMEE_B64:
922 switch (ip->m_mimecontent) {
923 case MIME_TEXT:
924 case MIME_TEXT_PLAIN:
925 case MIME_TEXT_HTML:
926 convert = CONV_FROMB64_T;
927 break;
928 default:
929 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
930 case MIME_HDL_TEXT:
931 case MIME_HDL_PTF:
932 convert = CONV_FROMB64_T;
933 break;
934 default:
935 convert = CONV_FROMB64;
936 break;
938 break;
940 break;
941 default:
942 convert = CONV_NONE;
945 if (action == SEND_DECRYPT || action == SEND_MBOX ||
946 action == SEND_RFC822 || action == SEND_SHOW)
947 convert = CONV_NONE;
948 #ifdef HAVE_ICONV
949 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
950 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
951 action == SEND_TOSRCH) &&
952 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
953 ip->m_mimecontent == MIME_TEXT_HTML ||
954 ip->m_mimecontent == MIME_TEXT ||
955 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_TEXT ||
956 (mh.mh_flags & MIME_HDL_TYPE_MASK) == MIME_HDL_PTF)) {
957 char const *tcs = charset_get_lc();
959 if (iconvd != (iconv_t)-1)
960 n_iconv_close(iconvd);
961 /* TODO Since Base64 has an odd 4:3 relation in between input
962 * TODO and output an input line may end with a partial
963 * TODO multibyte character; this is no problem at all unless
964 * TODO we send to the display or whatever, i.e., ensure
965 * TODO makeprint() or something; to avoid this trap, *force*
966 * TODO iconv(), in which case this layer will handle leftovers
967 * TODO correctly */
968 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
969 asccasecmp(charset_get_7bit(), ip->m_charset))) {
970 iconvd = n_iconv_open(tcs, ip->m_charset);
972 * TODO errors should DEFINETELY not be scrolled away!
973 * TODO what about an error buffer (think old shsp(1)),
974 * TODO re-dump errors since last snapshot when the
975 * TODO command loop enters again? i.e., at least print
976 * TODO "There were errors ?" before the next prompt,
977 * TODO so that the user can look at the error buffer?
979 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
980 n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
981 /*rv = 1; goto jleave;*/
985 #endif
987 switch (mh.mh_flags & MIME_HDL_TYPE_MASK) {
988 case MIME_HDL_CMD:
989 case MIME_HDL_PTF:
990 tmpname = NULL;
991 qbuf = obuf;
993 term_infd = COMMAND_FD_PASS;
994 if (mh.mh_flags & (MIME_HDL_TMPF | MIME_HDL_NEEDSTERM)) {
995 enum oflags of;
997 of = OF_RDWR | OF_REGISTER;
998 if (!(mh.mh_flags & MIME_HDL_TMPF)) {
999 term_infd = 0;
1000 mh.mh_flags |= MIME_HDL_TMPF_FILL;
1001 of |= OF_UNLINK;
1002 } else if (mh.mh_flags & MIME_HDL_TMPF_UNLINK)
1003 of |= OF_REGISTER_UNLINK;
1005 if ((pbuf = Ftmp((mh.mh_flags & MIME_HDL_TMPF ? &cp : NULL),
1006 (mh.mh_flags & MIME_HDL_TMPF_FILL ? "mimehdlfill" : "mimehdl"),
1007 of)) == NULL)
1008 goto jesend;
1010 if (mh.mh_flags & MIME_HDL_TMPF) {
1011 tmpname = savestr(cp);
1012 Ftmp_free(&cp);
1015 if (mh.mh_flags & MIME_HDL_TMPF_FILL) {
1016 if (term_infd == 0)
1017 term_infd = fileno(pbuf);
1018 goto jsend;
1022 jpipe_for_real:
1023 pbuf = _pipefile(&mh, ip, UNVOLATILE(&qbuf), tmpname, term_infd);
1024 if (pbuf == NULL) {
1025 jesend:
1026 pbuf = qbuf = NULL;
1027 rv = -1;
1028 goto jend;
1029 } else if ((mh.mh_flags & MIME_HDL_NEEDSTERM) && pbuf == (FILE*)-1) {
1030 pbuf = qbuf = NULL;
1031 goto jend;
1033 tmpname = NULL;
1034 action = SEND_TOPIPE;
1035 if (pbuf != qbuf) {
1036 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1037 if (sigsetjmp(_send_pipejmp, 1))
1038 goto jend;
1040 break;
1042 default:
1043 mh.mh_flags = MIME_HDL_NULL;
1044 pbuf = qbuf = obuf;
1045 break;
1048 jsend:
1050 bool_t volatile eof;
1051 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1052 ui64_t *save_stats = stats;
1054 if (pbuf != origobuf) {
1055 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1056 stats = NULL;
1058 eof = FAL0;
1059 rest.s = NULL;
1060 rest.l = 0;
1062 if (pbuf == qbuf) {
1063 __sendp_sig = 0;
1064 __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1065 if (sigsetjmp(__sendp_actjmp, 1)) {
1066 if (rest.s != NULL)
1067 free(rest.s);
1068 free(line);
1069 #ifdef HAVE_ICONV
1070 if (iconvd != (iconv_t)-1)
1071 n_iconv_close(iconvd);
1072 #endif
1073 safe_signal(SIGPIPE, __sendp_opipe);
1074 n_raise(__sendp_sig);
1078 quoteflt_reset(qf, pbuf);
1079 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1080 joutln:
1081 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1082 ferror(pbuf)) {
1083 rv = -1; /* XXX Should bail away?! */
1084 break;
1087 if (!eof && rv >= 0 && rest.l != 0) {
1088 linelen = 0;
1089 eof = TRU1;
1090 action |= _TD_EOF;
1091 goto joutln;
1094 /* TODO HACK: when sending to the display we yet get fooled if a message
1095 * TODO doesn't end in a newline, because of our input/output 1:1.
1096 * TODO This should be handled automatically by a display filter, then */
1097 if(rv >= 0 && !qf->qf_nl_last &&
1098 (action == SEND_TODISP || action == SEND_TODISP_ALL))
1099 rv = quoteflt_push(qf, "\n", 1);
1101 quoteflt_flush(qf);
1103 if (rv >= 0 && (mh.mh_flags & MIME_HDL_TMPF_FILL)) {
1104 mh.mh_flags &= ~MIME_HDL_TMPF_FILL;
1105 fflush(pbuf);
1106 really_rewind(pbuf);
1107 /* Don't Fclose() the Ftmp() thing due to OF_REGISTER_UNLINK++ */
1108 goto jpipe_for_real;
1111 if (pbuf == qbuf)
1112 safe_signal(SIGPIPE, __sendp_opipe);
1114 if (rest.s != NULL)
1115 free(rest.s);
1117 if (pbuf != origobuf) {
1118 qf->qf_pfix_len = save_qf_pfix_len;
1119 stats = save_stats;
1123 jend:
1124 if (line != NULL)
1125 free(line);
1126 if (pbuf != qbuf) {
1127 safe_signal(SIGPIPE, SIG_IGN);
1128 Pclose(pbuf, !(mh.mh_flags & MIME_HDL_ASYNC));
1129 safe_signal(SIGPIPE, oldpipe);
1130 if (rv >= 0 && qbuf != NULL && qbuf != obuf)
1131 pipecpy(qbuf, obuf, origobuf, qf, stats);
1133 #ifdef HAVE_ICONV
1134 if (iconvd != (iconv_t)-1)
1135 n_iconv_close(iconvd);
1136 #endif
1137 jleave:
1138 NYD_LEAVE;
1139 return rv;
1142 static FILE *
1143 newfile(struct mimepart *ip, bool_t volatile *ispipe)
1145 struct str in, out;
1146 char *f;
1147 FILE *fp;
1148 NYD_ENTER;
1150 f = ip->m_filename;
1151 *ispipe = FAL0;
1153 if (f != NULL && f != (char*)-1) {
1154 in.s = f;
1155 in.l = strlen(f);
1156 makeprint(&in, &out);
1157 out.l = delctrl(out.s, out.l);
1158 f = savestrbuf(out.s, out.l);
1159 free(out.s);
1162 if (options & OPT_INTERACTIVE) {
1163 struct str prompt;
1164 struct n_string shou, *shoup;
1165 char *f2, *f3;
1167 shoup = n_string_creat_auto(&shou);
1169 /* TODO Generic function which asks for filename.
1170 * TODO If the current part is the first textpart the target
1171 * TODO is implicit from outer `write' etc! */
1172 /* I18N: Filename input prompt with file type indication */
1173 str_concat_csvl(&prompt, _("Enter filename for part "),
1174 (ip->m_partstring != NULL) ? ip->m_partstring : _("?"),
1175 _(" ("), ip->m_ct_type_plain, _("): "), NULL);
1176 jgetname:
1177 f2 = n_lex_input_cp((n_LEXINPUT_CTX_BASE | n_LEXINPUT_HIST_ADD),
1178 prompt.s, ((f != (char*)-1 && f != NULL)
1179 ? n_shexp_quote_cp(f, FAL0) : NULL));
1180 if(f2 != NULL){
1181 in.s = UNCONST(f2);
1182 in.l = UIZ_MAX;
1183 if((n_shexp_parse_token(shoup, &in, n_SHEXP_PARSE_TRUNC |
1184 n_SHEXP_PARSE_TRIMSPACE | n_SHEXP_PARSE_LOG |
1185 n_SHEXP_PARSE_IGNORE_EMPTY) &
1186 (n_SHEXP_STATE_OUTPUT | n_SHEXP_STATE_ERR_MASK)) !=
1187 n_SHEXP_STATE_OUTPUT)
1188 goto jgetname;
1189 if(in.l != 0)
1190 goto jgetname;
1191 f2 = n_string_cp(shoup);
1193 if (f2 == NULL || *f2 == '\0') {
1194 if (options & OPT_D_V)
1195 n_err(_("... skipping this\n"));
1196 n_string_gut(shoup);
1197 fp = NULL;
1198 goto jleave;
1201 if (*f2 == '|')
1202 /* Pipes are expanded by the shell */
1203 f = f2;
1204 else if ((f3 = fexpand(f2, FEXP_LOCAL | FEXP_NVAR)) == NULL)
1205 /* (Error message written by fexpand()) */
1206 goto jgetname;
1207 else
1208 f = f3;
1210 n_string_gut(shoup);
1212 if (f == NULL || f == (char*)-1) {
1213 fp = NULL;
1214 goto jleave;
1217 if (*f == '|') {
1218 fp = Popen(f + 1, "w", ok_vlook(SHELL), NULL, 1);
1219 if (!(*ispipe = (fp != NULL)))
1220 n_perr(f, 0);
1221 } else {
1222 if ((fp = Fopen(f, "w")) == NULL)
1223 n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
1225 jleave:
1226 NYD_LEAVE;
1227 return fp;
1230 static void
1231 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1232 ui64_t *stats)
1234 char *line = NULL; /* TODO line pool */
1235 size_t linesize = 0, linelen, cnt;
1236 ssize_t all_sz, sz;
1237 NYD_ENTER;
1239 fflush(pipebuf);
1240 rewind(pipebuf);
1241 cnt = (size_t)fsize(pipebuf);
1242 all_sz = 0;
1244 quoteflt_reset(qf, outbuf);
1245 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1246 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1247 break;
1248 all_sz += sz;
1250 if ((sz = quoteflt_flush(qf)) > 0)
1251 all_sz += sz;
1252 if (line)
1253 free(line);
1255 if (all_sz > 0 && outbuf == origobuf && stats != NULL)
1256 *stats += all_sz;
1257 Fclose(pipebuf);
1258 NYD_LEAVE;
1261 static void
1262 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1263 ui64_t *stats)
1265 char statout[3], *cp = statout;
1266 NYD_ENTER;
1268 if (mp->m_flag & MREAD)
1269 *cp++ = 'R';
1270 if (!(mp->m_flag & MNEW))
1271 *cp++ = 'O';
1272 *cp = 0;
1273 if (statout[0]) {
1274 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1275 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1276 if (i > 0 && stats != NULL)
1277 *stats += i;
1279 NYD_LEAVE;
1282 static void
1283 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1284 ui64_t *stats)
1286 char xstatout[4];
1287 char *xp = xstatout;
1288 NYD_ENTER;
1290 if (mp->m_flag & MFLAGGED)
1291 *xp++ = 'F';
1292 if (mp->m_flag & MANSWERED)
1293 *xp++ = 'A';
1294 if (mp->m_flag & MDRAFTED)
1295 *xp++ = 'T';
1296 *xp = 0;
1297 if (xstatout[0]) {
1298 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1299 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1300 if (i > 0 && stats != NULL)
1301 *stats += i;
1303 NYD_LEAVE;
1306 static void
1307 put_from_(FILE *fp, struct mimepart *ip, ui64_t *stats)
1309 char const *froma, *date, *nl;
1310 int i;
1311 NYD_ENTER;
1313 if (ip != NULL && ip->m_from != NULL) {
1314 froma = ip->m_from;
1315 date = fakedate(ip->m_time);
1316 nl = "\n";
1317 } else {
1318 froma = myname;
1319 date = time_current.tc_ctime;
1320 nl = "";
1323 n_COLOUR( n_colour_put(fp, n_COLOUR_ID_VIEW_FROM_, NULL); )
1324 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1325 n_COLOUR( n_colour_reset(fp); )
1326 if (i > 0 && stats != NULL)
1327 *stats += i;
1328 NYD_LEAVE;
1331 FL int
1332 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1333 char const *prefix, enum sendaction action, ui64_t *stats)
1335 struct quoteflt qf;
1336 size_t cnt, sz, i;
1337 FILE *ibuf;
1338 enum mime_parse_flags mpf;
1339 struct mimepart *ip;
1340 int rv = -1, c;
1341 NYD_ENTER;
1343 time_current_update(&time_current, TRU1);
1345 if (mp == dot && action != SEND_TOSRCH)
1346 pstate |= PS_DID_PRINT_DOT;
1347 if (stats != NULL)
1348 *stats = 0;
1349 quoteflt_init(&qf, prefix);
1351 /* First line is the From_ line, so no headers there to worry about */
1352 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1353 goto jleave;
1355 cnt = mp->m_size;
1356 sz = 0;
1358 bool_t nozap;
1359 char const *cpre = "", *csuf = "";
1360 #ifdef HAVE_COLOUR
1361 struct n_colour_pen *cpen = n_colour_pen_create(n_COLOUR_ID_VIEW_FROM_,NULL);
1362 struct str const *sp = n_colour_pen_to_str(cpen);
1364 if (sp != NULL) {
1365 cpre = sp->s;
1366 sp = n_colour_reset_to_str();
1367 if (sp != NULL)
1368 csuf = sp->s;
1370 #endif
1372 nozap = (doign != allignore && doign != fwdignore && action != SEND_RFC822 &&
1373 !is_ign("from_", sizeof("from_") -1, doign));
1374 if (mp->m_flag & MNOFROM) {
1375 if (nozap)
1376 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1377 cpre, (int)qf.qf_pfix_len,
1378 (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""), fakefrom(mp),
1379 fakedate(mp->m_time), csuf);
1380 } else if (nozap) {
1381 if (qf.qf_pfix_len > 0) {
1382 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1383 if (i != qf.qf_pfix_len)
1384 goto jleave;
1385 sz += i;
1387 #ifdef HAVE_COLOUR
1388 if (cpre != NULL) {
1389 fputs(cpre, obuf);
1390 cpre = (char const*)0x1;
1392 #endif
1394 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1395 #ifdef HAVE_COLOUR
1396 if (c == '\n' && csuf != NULL) {
1397 cpre = (char const*)0x1;
1398 fputs(csuf, obuf);
1400 #endif
1401 putc(c, obuf);
1402 ++sz;
1403 --cnt;
1404 if (c == '\n')
1405 break;
1408 #ifdef HAVE_COLOUR
1409 if (csuf != NULL && cpre != (char const*)0x1)
1410 fputs(csuf, obuf);
1411 #endif
1412 } else {
1413 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1414 --cnt;
1415 if (c == '\n')
1416 break;
1420 if (sz > 0 && stats != NULL)
1421 *stats += sz;
1423 mpf = MIME_PARSE_NONE;
1424 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1425 mpf |= MIME_PARSE_DECRYPT | MIME_PARSE_PARTS;
1426 if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1427 goto jleave;
1429 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1430 jleave:
1431 quoteflt_destroy(&qf);
1432 NYD_LEAVE;
1433 return rv;
1436 /* s-it-mode */