tty.c, NCL: let ^O mean `dp'
[s-mailx.git] / send.c
blobc95a10e4b156e6ed8a684c1c14072d7eece709b1
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mail to mail folders and displays.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 enum pipeflags {
45 PIPE_NULL, /* No pipe- mimetype handler */
46 PIPE_COMM, /* Normal command */
47 PIPE_ASYNC, /* Normal command, run asynchronous */
48 PIPE_TEXT, /* @ special command to force treatment as text */
49 PIPE_MSG /* Display message (returned as command string) */
52 enum parseflags {
53 PARSE_DEFAULT = 0,
54 PARSE_DECRYPT = 01,
55 PARSE_PARTS = 02
58 static sigjmp_buf _send_pipejmp;
60 /* */
61 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
62 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
63 enum parseflags pf, int level);
64 static void parse822(struct message *zmp, struct mimepart *ip,
65 enum parseflags pf, int level);
66 #ifdef HAVE_SSL
67 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
68 enum parseflags pf, int level);
69 #endif
70 static void _parsemultipart(struct message *zmp,
71 struct mimepart *ip, enum parseflags pf, int level);
72 static void __newpart(struct mimepart *ip, struct mimepart **np,
73 off_t offs, int *part);
74 static void __endpart(struct mimepart **np, off_t xoffs, long lines);
76 /* Going for user display, print Part: info string */
77 static void _print_part_info(struct str *out, struct mimepart *mip,
78 struct ignoretab *doign, int level);
80 /* Query possible pipe command for MIME type */
81 static enum pipeflags _pipecmd(char **result, char const *content_type);
83 /* Create a pipe */
84 static FILE * _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote,
85 bool_t async);
87 /* Adjust output statistics */
88 SINLINE void _addstats(off_t *stats, off_t lines, off_t bytes);
90 /* Call mime_write() as approbiate and adjust statistics */
91 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
92 enum conversion convert, enum sendaction action,
93 struct quoteflt *qf, off_t *stats,
94 struct str *rest);
96 /* SIGPIPE handler */
97 static void _send_onpipe(int signo);
99 /* Send one part */
100 static int sendpart(struct message *zmp, struct mimepart *ip,
101 FILE *obuf, struct ignoretab *doign,
102 struct quoteflt *qf, enum sendaction action,
103 off_t *stats, int level);
105 /* Get a file for an attachment */
106 static FILE * newfile(struct mimepart *ip, int *ispipe);
108 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
109 struct quoteflt *qf, off_t *stats);
111 /* Output a reasonable looking status field */
112 static void statusput(const struct message *mp, FILE *obuf,
113 struct quoteflt *qf, off_t *stats);
114 static void xstatusput(const struct message *mp, FILE *obuf,
115 struct quoteflt *qf, off_t *stats);
117 static void put_from_(FILE *fp, struct mimepart *ip, off_t *stats);
119 static struct mimepart *
120 parsemsg(struct message *mp, enum parseflags pf)
122 struct mimepart *ip;
123 NYD_ENTER;
125 ip = csalloc(1, sizeof *ip);
126 ip->m_flag = mp->m_flag;
127 ip->m_have = mp->m_have;
128 ip->m_block = mp->m_block;
129 ip->m_offset = mp->m_offset;
130 ip->m_size = mp->m_size;
131 ip->m_xsize = mp->m_xsize;
132 ip->m_lines = mp->m_lines;
133 ip->m_xlines = mp->m_lines;
134 if (parsepart(mp, ip, pf, 0) != OKAY)
135 ip = NULL;
136 NYD_LEAVE;
137 return ip;
140 static enum okay
141 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
142 int level)
144 char *cp_b, *cp;
145 enum okay rv = STOP;
146 NYD_ENTER;
148 ip->m_ct_type = hfield1("content-type", (struct message*)ip);
149 if (ip->m_ct_type != NULL) {
150 cp_b = ip->m_ct_type_plain = savestr(ip->m_ct_type);
151 if ((cp = strchr(cp_b, ';')) != NULL)
152 *cp = '\0';
153 cp = cp_b + strlen(cp_b);
154 while (cp > cp_b && blankchar(cp[-1]))
155 --cp;
156 *cp = '\0';
157 } else if (ip->m_parent != NULL &&
158 ip->m_parent->m_mimecontent == MIME_DIGEST)
159 ip->m_ct_type_plain = UNCONST("message/rfc822");
160 else
161 ip->m_ct_type_plain = UNCONST("text/plain");
163 if (ip->m_ct_type != NULL)
164 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
165 if (ip->m_charset == NULL)
166 ip->m_charset = charset_get_7bit();
168 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
169 (struct message*)ip);
170 ip->m_mimeenc = (ip->m_ct_transfer_enc != NULL)
171 ? mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
173 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
174 (ip->m_filename = mime_getparam("filename", cp)) == NULL) &&
175 ip->m_ct_type != NULL)
176 ip->m_filename = mime_getparam("name", ip->m_ct_type);
178 ip->m_mimecontent = mime_classify_content_of_part(ip);
180 if (pf & PARSE_PARTS) {
181 if (level > 9999) { /* TODO MAGIC */
182 fprintf(stderr, tr(36, "MIME content too deeply nested\n"));
183 goto jleave;
185 switch (ip->m_mimecontent) {
186 case MIME_PKCS7:
187 if (pf & PARSE_DECRYPT) {
188 #ifdef HAVE_SSL
189 parsepkcs7(zmp, ip, pf, level);
190 break;
191 #else
192 fprintf(stderr, tr(225, "No SSL support compiled in.\n"));
193 goto jleave;
194 #endif
196 /* FALLTHRU */
197 default:
198 break;
199 case MIME_MULTI:
200 case MIME_ALTERNATIVE:
201 case MIME_DIGEST:
202 _parsemultipart(zmp, ip, pf, level);
203 break;
204 case MIME_822:
205 parse822(zmp, ip, pf, level);
206 break;
209 rv = OKAY;
210 jleave:
211 NYD_LEAVE;
212 return rv;
215 static void
216 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
217 int level)
219 int c, lastc = '\n';
220 size_t cnt;
221 FILE *ibuf;
222 off_t offs;
223 struct mimepart *np;
224 long lines;
225 NYD_ENTER;
227 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
228 goto jleave;
230 cnt = ip->m_size;
231 lines = ip->m_lines;
232 while (cnt && ((c = getc(ibuf)) != EOF)) {
233 --cnt;
234 if (c == '\n') {
235 --lines;
236 if (lastc == '\n')
237 break;
239 lastc = c;
241 offs = ftell(ibuf);
243 np = csalloc(1, sizeof *np);
244 np->m_flag = MNOFROM;
245 np->m_have = HAVE_HEADER | HAVE_BODY;
246 np->m_block = mailx_blockof(offs);
247 np->m_offset = mailx_offsetof(offs);
248 np->m_size = np->m_xsize = cnt;
249 np->m_lines = np->m_xlines = lines;
250 np->m_partstring = ip->m_partstring;
251 np->m_parent = ip;
252 ip->m_multipart = np;
254 if (ok_blook(rfc822_body_from_)) {
255 substdate((struct message*)np);
256 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
259 parsepart(zmp, np, pf, level + 1);
260 jleave:
261 NYD_LEAVE;
264 #ifdef HAVE_SSL
265 static void
266 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
267 int level)
269 struct message m, *xmp;
270 struct mimepart *np;
271 char *to, *cc;
272 NYD_ENTER;
274 memcpy(&m, ip, sizeof m);
275 to = hfield1("to", zmp);
276 cc = hfield1("cc", zmp);
278 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
279 np = csalloc(1, sizeof *np);
280 np->m_flag = xmp->m_flag;
281 np->m_have = xmp->m_have;
282 np->m_block = xmp->m_block;
283 np->m_offset = xmp->m_offset;
284 np->m_size = xmp->m_size;
285 np->m_xsize = xmp->m_xsize;
286 np->m_lines = xmp->m_lines;
287 np->m_xlines = xmp->m_xlines;
288 np->m_partstring = ip->m_partstring;
290 if (parsepart(zmp, np, pf, level + 1) == OKAY) {
291 np->m_parent = ip;
292 ip->m_multipart = np;
295 NYD_LEAVE;
297 #endif
299 static void
300 _parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
301 int level)
303 /* TODO Instead of the recursive multiple run parse we have today,
304 * TODO the send/MIME layer rewrite must create a "tree" of parts with
305 * TODO a single-pass parse, then address each part directly as
306 * TODO necessary; since boundaries start with -- and the content
307 * TODO rather forms a stack this is pretty cheap indeed! */
308 struct mimepart *np = NULL;
309 char *boundary, *line = NULL;
310 size_t linesize = 0, linelen, cnt, boundlen;
311 FILE *ibuf;
312 off_t offs;
313 int part = 0;
314 long lines = 0;
315 NYD_ENTER;
317 if ((boundary = mime_get_boundary(ip->m_ct_type, &linelen)) == NULL)
318 goto jleave;
320 boundlen = linelen;
321 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
322 goto jleave;
324 cnt = ip->m_size;
325 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
326 if (line[0] == '\n')
327 break;
328 offs = ftell(ibuf);
330 __newpart(ip, &np, offs, NULL);
331 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
332 /* XXX linelen includes LF */
333 if (!((lines > 0 || part == 0) && linelen > boundlen &&
334 !strncmp(line, boundary, boundlen))) {
335 ++lines;
336 continue;
339 /* Subpart boundary? */
340 if (line[boundlen] == '\n') {
341 offs = ftell(ibuf);
342 if (part > 0) {
343 __endpart(&np, offs - boundlen - 2, lines);
344 __newpart(ip, &np, offs - boundlen - 2, NULL);
346 __endpart(&np, offs, 2);
347 __newpart(ip, &np, offs, &part);
348 lines = 0;
349 continue;
352 /* Final boundary? Be aware of cases where there is no separating
353 * newline in between boundaries, as has been seen in a message with
354 * "Content-Type: multipart/appledouble;" */
355 if (linelen < boundlen + 2)
356 continue;
357 linelen -= boundlen + 2;
358 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
359 (linelen > 0 && line[boundlen + 2] != '\n'))
360 continue;
361 offs = ftell(ibuf);
362 if (part != 0) {
363 __endpart(&np, offs - boundlen - 4, lines);
364 __newpart(ip, &np, offs - boundlen - 4, NULL);
366 __endpart(&np, offs + cnt, 2);
367 break;
369 if (np) {
370 offs = ftell(ibuf);
371 __endpart(&np, offs, lines);
374 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
375 if (np->m_mimecontent != MIME_DISCARD)
376 parsepart(zmp, np, pf, level + 1);
377 free(line);
378 jleave:
379 NYD_LEAVE;
382 static void
383 __newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
385 struct mimepart *pp;
386 size_t sz;
387 NYD_ENTER;
389 *np = csalloc(1, sizeof **np);
390 (*np)->m_flag = MNOFROM;
391 (*np)->m_have = HAVE_HEADER | HAVE_BODY;
392 (*np)->m_block = mailx_blockof(offs);
393 (*np)->m_offset = mailx_offsetof(offs);
395 if (part) {
396 ++(*part);
397 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
398 sz += 20;
399 (*np)->m_partstring = salloc(sz);
400 if (ip->m_partstring)
401 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
402 else
403 snprintf((*np)->m_partstring, sz, "%u", *part);
404 } else
405 (*np)->m_mimecontent = MIME_DISCARD;
406 (*np)->m_parent = ip;
408 if (ip->m_multipart) {
409 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
411 pp->m_nextpart = *np;
412 } else
413 ip->m_multipart = *np;
414 NYD_LEAVE;
417 static void
418 __endpart(struct mimepart **np, off_t xoffs, long lines)
420 off_t offs;
421 NYD_ENTER;
423 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
424 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
425 (*np)->m_lines = (*np)->m_xlines = lines;
426 *np = NULL;
427 NYD_LEAVE;
430 static void
431 _print_part_info(struct str *out, struct mimepart *mip,
432 struct ignoretab *doign, int level)
434 struct str ct = {NULL, 0}, cd = {NULL, 0};
435 char const *ps;
436 struct str const *cpre, *csuf;
437 NYD_ENTER;
439 /* Max. 24 */
440 if (is_ign("content-type", 12, doign)) {
441 out->s = mip->m_ct_type_plain;
442 out->l = strlen(out->s);
443 ct.s = ac_alloc(out->l + 2 +1);
444 ct.s[0] = ',';
445 ct.s[1] = ' ';
446 ct.l = 2;
447 if (is_prefix("application/", out->s)) {
448 memcpy(ct.s + 2, "appl../", 7);
449 ct.l += 7;
450 out->l -= 12;
451 out->s += 12;
452 out->l = MIN(out->l, 17);
453 } else
454 out->l = MIN(out->l, 24);
455 memcpy(ct.s + ct.l, out->s, out->l);
456 ct.l += out->l;
457 ct.s[ct.l] = '\0';
460 /* Max. 27 */
461 if (is_ign("content-disposition", 19, doign) && mip->m_filename != NULL) {
462 struct str ti, to;
464 ti.l = strlen(ti.s = mip->m_filename);
465 mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV | TD_DELCTRL);
466 to.l = MIN(to.l, 25); /* FIXME MIME: filename may be multibyte enc!! */
467 cd.s = ac_alloc(to.l + 2 +1); /* FIXME ..visual length would be better */
468 cd.s[0] = ',';
469 cd.s[1] = ' ';
470 memcpy(cd.s + 2, to.s, to.l);
471 to.l += 2;
472 cd.s[to.l] = '\0';
473 free(to.s);
476 /* Take care of "99.99", i.e., 5 */
477 if ((ps = mip->m_partstring) == NULL || ps[0] == '\0')
478 ps = "?";
480 #ifdef HAVE_COLOUR
481 cpre = colour_get(COLOURSPEC_PARTINFO);
482 csuf = colour_get(COLOURSPEC_RESET);
483 #else
484 cpre = csuf = NULL;
485 #endif
487 /* Assume maximum possible sizes for 64 bit integers here to avoid any
488 * buffer overflows just in case we have a bug somewhere and / or the
489 * snprintf() is our internal version that doesn't really provide hard
490 * buffer cuts */
491 #define __msg "%s%s[-- #%s : %lu/%lu%s%s --]%s\n"
492 out->l = sizeof(__msg) +
493 #ifdef HAVE_COLOUR
494 (cpre != NULL ? cpre->l + csuf->l : 0) +
495 #endif
496 strlen(ps) + 2*21 + ct.l + cd.l +1;
497 out->s = salloc(out->l);
498 out->l = snprintf(out->s, out->l, __msg,
499 (level || (ps[0] != '1' && ps[1] == '\0') ? "\n" : ""),
500 (cpre != NULL ? cpre->s : ""),
501 ps, (ul_it)mip->m_lines, (ul_it)mip->m_size,
502 (ct.s != NULL ? ct.s : ""),
503 (cd.s != NULL ? cd.s : ""),
504 (csuf != NULL ? csuf->s : ""));
505 out->s[out->l] = '\0';
506 #undef __msg
508 if (cd.s != NULL)
509 ac_free(cd.s);
510 if (ct.s != NULL)
511 ac_free(ct.s);
512 NYD_LEAVE;
515 static enum pipeflags
516 _pipecmd(char **result, char const *content_type)
518 enum pipeflags ret;
519 char *s, *cp;
520 char const *cq;
521 NYD_ENTER;
523 ret = PIPE_NULL;
524 *result = NULL;
525 if (content_type == NULL)
526 goto jleave;
528 /* First check wether there is a special pipe-MIMETYPE handler */
529 s = ac_alloc(strlen(content_type) + 5 +1);
530 memcpy(s, "pipe-", 5);
531 cp = s + 5;
532 cq = content_type;
534 *cp++ = lowerconv(*cq);
535 while (*cq++ != '\0');
536 cp = vok_vlook(s);
537 ac_free(s);
539 if (cp == NULL)
540 goto jleave;
542 /* User specified a command, inspect for special cases */
543 if (cp[0] != '@') {
544 /* Normal command line */
545 ret = PIPE_COMM;
546 *result = cp;
547 } else if (*++cp == '\0') {
548 /* Treat as plain text */
549 ret = PIPE_TEXT;
550 } else if (!msglist_is_single) {
551 /* Viewing multiple messages in one go, don't block system */
552 ret = PIPE_MSG;
553 *result = UNCONST(tr(86,
554 "[Directly address message only to display this]\n"));
555 } else {
556 /* Viewing a single message only */
557 #if 0 /* TODO send/MIME layer rewrite: when we have a single-pass parser
558 * TODO then the parsing phase and the send phase will be separated;
559 * TODO that allows us to ask a user *before* we start the send, i.e.,
560 * TODO *before* a pager pipe is setup (which is the problem with
561 * TODO the '#if 0' code here) */
562 size_t l = strlen(content_type);
563 char const *x = tr(999, "Should i display a part `%s' (y/n)? ");
564 s = ac_alloc(l += strlen(x) +1);
565 snprintf(s, l - 1, x, content_type);
566 l = getapproval(s), TRU1;
567 puts(""); /* .. we've hijacked a pipe 8-] ... */
568 ac_free(s);
569 if (!l) {
570 x = tr(210, "[User skipped diplay]\n");
571 ret = PIPE_MSG;
572 *result = UNCONST(x);
573 } else
574 #endif
575 if (cp[0] == '&')
576 /* Asynchronous command, normal command line */
577 ret = PIPE_ASYNC, *result = ++cp;
578 else
579 ret = PIPE_COMM, *result = cp;
581 jleave:
582 NYD_LEAVE;
583 return ret;
586 static FILE *
587 _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote, bool_t async)
589 char const *sh;
590 FILE *rbuf;
591 NYD_ENTER;
593 rbuf = *qbuf;
595 if (quote) {
596 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER,
597 0600)) == NULL) {
598 perror(tr(173, "tmpfile"));
599 *qbuf = rbuf;
601 async = FAL0;
604 if ((sh = ok_vlook(SHELL)) == NULL)
605 sh = XSHELL;
606 if ((rbuf = Popen(pipecomm, "W", sh, async ? -1 : fileno(*qbuf))) == NULL)
607 perror(pipecomm);
608 else {
609 fflush(*qbuf);
610 if (*qbuf != stdout)
611 fflush(stdout);
613 NYD_LEAVE;
614 return rbuf;
617 SINLINE void
618 _addstats(off_t *stats, off_t lines, off_t bytes)
620 NYD_ENTER;
621 if (stats != NULL) {
622 if (stats[0] >= 0)
623 stats[0] += lines;
624 stats[1] += bytes;
626 NYD_LEAVE;
629 SINLINE ssize_t
630 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
631 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
633 ssize_t sz = 0, n;
634 int flags;
635 char const *cp;
636 NYD_ENTER;
638 #if 0
639 Well ... it turns out to not work like that since of course a valid
640 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
641 after an empty line has been seen, which cannot be detected that easily
642 right here!
643 ifdef HAVE_DEBUG /* TODO assert legacy */
644 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
645 * TODO other input situations handle RFC 4155 OR, if newly generated,
646 * TODO enforce quoted-printable if there is From_, as "required" by
647 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
648 * TODO if it may happen in this path, we should just treat decryption
649 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
650 if (action == SEND_MBOX || action == SEND_DECRYPT)
651 assert(!is_head(buf, len));
652 #else
653 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
654 is_head(buf, len)) {
655 putc('>', fp);
656 ++sz;
658 #endif
660 flags = ((int)action & _TD_EOF);
661 action &= ~_TD_EOF;
662 n = mime_write(buf, len, fp,
663 action == SEND_MBOX ? CONV_NONE : convert,
664 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
665 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
666 ? TD_ISPR | TD_ICONV
667 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
668 ? TD_ICONV : (action == SEND_TOFLTR)
669 ? TD_DELCTRL : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
670 qf, rest);
671 if (n < 0)
672 sz = n;
673 else if (n > 0) {
674 sz = (ssize_t)((size_t)sz + n);
675 n = 0;
676 if (stats != NULL && stats[0] != -1)
677 for (cp = buf; PTRCMP(cp, <, buf + sz); ++cp)
678 if (*cp == '\n')
679 ++n;
680 _addstats(stats, n, sz);
682 NYD_LEAVE;
683 return sz;
686 static void
687 _send_onpipe(int signo)
689 NYD_X; /* Signal handler */
690 UNUSED(signo);
691 siglongjmp(_send_pipejmp, 1);
694 static int
695 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
696 struct ignoretab *doign, struct quoteflt *qf,
697 enum sendaction volatile action, off_t *volatile stats, int level)
699 int volatile ispipe, rv = 0;
700 struct str rest;
701 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
702 size_t linesize = 0, linelen, cnt;
703 int dostat, infld = 0, ignoring = 1, isenc, c;
704 struct mimepart *volatile np;
705 FILE * volatile ibuf = NULL, * volatile pbuf = obuf, * volatile qbuf = obuf,
706 *origobuf = obuf;
707 enum conversion volatile convert;
708 sighandler_type volatile oldpipe = SIG_DFL;
709 long lineno = 0;
710 NYD_ENTER;
712 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
713 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
714 goto jskip;
716 dostat = 0;
717 if (level == 0) {
718 if (doign != NULL) {
719 if (!is_ign("status", 6, doign))
720 dostat |= 1;
721 if (!is_ign("x-status", 8, doign))
722 dostat |= 2;
723 } else
724 dostat = 3;
726 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
727 rv = -1;
728 goto jleave;
730 cnt = ip->m_size;
732 if (ip->m_mimecontent == MIME_DISCARD)
733 goto jskip;
735 if (!(ip->m_flag & MNOFROM))
736 while (cnt && (c = getc(ibuf)) != EOF) {
737 cnt--;
738 if (c == '\n')
739 break;
741 isenc = 0;
742 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
743 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
744 action == SEND_TOSRCH || action == SEND_TOFLTR)
745 ? CONV_FROMHDR : CONV_NONE;
747 /* Work the headers */
748 quoteflt_reset(qf, obuf);
749 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
750 ++lineno;
751 if (line[0] == '\n') {
752 /* If line is blank, we've reached end of headers, so force out
753 * status: field and note that we are no longer in header fields */
754 if (dostat & 1)
755 statusput(zmp, obuf, qf, stats);
756 if (dostat & 2)
757 xstatusput(zmp, obuf, qf, stats);
758 if (doign != allignore)
759 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
760 break;
763 isenc &= ~1;
764 if (infld && blankchar(line[0])) {
765 /* If this line is a continuation (SP / HT) of a previous header
766 * field, determine if the start of the line is a MIME encoded word */
767 if (isenc & 2) {
768 for (cp = line; blankchar(*cp); ++cp);
769 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
770 cp[0] == '=' && cp[1] == '?')
771 isenc |= 1;
773 } else {
774 /* Pick up the header field if we have one */
775 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
777 cp2 = cp;
778 while (spacechar(*cp))
779 ++cp;
780 if (cp[0] != ':' && level == 0 && lineno == 1) {
781 /* Not a header line, force out status: This happens in uucp style
782 * mail where there are no headers at all */
783 if (dostat & 1)
784 statusput(zmp, obuf, qf, stats);
785 if (dostat & 2)
786 xstatusput(zmp, obuf, qf, stats);
787 if (doign != allignore)
788 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
789 break;
792 /* If it is an ignored field and we care about such things, skip it.
793 * Misuse dostat also for another bit xxx use a bitenum + for more */
794 if (ok_blook(keep_content_length))
795 dostat |= 1 << 2;
796 c = *cp2;
797 *cp2 = 0; /* temporarily null terminate */
798 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
799 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
800 (!asccasecmp(line, "content-length") ||
801 !asccasecmp(line, "lines"))))
802 ignoring = 1;
803 else if (!asccasecmp(line, "status")) {
804 /* If field is "status," go compute and print real Status: field */
805 if (dostat & 1) {
806 statusput(zmp, obuf, qf, stats);
807 dostat &= ~1;
808 ignoring = 1;
810 } else if (!asccasecmp(line, "x-status")) {
811 /* If field is "status," go compute and print real Status: field */
812 if (dostat & 2) {
813 xstatusput(zmp, obuf, qf, stats);
814 dostat &= ~2;
815 ignoring = 1;
817 } else {
818 ignoring = 0;
819 #ifdef HAVE_COLOUR
820 pipecomm = savestrbuf(line, PTR2SIZE(cp2 - line));
821 #endif
823 *cp2 = c;
824 dostat &= ~(1 << 2);
825 infld = 1;
828 /* Determine if the end of the line is a MIME encoded word */
829 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
830 * TODO with header follow lines, and it should be up to the backend
831 * TODO what happens and what not, i.e., it doesn't matter wether it's
832 * TODO a MIME-encoded word or not, as long as a single separating space
833 * TODO remains in between lines (the MIME stuff will correctly remove
834 * TODO whitespace in between multiple adjacent encoded words) */
835 isenc &= ~2;
836 if (cnt && (c = getc(ibuf)) != EOF) {
837 if (blankchar(c)) {
838 cp = line + linelen - 1;
839 if (linelen > 0 && *cp == '\n')
840 --cp;
841 while (cp >= line && whitechar(*cp))
842 --cp;
843 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
844 isenc |= 2;
846 ungetc(c, ibuf);
849 if (!ignoring) {
850 size_t len = linelen;
851 start = line;
852 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
853 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
854 action == SEND_TOSRCH || action == SEND_TOFLTR) {
855 /* Strip blank characters if two MIME-encoded words follow on
856 * continuing lines */
857 if (isenc & 1)
858 while (len > 0 && blankchar(*start)) {
859 ++start;
860 --len;
862 if (isenc & 2)
863 if (len > 0 && start[len - 1] == '\n')
864 --len;
865 while (len > 0 && blankchar(start[len - 1]))
866 --len;
868 #ifdef HAVE_COLOUR
870 bool_t colour_stripped = FAL0;
871 if (pipecomm != NULL) {
872 colour_put_header(obuf, pipecomm);
873 if (len > 0 && start[len - 1] == '\n') {
874 colour_stripped = TRU1;
875 --len;
878 #endif
879 _out(start, len, obuf, convert, action, qf, stats, NULL);
880 #ifdef HAVE_COLOUR
881 if (pipecomm != NULL) {
882 colour_reset(obuf); /* XXX reset after \n!! */
883 if (colour_stripped)
884 fputc('\n', obuf);
887 #endif
888 if (ferror(obuf)) {
889 free(line);
890 rv = -1;
891 goto jleave;
895 quoteflt_flush(qf);
896 free(line);
897 line = NULL;
899 jskip:
900 switch (ip->m_mimecontent) {
901 case MIME_822:
902 switch (action) {
903 case SEND_TOFLTR:
904 putc('\0', obuf);
905 /* FALLTHRU */
906 case SEND_TODISP:
907 case SEND_TODISP_ALL:
908 case SEND_QUOTE:
909 case SEND_QUOTE_ALL:
910 if (ok_blook(rfc822_body_from_)) {
911 if (qf->qf_pfix_len > 0) {
912 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
913 qf->qf_pfix_len, obuf);
914 if (i == qf->qf_pfix_len)
915 _addstats(stats, 0, i);
917 put_from_(obuf, ip->m_multipart, stats);
919 /* FALLTHRU */
920 case SEND_TOSRCH:
921 case SEND_DECRYPT:
922 goto jmulti;
923 case SEND_TOFILE:
924 case SEND_TOPIPE:
925 if (ok_blook(rfc822_body_from_))
926 put_from_(obuf, ip->m_multipart, stats);
927 /* FALLTHRU */
928 case SEND_MBOX:
929 case SEND_RFC822:
930 case SEND_SHOW:
931 break;
933 break;
934 case MIME_TEXT_HTML:
935 if (action == SEND_TOFLTR)
936 putc('\b', obuf);
937 /* FALLTHRU */
938 case MIME_TEXT:
939 case MIME_TEXT_PLAIN:
940 switch (action) {
941 case SEND_TODISP:
942 case SEND_TODISP_ALL:
943 case SEND_QUOTE:
944 case SEND_QUOTE_ALL:
945 ispipe = TRU1;
946 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
947 case PIPE_MSG:
948 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
949 stats, NULL);
950 pipecomm = NULL;
951 /* FALLTRHU */
952 case PIPE_TEXT:
953 case PIPE_COMM:
954 case PIPE_ASYNC:
955 case PIPE_NULL:
956 break;
958 /* FALLTRHU */
959 default:
960 break;
962 break;
963 case MIME_DISCARD:
964 if (action != SEND_DECRYPT)
965 goto jleave;
966 break;
967 case MIME_PKCS7:
968 if (action != SEND_MBOX && action != SEND_RFC822 &&
969 action != SEND_SHOW && ip->m_multipart != NULL)
970 goto jmulti;
971 /* FALLTHRU */
972 default:
973 switch (action) {
974 case SEND_TODISP:
975 case SEND_TODISP_ALL:
976 case SEND_QUOTE:
977 case SEND_QUOTE_ALL:
978 ispipe = TRU1;
979 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
980 case PIPE_MSG:
981 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
982 stats, NULL);
983 pipecomm = NULL;
984 break;
985 case PIPE_ASYNC:
986 ispipe = FAL0;
987 /* FALLTHRU */
988 case PIPE_COMM:
989 case PIPE_NULL:
990 break;
991 case PIPE_TEXT:
992 goto jcopyout; /* break; break; */
994 if (pipecomm != NULL)
995 break;
996 if (level == 0 && cnt) {
997 char const *x = tr(210, "[Binary content]\n");
998 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1000 /* FALLTHRU */
1001 case SEND_TOFLTR:
1002 goto jleave;
1003 case SEND_TOFILE:
1004 case SEND_TOPIPE:
1005 case SEND_TOSRCH:
1006 case SEND_DECRYPT:
1007 case SEND_MBOX:
1008 case SEND_RFC822:
1009 case SEND_SHOW:
1010 break;
1012 break;
1013 case MIME_ALTERNATIVE:
1014 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
1015 !ok_blook(print_alternatives)) {
1016 bool_t doact = FAL0;
1018 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
1019 if (np->m_mimecontent == MIME_TEXT_PLAIN)
1020 doact = TRU1;
1021 if (doact) {
1022 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1023 if (np->m_ct_type_plain != NULL && action != SEND_QUOTE) {
1024 _print_part_info(&rest, np, doign, level);
1025 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1026 NULL);
1028 if (doact && np->m_mimecontent == MIME_TEXT_PLAIN) {
1029 doact = FAL0;
1030 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
1031 level + 1);
1032 quoteflt_reset(qf, origobuf);
1033 if (rv < 0)
1034 break;
1037 goto jleave;
1040 /* FALLTHRU */
1041 case MIME_MULTI:
1042 case MIME_DIGEST:
1043 switch (action) {
1044 case SEND_TODISP:
1045 case SEND_TODISP_ALL:
1046 case SEND_QUOTE:
1047 case SEND_QUOTE_ALL:
1048 case SEND_TOFILE:
1049 case SEND_TOPIPE:
1050 case SEND_TOSRCH:
1051 case SEND_TOFLTR:
1052 case SEND_DECRYPT:
1053 jmulti:
1054 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
1055 ip->m_multipart != NULL &&
1056 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
1057 ip->m_multipart->m_nextpart == NULL) {
1058 char const *x = tr(85,
1059 "[Missing multipart boundary - use \"show\" to display "
1060 "the raw message]\n");
1061 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1064 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1065 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
1066 continue;
1067 ispipe = FAL0;
1068 switch (action) {
1069 case SEND_TOFILE:
1070 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
1071 break;
1072 stats = NULL;
1073 if ((obuf = newfile(np, UNVOLATILE(&ispipe))) == NULL)
1074 continue;
1075 if (!ispipe)
1076 break;
1077 if (sigsetjmp(_send_pipejmp, 1)) {
1078 rv = -1;
1079 goto jpipe_close;
1081 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1082 break;
1083 case SEND_TODISP:
1084 case SEND_TODISP_ALL:
1085 case SEND_QUOTE_ALL:
1086 if (ip->m_mimecontent != MIME_MULTI &&
1087 ip->m_mimecontent != MIME_ALTERNATIVE &&
1088 ip->m_mimecontent != MIME_DIGEST)
1089 break;
1090 _print_part_info(&rest, np, doign, level);
1091 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1092 NULL);
1093 break;
1094 case SEND_TOFLTR:
1095 putc('\0', obuf);
1096 /* FALLTHRU */
1097 case SEND_MBOX:
1098 case SEND_RFC822:
1099 case SEND_SHOW:
1100 case SEND_TOSRCH:
1101 case SEND_QUOTE:
1102 case SEND_DECRYPT:
1103 case SEND_TOPIPE:
1104 break;
1107 quoteflt_flush(qf);
1108 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
1109 rv = -1;
1110 quoteflt_reset(qf, origobuf);
1111 if (action == SEND_QUOTE)
1112 break;
1113 if (action == SEND_TOFILE && obuf != origobuf) {
1114 if (!ispipe)
1115 Fclose(obuf);
1116 else {
1117 jpipe_close:
1118 safe_signal(SIGPIPE, SIG_IGN);
1119 Pclose(obuf, TRU1);
1120 safe_signal(SIGPIPE, oldpipe);
1124 goto jleave;
1125 case SEND_MBOX:
1126 case SEND_RFC822:
1127 case SEND_SHOW:
1128 break;
1132 /* Copy out message body */
1133 jcopyout:
1134 if (doign == allignore && level == 0) /* skip final blank line */
1135 --cnt;
1136 switch (ip->m_mimeenc) {
1137 case MIME_BIN:
1138 if (stats != NULL)
1139 stats[0] = -1;
1140 /* FALLTHRU */
1141 case MIME_7B:
1142 case MIME_8B:
1143 convert = CONV_NONE;
1144 break;
1145 case MIME_QP:
1146 convert = CONV_FROMQP;
1147 break;
1148 case MIME_B64:
1149 switch (ip->m_mimecontent) {
1150 case MIME_TEXT:
1151 case MIME_TEXT_PLAIN:
1152 case MIME_TEXT_HTML:
1153 convert = CONV_FROMB64_T;
1154 break;
1155 default:
1156 convert = CONV_FROMB64;
1158 break;
1159 default:
1160 convert = CONV_NONE;
1163 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1164 action == SEND_RFC822 || action == SEND_SHOW)
1165 convert = CONV_NONE;
1166 #ifdef HAVE_ICONV
1167 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1168 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1169 action == SEND_TOSRCH) &&
1170 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1171 ip->m_mimecontent == MIME_TEXT_HTML ||
1172 ip->m_mimecontent == MIME_TEXT)) {
1173 char const *tcs = charset_get_lc();
1175 if (iconvd != (iconv_t)-1)
1176 n_iconv_close(iconvd);
1177 /* TODO Since Base64 has an odd 4:3 relation in between input
1178 * TODO and output an input line may end with a partial
1179 * TODO multibyte character; this is no problem at all unless
1180 * TODO we send to the display or whatever, i.e., ensure
1181 * TODO makeprint() or something; to avoid this trap, *force*
1182 * TODO iconv(), in which case this layer will handle leftovers
1183 * TODO correctly */
1184 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
1185 asccasecmp(charset_get_7bit(), ip->m_charset))) {
1186 iconvd = n_iconv_open(tcs, ip->m_charset);
1187 /* XXX Don't bail out if we cannot iconv(3) here;
1188 * XXX alternatively we could avoid trying to open
1189 * XXX if ip->m_charset is "unknown-8bit", which was
1190 * XXX the one that has bitten me?? */
1192 * TODO errors should DEFINETELY not be scrolled away!
1193 * TODO what about an error buffer (think old shsp(1)),
1194 * TODO re-dump errors since last snapshot when the
1195 * TODO command loop enters again? i.e., at least print
1196 * TODO "There were errors ?" before the next prompt,
1197 * TODO so that the user can look at the error buffer?
1199 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1200 fprintf(stderr, tr(179, "Cannot convert from %s to %s\n"),
1201 ip->m_charset, tcs);
1202 /*rv = 1; goto jleave;*/
1206 #endif
1208 if (pipecomm != NULL && (action == SEND_TODISP ||
1209 action == SEND_TODISP_ALL || action == SEND_QUOTE ||
1210 action == SEND_QUOTE_ALL)) {
1211 qbuf = obuf;
1212 pbuf = _pipefile(pipecomm, UNVOLATILE(&qbuf),
1213 (action == SEND_QUOTE || action == SEND_QUOTE_ALL), !ispipe);
1214 action = SEND_TOPIPE;
1215 if (pbuf != qbuf) {
1216 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1217 if (sigsetjmp(_send_pipejmp, 1))
1218 goto jend;
1220 } else
1221 pbuf = qbuf = obuf;
1224 bool_t eof;
1225 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1226 off_t *save_stats = stats;
1228 if (pbuf != origobuf) {
1229 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1230 stats = NULL;
1232 eof = FAL0;
1233 rest.s = NULL;
1234 rest.l = 0;
1236 quoteflt_reset(qf, pbuf);
1237 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1238 joutln:
1239 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1240 ferror(pbuf)) {
1241 rv = -1; /* XXX Should bail away?! */
1242 break;
1245 if (!eof && rest.l != 0) {
1246 linelen = 0;
1247 eof = TRU1;
1248 action |= _TD_EOF;
1249 goto joutln;
1251 quoteflt_flush(qf);
1252 if (rest.s != NULL)
1253 free(rest.s);
1255 if (pbuf != origobuf) {
1256 qf->qf_pfix_len = save_qf_pfix_len;
1257 stats = save_stats;
1261 jend:
1262 free(line);
1263 if (pbuf != qbuf) {
1264 safe_signal(SIGPIPE, SIG_IGN);
1265 Pclose(pbuf, ispipe);
1266 safe_signal(SIGPIPE, oldpipe);
1267 if (qbuf != obuf)
1268 pipecpy(qbuf, obuf, origobuf, qf, stats);
1270 #ifdef HAVE_ICONV
1271 if (iconvd != (iconv_t)-1)
1272 n_iconv_close(iconvd);
1273 #endif
1274 jleave:
1275 NYD_LEAVE;
1276 return rv;
1279 static FILE *
1280 newfile(struct mimepart *ip, int *ispipe)
1282 struct str in, out;
1283 char *f;
1284 FILE *fp;
1285 NYD_ENTER;
1287 f = ip->m_filename;
1288 *ispipe = 0;
1290 if (f != NULL && f != (char*)-1) {
1291 in.s = f;
1292 in.l = strlen(f);
1293 mime_fromhdr(&in, &out, TD_ISPR);
1294 memcpy(f, out.s, out.l);
1295 *(f + out.l) = '\0';
1296 free(out.s);
1299 if (options & OPT_INTERACTIVE) {
1300 char *f2, *f3;
1301 jgetname:
1302 printf(tr(278, "Enter filename for part %s (%s)"),
1303 (ip->m_partstring != NULL) ? ip->m_partstring : "?",
1304 ip->m_ct_type_plain);
1305 f2 = readstr_input(": ", (f != (char*)-1) ? f : NULL);
1306 if (f2 == NULL || *f2 == '\0') {
1307 fprintf(stderr, tr(279, "... skipping this\n"));
1308 fp = NULL;
1309 goto jleave;
1310 } else if (*f2 == '|')
1311 /* Pipes are expanded by the shell */
1312 f = f2;
1313 else if ((f3 = file_expand(f2)) == NULL)
1314 /* (Error message written by file_expand()) */
1315 goto jgetname;
1316 else
1317 f = f3;
1319 if (f == NULL || f == (char*)-1) {
1320 fp = NULL;
1321 goto jleave;
1324 if (*f == '|') {
1325 char const *cp;
1326 cp = ok_vlook(SHELL);
1327 if (cp == NULL)
1328 cp = XSHELL;
1329 fp = Popen(f + 1, "w", cp, 1);
1330 if (!(*ispipe = (fp != NULL)))
1331 perror(f);
1332 } else {
1333 if ((fp = Fopen(f, "w")) == NULL)
1334 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1336 jleave:
1337 NYD_LEAVE;
1338 return fp;
1341 static void
1342 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1343 off_t *stats)
1345 char *line = NULL; /* TODO line pool */
1346 size_t linesize = 0, linelen, cnt;
1347 ssize_t all_sz, sz;
1348 NYD_ENTER;
1350 fflush(pipebuf);
1351 rewind(pipebuf);
1352 cnt = fsize(pipebuf);
1353 all_sz = 0;
1355 quoteflt_reset(qf, outbuf);
1356 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1357 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1358 break;
1359 all_sz += sz;
1361 if ((sz = quoteflt_flush(qf)) > 0)
1362 all_sz += sz;
1363 if (line)
1364 free(line);
1366 if (all_sz > 0 && outbuf == origobuf)
1367 _addstats(stats, 1, all_sz);
1368 fclose(pipebuf);
1369 NYD_LEAVE;
1372 static void
1373 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1374 off_t *stats)
1376 char statout[3], *cp = statout;
1377 NYD_ENTER;
1379 if (mp->m_flag & MREAD)
1380 *cp++ = 'R';
1381 if (!(mp->m_flag & MNEW))
1382 *cp++ = 'O';
1383 *cp = 0;
1384 if (statout[0]) {
1385 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1386 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1387 if (i > 0)
1388 _addstats(stats, 1, i);
1390 NYD_LEAVE;
1393 static void
1394 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1395 off_t *stats)
1397 char xstatout[4];
1398 char *xp = xstatout;
1399 NYD_ENTER;
1401 if (mp->m_flag & MFLAGGED)
1402 *xp++ = 'F';
1403 if (mp->m_flag & MANSWERED)
1404 *xp++ = 'A';
1405 if (mp->m_flag & MDRAFTED)
1406 *xp++ = 'T';
1407 *xp = 0;
1408 if (xstatout[0]) {
1409 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1410 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1411 if (i > 0)
1412 _addstats(stats, 1, i);
1414 NYD_LEAVE;
1417 static void
1418 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1420 char const *froma, *date, *nl;
1421 int i;
1422 NYD_ENTER;
1424 if (ip != NULL && ip->m_from != NULL) {
1425 froma = ip->m_from;
1426 date = fakedate(ip->m_time);
1427 nl = "\n";
1428 } else {
1429 froma = myname;
1430 date = time_current.tc_ctime;
1431 nl = "";
1434 colour_put(fp, COLOURSPEC_FROM_);
1435 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1436 colour_reset(fp);
1437 if (i > 0)
1438 _addstats(stats, (*nl != '\0'), i);
1439 NYD_LEAVE;
1442 FL int
1443 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1444 char const *prefix, enum sendaction action, off_t *stats)
1446 struct quoteflt qf;
1447 size_t cnt, sz, i;
1448 FILE *ibuf;
1449 enum parseflags pf;
1450 struct mimepart *ip;
1451 int rv = -1, c;
1452 NYD_ENTER;
1454 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
1455 did_print_dot = 1;
1456 if (stats != NULL)
1457 stats[0] = stats[1] = 0;
1458 quoteflt_init(&qf, prefix);
1460 /* First line is the From_ line, so no headers there to worry about */
1461 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1462 goto jleave;
1464 cnt = mp->m_size;
1465 sz = 0;
1467 struct str const *cpre, *csuf;
1468 #ifdef HAVE_COLOUR
1469 cpre = colour_get(COLOURSPEC_FROM_);
1470 csuf = colour_get(COLOURSPEC_RESET);
1471 #else
1472 cpre = csuf = NULL;
1473 #endif
1474 if (mp->m_flag & MNOFROM) {
1475 if (doign != allignore && doign != fwdignore && action != SEND_RFC822)
1476 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1477 (cpre != NULL ? cpre->s : ""),
1478 (int)qf.qf_pfix_len, (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
1479 fakefrom(mp), fakedate(mp->m_time),
1480 (csuf != NULL ? csuf->s : ""));
1481 } else {
1482 if (doign != allignore && doign != fwdignore && action != SEND_RFC822) {
1483 if (qf.qf_pfix_len > 0) {
1484 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1485 if (i != qf.qf_pfix_len)
1486 goto jleave;
1487 sz += i;
1489 #ifdef HAVE_COLOUR
1490 if (cpre != NULL) {
1491 fputs(cpre->s, obuf);
1492 cpre = (struct str const*)0x1;
1494 #endif
1497 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1498 if (doign != allignore && doign != fwdignore &&
1499 action != SEND_RFC822) {
1500 #ifdef HAVE_COLOUR
1501 if (c == '\n' && csuf != NULL) {
1502 cpre = (struct str const*)0x1;
1503 fputs(csuf->s, obuf);
1505 #endif
1506 putc(c, obuf);
1507 sz++;
1509 --cnt;
1510 if (c == '\n')
1511 break;
1514 #ifdef HAVE_COLOUR
1515 if (csuf != NULL && cpre != (struct str const*)0x1)
1516 fputs(csuf->s, obuf);
1517 #endif
1520 if (sz > 0)
1521 _addstats(stats, 1, sz);
1523 pf = 0;
1524 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1525 pf |= PARSE_DECRYPT | PARSE_PARTS;
1526 if ((ip = parsemsg(mp, pf)) == NULL)
1527 goto jleave;
1529 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1530 jleave:
1531 quoteflt_destroy(&qf);
1532 NYD_LEAVE;
1533 return rv;
1536 /* vim:set fenc=utf-8:s-it-mode */