nail.h: adjust really_rewind(): POSIX Issue 7 defined a way..
[s-mailx.git] / send.c
blob8b3f5a11762db63ad0d58ff37b69e6fccc3746b3
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;
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 ip->m_ct_type_plain = savestr(ip->m_ct_type);
151 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
152 *cp = '\0';
153 } else if (ip->m_parent != NULL &&
154 ip->m_parent->m_mimecontent == MIME_DIGEST)
155 ip->m_ct_type_plain = UNCONST("message/rfc822");
156 else
157 ip->m_ct_type_plain = UNCONST("text/plain");
159 if (ip->m_ct_type != NULL)
160 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
161 if (ip->m_charset == NULL)
162 ip->m_charset = charset_get_7bit();
164 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
165 (struct message*)ip);
166 ip->m_mimeenc = (ip->m_ct_transfer_enc != NULL)
167 ? mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
169 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
170 (ip->m_filename = mime_getparam("filename", cp)) == NULL) &&
171 ip->m_ct_type != NULL)
172 ip->m_filename = mime_getparam("name", ip->m_ct_type);
174 ip->m_mimecontent = mime_classify_content_of_part(ip);
176 if (pf & PARSE_PARTS) {
177 if (level > 9999) { /* TODO MAGIC */
178 fprintf(stderr, tr(36, "MIME content too deeply nested\n"));
179 goto jleave;
181 switch (ip->m_mimecontent) {
182 case MIME_PKCS7:
183 if (pf & PARSE_DECRYPT) {
184 #ifdef HAVE_SSL
185 parsepkcs7(zmp, ip, pf, level);
186 break;
187 #else
188 fprintf(stderr, tr(225, "No SSL support compiled in.\n"));
189 goto jleave;
190 #endif
192 /* FALLTHRU */
193 default:
194 break;
195 case MIME_MULTI:
196 case MIME_ALTERNATIVE:
197 case MIME_DIGEST:
198 _parsemultipart(zmp, ip, pf, level);
199 break;
200 case MIME_822:
201 parse822(zmp, ip, pf, level);
202 break;
205 rv = OKAY;
206 jleave:
207 NYD_LEAVE;
208 return rv;
211 static void
212 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
213 int level)
215 int c, lastc = '\n';
216 size_t cnt;
217 FILE *ibuf;
218 off_t offs;
219 struct mimepart *np;
220 long lines;
221 NYD_ENTER;
223 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
224 goto jleave;
226 cnt = ip->m_size;
227 lines = ip->m_lines;
228 while (cnt && ((c = getc(ibuf)) != EOF)) {
229 --cnt;
230 if (c == '\n') {
231 --lines;
232 if (lastc == '\n')
233 break;
235 lastc = c;
237 offs = ftell(ibuf);
239 np = csalloc(1, sizeof *np);
240 np->m_flag = MNOFROM;
241 np->m_have = HAVE_HEADER | HAVE_BODY;
242 np->m_block = mailx_blockof(offs);
243 np->m_offset = mailx_offsetof(offs);
244 np->m_size = np->m_xsize = cnt;
245 np->m_lines = np->m_xlines = lines;
246 np->m_partstring = ip->m_partstring;
247 np->m_parent = ip;
248 ip->m_multipart = np;
250 if (ok_blook(rfc822_body_from_)) {
251 substdate((struct message*)np);
252 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
255 parsepart(zmp, np, pf, level + 1);
256 jleave:
257 NYD_LEAVE;
260 #ifdef HAVE_SSL
261 static void
262 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
263 int level)
265 struct message m, *xmp;
266 struct mimepart *np;
267 char *to, *cc;
268 NYD_ENTER;
270 memcpy(&m, ip, sizeof m);
271 to = hfield1("to", zmp);
272 cc = hfield1("cc", zmp);
274 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
275 np = csalloc(1, sizeof *np);
276 np->m_flag = xmp->m_flag;
277 np->m_have = xmp->m_have;
278 np->m_block = xmp->m_block;
279 np->m_offset = xmp->m_offset;
280 np->m_size = xmp->m_size;
281 np->m_xsize = xmp->m_xsize;
282 np->m_lines = xmp->m_lines;
283 np->m_xlines = xmp->m_xlines;
284 np->m_partstring = ip->m_partstring;
286 if (parsepart(zmp, np, pf, level + 1) == OKAY) {
287 np->m_parent = ip;
288 ip->m_multipart = np;
291 NYD_LEAVE;
293 #endif
295 static void
296 _parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
297 int level)
299 /* TODO Instead of the recursive multiple run parse we have today,
300 * TODO the send/MIME layer rewrite must create a "tree" of parts with
301 * TODO a single-pass parse, then address each part directly as
302 * TODO necessary; since boundaries start with -- and the content
303 * TODO rather forms a stack this is pretty cheap indeed! */
304 struct mimepart *np = NULL;
305 char *boundary, *line = NULL;
306 size_t linesize = 0, linelen, cnt, boundlen;
307 FILE *ibuf;
308 off_t offs;
309 int part = 0;
310 long lines = 0;
311 NYD_ENTER;
313 if ((boundary = mime_get_boundary(ip->m_ct_type, &linelen)) == NULL)
314 goto jleave;
316 boundlen = linelen;
317 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
318 goto jleave;
320 cnt = ip->m_size;
321 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
322 if (line[0] == '\n')
323 break;
324 offs = ftell(ibuf);
326 __newpart(ip, &np, offs, NULL);
327 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
328 /* XXX linelen includes LF */
329 if (!((lines > 0 || part == 0) && linelen > boundlen &&
330 !strncmp(line, boundary, boundlen))) {
331 ++lines;
332 continue;
335 /* Subpart boundary? */
336 if (line[boundlen] == '\n') {
337 offs = ftell(ibuf);
338 if (part > 0) {
339 __endpart(&np, offs - boundlen - 2, lines);
340 __newpart(ip, &np, offs - boundlen - 2, NULL);
342 __endpart(&np, offs, 2);
343 __newpart(ip, &np, offs, &part);
344 lines = 0;
345 continue;
348 /* Final boundary? Be aware of cases where there is no separating
349 * newline in between boundaries, as has been seen in a message with
350 * "Content-Type: multipart/appledouble;" */
351 if (linelen < boundlen + 2)
352 continue;
353 linelen -= boundlen + 2;
354 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
355 (linelen > 0 && line[boundlen + 2] != '\n'))
356 continue;
357 offs = ftell(ibuf);
358 if (part != 0) {
359 __endpart(&np, offs - boundlen - 4, lines);
360 __newpart(ip, &np, offs - boundlen - 4, NULL);
362 __endpart(&np, offs + cnt, 2);
363 break;
365 if (np) {
366 offs = ftell(ibuf);
367 __endpart(&np, offs, lines);
370 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
371 if (np->m_mimecontent != MIME_DISCARD)
372 parsepart(zmp, np, pf, level + 1);
373 free(line);
374 jleave:
375 NYD_LEAVE;
378 static void
379 __newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
381 struct mimepart *pp;
382 size_t sz;
383 NYD_ENTER;
385 *np = csalloc(1, sizeof **np);
386 (*np)->m_flag = MNOFROM;
387 (*np)->m_have = HAVE_HEADER | HAVE_BODY;
388 (*np)->m_block = mailx_blockof(offs);
389 (*np)->m_offset = mailx_offsetof(offs);
391 if (part) {
392 ++(*part);
393 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
394 sz += 20;
395 (*np)->m_partstring = salloc(sz);
396 if (ip->m_partstring)
397 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
398 else
399 snprintf((*np)->m_partstring, sz, "%u", *part);
400 } else
401 (*np)->m_mimecontent = MIME_DISCARD;
402 (*np)->m_parent = ip;
404 if (ip->m_multipart) {
405 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
407 pp->m_nextpart = *np;
408 } else
409 ip->m_multipart = *np;
410 NYD_LEAVE;
413 static void
414 __endpart(struct mimepart **np, off_t xoffs, long lines)
416 off_t offs;
417 NYD_ENTER;
419 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
420 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
421 (*np)->m_lines = (*np)->m_xlines = lines;
422 *np = NULL;
423 NYD_LEAVE;
426 static void
427 _print_part_info(struct str *out, struct mimepart *mip,
428 struct ignoretab *doign, int level)
430 struct str ct = {NULL, 0}, cd = {NULL, 0};
431 char const *ps;
432 struct str const *cpre, *csuf;
433 NYD_ENTER;
435 /* Max. 24 */
436 if (is_ign("content-type", 12, doign)) {
437 out->s = mip->m_ct_type_plain;
438 out->l = strlen(out->s);
439 ct.s = ac_alloc(out->l + 2 +1);
440 ct.s[0] = ',';
441 ct.s[1] = ' ';
442 ct.l = 2;
443 if (is_prefix("application/", out->s)) {
444 memcpy(ct.s + 2, "appl../", 7);
445 ct.l += 7;
446 out->l -= 12;
447 out->s += 12;
448 out->l = MIN(out->l, 17);
449 } else
450 out->l = MIN(out->l, 24);
451 memcpy(ct.s + ct.l, out->s, out->l);
452 ct.l += out->l;
453 ct.s[ct.l] = '\0';
456 /* Max. 27 */
457 if (is_ign("content-disposition", 19, doign) && mip->m_filename != NULL) {
458 struct str ti, to;
460 ti.l = strlen(ti.s = mip->m_filename);
461 mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV | TD_DELCTRL);
462 to.l = MIN(to.l, 25); /* FIXME MIME: filename may be multibyte enc!! */
463 cd.s = ac_alloc(to.l + 2 +1); /* FIXME ..visual length would be better */
464 cd.s[0] = ',';
465 cd.s[1] = ' ';
466 memcpy(cd.s + 2, to.s, to.l);
467 to.l += 2;
468 cd.s[to.l] = '\0';
469 free(to.s);
472 /* Take care of "99.99", i.e., 5 */
473 if ((ps = mip->m_partstring) == NULL || ps[0] == '\0')
474 ps = "?";
476 #ifdef HAVE_COLOUR
477 cpre = colour_get(COLOURSPEC_PARTINFO);
478 csuf = colour_get(COLOURSPEC_RESET);
479 #else
480 cpre = csuf = NULL;
481 #endif
483 /* Assume maximum possible sizes for 64 bit integers here to avoid any
484 * buffer overflows just in case we have a bug somewhere and / or the
485 * snprintf() is our internal version that doesn't really provide hard
486 * buffer cuts */
487 #define __msg "%s%s[-- #%s : %lu/%lu%s%s --]%s\n"
488 out->l = sizeof(__msg) +
489 #ifdef HAVE_COLOUR
490 (cpre != NULL ? cpre->l + csuf->l : 0) +
491 #endif
492 strlen(ps) + 2*21 + ct.l + cd.l +1;
493 out->s = salloc(out->l);
494 out->l = snprintf(out->s, out->l, __msg,
495 (level || (ps[0] != '1' && ps[1] == '\0') ? "\n" : ""),
496 (cpre != NULL ? cpre->s : ""),
497 ps, (ul_it)mip->m_lines, (ul_it)mip->m_size,
498 (ct.s != NULL ? ct.s : ""),
499 (cd.s != NULL ? cd.s : ""),
500 (csuf != NULL ? csuf->s : ""));
501 out->s[out->l] = '\0';
502 #undef __msg
504 if (cd.s != NULL)
505 ac_free(cd.s);
506 if (ct.s != NULL)
507 ac_free(ct.s);
508 NYD_LEAVE;
511 static enum pipeflags
512 _pipecmd(char **result, char const *content_type)
514 enum pipeflags ret;
515 char *s, *cp;
516 char const *cq;
517 NYD_ENTER;
519 ret = PIPE_NULL;
520 *result = NULL;
521 if (content_type == NULL)
522 goto jleave;
524 /* First check wether there is a special pipe-MIMETYPE handler */
525 s = ac_alloc(strlen(content_type) + 5 +1);
526 memcpy(s, "pipe-", 5);
527 cp = s + 5;
528 cq = content_type;
530 *cp++ = lowerconv(*cq);
531 while (*cq++ != '\0');
532 cp = vok_vlook(s);
533 ac_free(s);
535 if (cp == NULL)
536 goto jleave;
538 /* User specified a command, inspect for special cases */
539 if (cp[0] != '@') {
540 /* Normal command line */
541 ret = PIPE_COMM;
542 *result = cp;
543 } else if (*++cp == '\0') {
544 /* Treat as plain text */
545 ret = PIPE_TEXT;
546 } else if (!msglist_is_single) {
547 /* Viewing multiple messages in one go, don't block system */
548 ret = PIPE_MSG;
549 *result = UNCONST(tr(86,
550 "[Directly address message only to display this]\n"));
551 } else {
552 /* Viewing a single message only */
553 #if 0 /* TODO send/MIME layer rewrite: when we have a single-pass parser
554 * TODO then the parsing phase and the send phase will be separated;
555 * TODO that allows us to ask a user *before* we start the send, i.e.,
556 * TODO *before* a pager pipe is setup (which is the problem with
557 * TODO the '#if 0' code here) */
558 size_t l = strlen(content_type);
559 char const *x = tr(999, "Should i display a part `%s' (y/n)? ");
560 s = ac_alloc(l += strlen(x) +1);
561 snprintf(s, l - 1, x, content_type);
562 l = yorn(s);
563 puts(""); /* .. we've hijacked a pipe 8-] ... */
564 ac_free(s);
565 if (!l) {
566 x = tr(210, "[User skipped diplay]\n");
567 ret = PIPE_MSG;
568 *result = UNCONST(x);
569 } else
570 #endif
571 if (cp[0] == '&')
572 /* Asynchronous command, normal command line */
573 ret = PIPE_ASYNC, *result = ++cp;
574 else
575 ret = PIPE_COMM, *result = cp;
577 jleave:
578 NYD_LEAVE;
579 return ret;
582 static FILE *
583 _pipefile(char const *pipecomm, FILE **qbuf, bool_t quote, bool_t async)
585 char const *sh;
586 FILE *rbuf;
587 NYD_ENTER;
589 rbuf = *qbuf;
591 if (quote) {
592 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER,
593 0600)) == NULL) {
594 perror(tr(173, "tmpfile"));
595 *qbuf = rbuf;
597 async = FAL0;
600 if ((sh = ok_vlook(SHELL)) == NULL)
601 sh = XSHELL;
602 if ((rbuf = Popen(pipecomm, "W", sh, async ? -1 : fileno(*qbuf))) == NULL)
603 perror(pipecomm);
604 else {
605 fflush(*qbuf);
606 if (*qbuf != stdout)
607 fflush(stdout);
609 NYD_LEAVE;
610 return rbuf;
613 SINLINE void
614 _addstats(off_t *stats, off_t lines, off_t bytes)
616 NYD_ENTER;
617 if (stats != NULL) {
618 if (stats[0] >= 0)
619 stats[0] += lines;
620 stats[1] += bytes;
622 NYD_LEAVE;
625 SINLINE ssize_t
626 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
627 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
629 ssize_t sz = 0, n;
630 int flags;
631 char const *cp;
632 NYD_ENTER;
634 #if 0
635 Well ... it turns out to not work like that since of course a valid
636 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
637 after an empty line has been seen, which cannot be detected that easily
638 right here!
639 ifdef HAVE_DEBUG /* TODO assert legacy */
640 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
641 * TODO other input situations handle RFC 4155 OR, if newly generated,
642 * TODO enforce quoted-printable if there is From_, as "required" by
643 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
644 * TODO if it may happen in this path, we should just treat decryption
645 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
646 if (action == SEND_MBOX || action == SEND_DECRYPT)
647 assert(!is_head(buf, len));
648 #else
649 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
650 is_head(buf, len)) {
651 putc('>', fp);
652 ++sz;
654 #endif
656 flags = ((int)action & _TD_EOF);
657 action &= ~_TD_EOF;
658 n = mime_write(buf, len, fp,
659 action == SEND_MBOX ? CONV_NONE : convert,
660 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
661 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
662 ? TD_ISPR | TD_ICONV
663 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
664 ? TD_ICONV : (action == SEND_TOFLTR)
665 ? TD_DELCTRL : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
666 qf, rest);
667 if (n < 0)
668 sz = n;
669 else if (n > 0) {
670 sz = (ssize_t)((size_t)sz + n);
671 n = 0;
672 if (stats != NULL && stats[0] != -1)
673 for (cp = buf; PTRCMP(cp, <, buf + sz); ++cp)
674 if (*cp == '\n')
675 ++n;
676 _addstats(stats, n, sz);
678 NYD_LEAVE;
679 return sz;
682 static void
683 _send_onpipe(int signo)
685 NYD_X; /* Signal handler */
686 UNUSED(signo);
687 siglongjmp(_send_pipejmp, 1);
690 static int
691 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
692 struct ignoretab *doign, struct quoteflt *qf,
693 enum sendaction volatile action, off_t *volatile stats, int level)
695 int volatile ispipe, rv = 0;
696 struct str rest;
697 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
698 size_t linesize = 0, linelen, cnt;
699 int dostat, infld = 0, ignoring = 1, isenc, c;
700 struct mimepart *volatile np;
701 FILE * volatile ibuf = NULL, * volatile pbuf = obuf, * volatile qbuf = obuf,
702 *origobuf = obuf;
703 enum conversion volatile convert;
704 sighandler_type volatile oldpipe = SIG_DFL;
705 long lineno = 0;
706 NYD_ENTER;
708 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
709 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
710 goto jskip;
712 dostat = 0;
713 if (level == 0) {
714 if (doign != NULL) {
715 if (!is_ign("status", 6, doign))
716 dostat |= 1;
717 if (!is_ign("x-status", 8, doign))
718 dostat |= 2;
719 } else
720 dostat = 3;
722 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
723 rv = -1;
724 goto jleave;
726 cnt = ip->m_size;
728 if (ip->m_mimecontent == MIME_DISCARD)
729 goto jskip;
731 if (!(ip->m_flag & MNOFROM))
732 while (cnt && (c = getc(ibuf)) != EOF) {
733 cnt--;
734 if (c == '\n')
735 break;
737 isenc = 0;
738 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
739 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
740 action == SEND_TOSRCH || action == SEND_TOFLTR)
741 ? CONV_FROMHDR : CONV_NONE;
743 /* Work the headers */
744 quoteflt_reset(qf, obuf);
745 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
746 ++lineno;
747 if (line[0] == '\n') {
748 /* If line is blank, we've reached end of headers, so force out
749 * status: field and note that we are no longer in header fields */
750 if (dostat & 1)
751 statusput(zmp, obuf, qf, stats);
752 if (dostat & 2)
753 xstatusput(zmp, obuf, qf, stats);
754 if (doign != allignore)
755 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
756 break;
759 isenc &= ~1;
760 if (infld && blankchar(line[0])) {
761 /* If this line is a continuation (SP / HT) of a previous header
762 * field, determine if the start of the line is a MIME encoded word */
763 if (isenc & 2) {
764 for (cp = line; blankchar(*cp); ++cp);
765 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
766 cp[0] == '=' && cp[1] == '?')
767 isenc |= 1;
769 } else {
770 /* Pick up the header field if we have one */
771 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
773 cp2 = cp;
774 while (spacechar(*cp))
775 ++cp;
776 if (cp[0] != ':' && level == 0 && lineno == 1) {
777 /* Not a header line, force out status: This happens in uucp style
778 * mail where there are no headers at all */
779 if (dostat & 1)
780 statusput(zmp, obuf, qf, stats);
781 if (dostat & 2)
782 xstatusput(zmp, obuf, qf, stats);
783 if (doign != allignore)
784 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
785 break;
788 /* If it is an ignored field and we care about such things, skip it.
789 * Misuse dostat also for another bit xxx use a bitenum + for more */
790 if (ok_blook(keep_content_length))
791 dostat |= 1 << 2;
792 c = *cp2;
793 *cp2 = 0; /* temporarily null terminate */
794 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
795 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
796 (!asccasecmp(line, "content-length") ||
797 !asccasecmp(line, "lines"))))
798 ignoring = 1;
799 else if (!asccasecmp(line, "status")) {
800 /* If field is "status," go compute and print real Status: field */
801 if (dostat & 1) {
802 statusput(zmp, obuf, qf, stats);
803 dostat &= ~1;
804 ignoring = 1;
806 } else if (!asccasecmp(line, "x-status")) {
807 /* If field is "status," go compute and print real Status: field */
808 if (dostat & 2) {
809 xstatusput(zmp, obuf, qf, stats);
810 dostat &= ~2;
811 ignoring = 1;
813 } else {
814 ignoring = 0;
815 #ifdef HAVE_COLOUR
816 pipecomm = savestrbuf(line, PTR2SIZE(cp2 - line));
817 #endif
819 *cp2 = c;
820 dostat &= ~(1 << 2);
821 infld = 1;
824 /* Determine if the end of the line is a MIME encoded word */
825 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
826 * TODO with header follow lines, and it should be up to the backend
827 * TODO what happens and what not, i.e., it doesn't matter wether it's
828 * TODO a MIME-encoded word or not, as long as a single separating space
829 * TODO remains in between lines (the MIME stuff will correctly remove
830 * TODO whitespace in between multiple adjacent encoded words) */
831 isenc &= ~2;
832 if (cnt && (c = getc(ibuf)) != EOF) {
833 if (blankchar(c)) {
834 cp = line + linelen - 1;
835 if (linelen > 0 && *cp == '\n')
836 --cp;
837 while (cp >= line && whitechar(*cp))
838 --cp;
839 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
840 isenc |= 2;
842 ungetc(c, ibuf);
845 if (!ignoring) {
846 size_t len = linelen;
847 start = line;
848 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
849 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
850 action == SEND_TOSRCH || action == SEND_TOFLTR) {
851 /* Strip blank characters if two MIME-encoded words follow on
852 * continuing lines */
853 if (isenc & 1)
854 while (len > 0 && blankchar(*start)) {
855 ++start;
856 --len;
858 if (isenc & 2)
859 if (len > 0 && start[len - 1] == '\n')
860 --len;
861 while (len > 0 && blankchar(start[len - 1]))
862 --len;
864 #ifdef HAVE_COLOUR
866 bool_t colour_stripped = FAL0;
867 if (pipecomm != NULL) {
868 colour_put_header(obuf, pipecomm);
869 if (len > 0 && start[len - 1] == '\n') {
870 colour_stripped = TRU1;
871 --len;
874 #endif
875 _out(start, len, obuf, convert, action, qf, stats, NULL);
876 #ifdef HAVE_COLOUR
877 if (pipecomm != NULL) {
878 colour_reset(obuf); /* XXX reset after \n!! */
879 if (colour_stripped)
880 fputc('\n', obuf);
883 #endif
884 if (ferror(obuf)) {
885 free(line);
886 rv = -1;
887 goto jleave;
891 quoteflt_flush(qf);
892 free(line);
893 line = NULL;
895 jskip:
896 switch (ip->m_mimecontent) {
897 case MIME_822:
898 switch (action) {
899 case SEND_TOFLTR:
900 putc('\0', obuf);
901 /* FALLTHRU */
902 case SEND_TODISP:
903 case SEND_TODISP_ALL:
904 case SEND_QUOTE:
905 case SEND_QUOTE_ALL:
906 if (ok_blook(rfc822_body_from_)) {
907 if (qf->qf_pfix_len > 0) {
908 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
909 qf->qf_pfix_len, obuf);
910 if (i == qf->qf_pfix_len)
911 _addstats(stats, 0, i);
913 put_from_(obuf, ip->m_multipart, stats);
915 /* FALLTHRU */
916 case SEND_TOSRCH:
917 case SEND_DECRYPT:
918 goto jmulti;
919 case SEND_TOFILE:
920 case SEND_TOPIPE:
921 if (ok_blook(rfc822_body_from_))
922 put_from_(obuf, ip->m_multipart, stats);
923 /* FALLTHRU */
924 case SEND_MBOX:
925 case SEND_RFC822:
926 case SEND_SHOW:
927 break;
929 break;
930 case MIME_TEXT_HTML:
931 if (action == SEND_TOFLTR)
932 putc('\b', obuf);
933 /* FALLTHRU */
934 case MIME_TEXT:
935 case MIME_TEXT_PLAIN:
936 switch (action) {
937 case SEND_TODISP:
938 case SEND_TODISP_ALL:
939 case SEND_QUOTE:
940 case SEND_QUOTE_ALL:
941 ispipe = TRU1;
942 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
943 case PIPE_MSG:
944 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
945 stats, NULL);
946 pipecomm = NULL;
947 /* FALLTRHU */
948 case PIPE_TEXT:
949 case PIPE_COMM:
950 case PIPE_ASYNC:
951 case PIPE_NULL:
952 break;
954 /* FALLTRHU */
955 default:
956 break;
958 break;
959 case MIME_DISCARD:
960 if (action != SEND_DECRYPT)
961 goto jleave;
962 break;
963 case MIME_PKCS7:
964 if (action != SEND_MBOX && action != SEND_RFC822 &&
965 action != SEND_SHOW && ip->m_multipart != NULL)
966 goto jmulti;
967 /* FALLTHRU */
968 default:
969 switch (action) {
970 case SEND_TODISP:
971 case SEND_TODISP_ALL:
972 case SEND_QUOTE:
973 case SEND_QUOTE_ALL:
974 ispipe = TRU1;
975 switch (_pipecmd(&pipecomm, ip->m_ct_type_plain)) {
976 case PIPE_MSG:
977 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
978 stats, NULL);
979 pipecomm = NULL;
980 break;
981 case PIPE_ASYNC:
982 ispipe = FAL0;
983 /* FALLTHRU */
984 case PIPE_COMM:
985 case PIPE_NULL:
986 break;
987 case PIPE_TEXT:
988 goto jcopyout; /* break; break; */
990 if (pipecomm != NULL)
991 break;
992 if (level == 0 && cnt) {
993 char const *x = tr(210, "[Binary content]\n");
994 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
996 /* FALLTHRU */
997 case SEND_TOFLTR:
998 goto jleave;
999 case SEND_TOFILE:
1000 case SEND_TOPIPE:
1001 case SEND_TOSRCH:
1002 case SEND_DECRYPT:
1003 case SEND_MBOX:
1004 case SEND_RFC822:
1005 case SEND_SHOW:
1006 break;
1008 break;
1009 case MIME_ALTERNATIVE:
1010 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
1011 !ok_blook(print_alternatives)) {
1012 bool_t doact = FAL0;
1014 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
1015 if (np->m_mimecontent == MIME_TEXT_PLAIN)
1016 doact = TRU1;
1017 if (doact) {
1018 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1019 if (np->m_ct_type_plain != NULL && action != SEND_QUOTE) {
1020 _print_part_info(&rest, np, doign, level);
1021 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1022 NULL);
1024 if (doact && np->m_mimecontent == MIME_TEXT_PLAIN) {
1025 doact = FAL0;
1026 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
1027 level + 1);
1028 quoteflt_reset(qf, origobuf);
1029 if (rv < 0)
1030 break;
1033 goto jleave;
1036 /* FALLTHRU */
1037 case MIME_MULTI:
1038 case MIME_DIGEST:
1039 switch (action) {
1040 case SEND_TODISP:
1041 case SEND_TODISP_ALL:
1042 case SEND_QUOTE:
1043 case SEND_QUOTE_ALL:
1044 case SEND_TOFILE:
1045 case SEND_TOPIPE:
1046 case SEND_TOSRCH:
1047 case SEND_TOFLTR:
1048 case SEND_DECRYPT:
1049 jmulti:
1050 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
1051 ip->m_multipart != NULL &&
1052 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
1053 ip->m_multipart->m_nextpart == NULL) {
1054 char const *x = tr(85,
1055 "[Missing multipart boundary - use \"show\" to display "
1056 "the raw message]\n");
1057 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1060 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1061 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
1062 continue;
1063 ispipe = FAL0;
1064 switch (action) {
1065 case SEND_TOFILE:
1066 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
1067 break;
1068 stats = NULL;
1069 if ((obuf = newfile(np, UNVOLATILE(&ispipe))) == NULL)
1070 continue;
1071 if (!ispipe)
1072 break;
1073 if (sigsetjmp(_send_pipejmp, 1)) {
1074 rv = -1;
1075 goto jpipe_close;
1077 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1078 break;
1079 case SEND_TODISP:
1080 case SEND_TODISP_ALL:
1081 case SEND_QUOTE_ALL:
1082 if (ip->m_mimecontent != MIME_MULTI &&
1083 ip->m_mimecontent != MIME_ALTERNATIVE &&
1084 ip->m_mimecontent != MIME_DIGEST)
1085 break;
1086 _print_part_info(&rest, np, doign, level);
1087 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1088 NULL);
1089 break;
1090 case SEND_TOFLTR:
1091 putc('\0', obuf);
1092 /* FALLTHRU */
1093 case SEND_MBOX:
1094 case SEND_RFC822:
1095 case SEND_SHOW:
1096 case SEND_TOSRCH:
1097 case SEND_QUOTE:
1098 case SEND_DECRYPT:
1099 case SEND_TOPIPE:
1100 break;
1103 quoteflt_flush(qf);
1104 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
1105 rv = -1;
1106 quoteflt_reset(qf, origobuf);
1107 if (action == SEND_QUOTE)
1108 break;
1109 if (action == SEND_TOFILE && obuf != origobuf) {
1110 if (!ispipe)
1111 Fclose(obuf);
1112 else {
1113 jpipe_close:
1114 safe_signal(SIGPIPE, SIG_IGN);
1115 Pclose(obuf, TRU1);
1116 safe_signal(SIGPIPE, oldpipe);
1120 goto jleave;
1121 case SEND_MBOX:
1122 case SEND_RFC822:
1123 case SEND_SHOW:
1124 break;
1128 /* Copy out message body */
1129 jcopyout:
1130 if (doign == allignore && level == 0) /* skip final blank line */
1131 --cnt;
1132 switch (ip->m_mimeenc) {
1133 case MIME_BIN:
1134 if (stats != NULL)
1135 stats[0] = -1;
1136 /* FALLTHRU */
1137 case MIME_7B:
1138 case MIME_8B:
1139 convert = CONV_NONE;
1140 break;
1141 case MIME_QP:
1142 convert = CONV_FROMQP;
1143 break;
1144 case MIME_B64:
1145 switch (ip->m_mimecontent) {
1146 case MIME_TEXT:
1147 case MIME_TEXT_PLAIN:
1148 case MIME_TEXT_HTML:
1149 convert = CONV_FROMB64_T;
1150 break;
1151 default:
1152 convert = CONV_FROMB64;
1154 break;
1155 default:
1156 convert = CONV_NONE;
1159 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1160 action == SEND_RFC822 || action == SEND_SHOW)
1161 convert = CONV_NONE;
1162 #ifdef HAVE_ICONV
1163 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1164 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1165 action == SEND_TOSRCH) &&
1166 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1167 ip->m_mimecontent == MIME_TEXT_HTML ||
1168 ip->m_mimecontent == MIME_TEXT)) {
1169 char const *tcs = charset_get_lc();
1171 if (iconvd != (iconv_t)-1)
1172 n_iconv_close(iconvd);
1173 /* TODO Since Base64 has an odd 4:3 relation in between input
1174 * TODO and output an input line may end with a partial
1175 * TODO multibyte character; this is no problem at all unless
1176 * TODO we send to the display or whatever, i.e., ensure
1177 * TODO makeprint() or something; to avoid this trap, *force*
1178 * TODO iconv(), in which case this layer will handle leftovers
1179 * TODO correctly */
1180 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
1181 asccasecmp(charset_get_7bit(), ip->m_charset))) {
1182 iconvd = n_iconv_open(tcs, ip->m_charset);
1183 /* XXX Don't bail out if we cannot iconv(3) here;
1184 * XXX alternatively we could avoid trying to open
1185 * XXX if ip->m_charset is "unknown-8bit", which was
1186 * XXX the one that has bitten me?? */
1188 * TODO errors should DEFINETELY not be scrolled away!
1189 * TODO what about an error buffer (think old shsp(1)),
1190 * TODO re-dump errors since last snapshot when the
1191 * TODO command loop enters again? i.e., at least print
1192 * TODO "There were errors ?" before the next prompt,
1193 * TODO so that the user can look at the error buffer?
1195 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1196 fprintf(stderr, tr(179, "Cannot convert from %s to %s\n"),
1197 ip->m_charset, tcs);
1198 /*rv = 1; goto jleave;*/
1202 #endif
1204 if (pipecomm != NULL && (action == SEND_TODISP ||
1205 action == SEND_TODISP_ALL || action == SEND_QUOTE ||
1206 action == SEND_QUOTE_ALL)) {
1207 qbuf = obuf;
1208 pbuf = _pipefile(pipecomm, UNVOLATILE(&qbuf),
1209 (action == SEND_QUOTE || action == SEND_QUOTE_ALL), !ispipe);
1210 action = SEND_TOPIPE;
1211 if (pbuf != qbuf) {
1212 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1213 if (sigsetjmp(_send_pipejmp, 1))
1214 goto jend;
1216 } else
1217 pbuf = qbuf = obuf;
1220 bool_t eof;
1221 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1222 off_t *save_stats = stats;
1224 if (pbuf != origobuf) {
1225 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1226 stats = NULL;
1228 eof = FAL0;
1229 rest.s = NULL;
1230 rest.l = 0;
1232 quoteflt_reset(qf, pbuf);
1233 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1234 joutln:
1235 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1236 ferror(pbuf)) {
1237 rv = -1; /* XXX Should bail away?! */
1238 break;
1241 if (!eof && rest.l != 0) {
1242 linelen = 0;
1243 eof = TRU1;
1244 action |= _TD_EOF;
1245 goto joutln;
1247 quoteflt_flush(qf);
1248 if (rest.s != NULL)
1249 free(rest.s);
1251 if (pbuf != origobuf) {
1252 qf->qf_pfix_len = save_qf_pfix_len;
1253 stats = save_stats;
1257 jend:
1258 free(line);
1259 if (pbuf != qbuf) {
1260 safe_signal(SIGPIPE, SIG_IGN);
1261 Pclose(pbuf, ispipe);
1262 safe_signal(SIGPIPE, oldpipe);
1263 if (qbuf != obuf)
1264 pipecpy(qbuf, obuf, origobuf, qf, stats);
1266 #ifdef HAVE_ICONV
1267 if (iconvd != (iconv_t)-1)
1268 n_iconv_close(iconvd);
1269 #endif
1270 jleave:
1271 NYD_LEAVE;
1272 return rv;
1275 static FILE *
1276 newfile(struct mimepart *ip, int *ispipe)
1278 struct str in, out;
1279 char *f;
1280 FILE *fp;
1281 NYD_ENTER;
1283 f = ip->m_filename;
1284 *ispipe = 0;
1286 if (f != NULL && f != (char*)-1) {
1287 in.s = f;
1288 in.l = strlen(f);
1289 mime_fromhdr(&in, &out, TD_ISPR);
1290 memcpy(f, out.s, out.l);
1291 *(f + out.l) = '\0';
1292 free(out.s);
1295 if (options & OPT_INTERACTIVE) {
1296 char *f2, *f3;
1297 jgetname:
1298 printf(tr(278, "Enter filename for part %s (%s)"),
1299 (ip->m_partstring != NULL) ? ip->m_partstring : "?",
1300 ip->m_ct_type_plain);
1301 f2 = readstr_input(": ", (f != (char*)-1) ? f : NULL);
1302 if (f2 == NULL || *f2 == '\0') {
1303 fprintf(stderr, tr(279, "... skipping this\n"));
1304 fp = NULL;
1305 goto jleave;
1306 } else if (*f2 == '|')
1307 /* Pipes are expanded by the shell */
1308 f = f2;
1309 else if ((f3 = file_expand(f2)) == NULL)
1310 /* (Error message written by file_expand()) */
1311 goto jgetname;
1312 else
1313 f = f3;
1315 if (f == NULL || f == (char*)-1) {
1316 fp = NULL;
1317 goto jleave;
1320 if (*f == '|') {
1321 char const *cp;
1322 cp = ok_vlook(SHELL);
1323 if (cp == NULL)
1324 cp = XSHELL;
1325 fp = Popen(f + 1, "w", cp, 1);
1326 if (!(*ispipe = (fp != NULL)))
1327 perror(f);
1328 } else {
1329 if ((fp = Fopen(f, "w")) == NULL)
1330 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1332 jleave:
1333 NYD_LEAVE;
1334 return fp;
1337 static void
1338 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1339 off_t *stats)
1341 char *line = NULL; /* TODO line pool */
1342 size_t linesize = 0, linelen, cnt;
1343 ssize_t all_sz, sz;
1344 NYD_ENTER;
1346 fflush(pipebuf);
1347 rewind(pipebuf);
1348 cnt = fsize(pipebuf);
1349 all_sz = 0;
1351 quoteflt_reset(qf, outbuf);
1352 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1353 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1354 break;
1355 all_sz += sz;
1357 if ((sz = quoteflt_flush(qf)) > 0)
1358 all_sz += sz;
1359 if (line)
1360 free(line);
1362 if (all_sz > 0 && outbuf == origobuf)
1363 _addstats(stats, 1, all_sz);
1364 fclose(pipebuf);
1365 NYD_LEAVE;
1368 static void
1369 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1370 off_t *stats)
1372 char statout[3], *cp = statout;
1373 NYD_ENTER;
1375 if (mp->m_flag & MREAD)
1376 *cp++ = 'R';
1377 if (!(mp->m_flag & MNEW))
1378 *cp++ = 'O';
1379 *cp = 0;
1380 if (statout[0]) {
1381 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1382 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1383 if (i > 0)
1384 _addstats(stats, 1, i);
1386 NYD_LEAVE;
1389 static void
1390 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1391 off_t *stats)
1393 char xstatout[4];
1394 char *xp = xstatout;
1395 NYD_ENTER;
1397 if (mp->m_flag & MFLAGGED)
1398 *xp++ = 'F';
1399 if (mp->m_flag & MANSWERED)
1400 *xp++ = 'A';
1401 if (mp->m_flag & MDRAFTED)
1402 *xp++ = 'T';
1403 *xp = 0;
1404 if (xstatout[0]) {
1405 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1406 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1407 if (i > 0)
1408 _addstats(stats, 1, i);
1410 NYD_LEAVE;
1413 static void
1414 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1416 char const *froma, *date, *nl;
1417 int i;
1418 NYD_ENTER;
1420 if (ip != NULL && ip->m_from != NULL) {
1421 froma = ip->m_from;
1422 date = fakedate(ip->m_time);
1423 nl = "\n";
1424 } else {
1425 froma = myname;
1426 date = time_current.tc_ctime;
1427 nl = "";
1430 colour_put(fp, COLOURSPEC_FROM_);
1431 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1432 colour_reset(fp);
1433 if (i > 0)
1434 _addstats(stats, (*nl != '\0'), i);
1435 NYD_LEAVE;
1438 FL int
1439 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1440 char const *prefix, enum sendaction action, off_t *stats)
1442 struct quoteflt qf;
1443 size_t cnt, sz, i;
1444 FILE *ibuf;
1445 enum parseflags pf;
1446 struct mimepart *ip;
1447 int rv = -1, c;
1448 NYD_ENTER;
1450 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
1451 did_print_dot = 1;
1452 if (stats != NULL)
1453 stats[0] = stats[1] = 0;
1454 quoteflt_init(&qf, prefix);
1456 /* First line is the From_ line, so no headers there to worry about */
1457 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1458 goto jleave;
1460 cnt = mp->m_size;
1461 sz = 0;
1463 struct str const *cpre, *csuf;
1464 #ifdef HAVE_COLOUR
1465 cpre = colour_get(COLOURSPEC_FROM_);
1466 csuf = colour_get(COLOURSPEC_RESET);
1467 #else
1468 cpre = csuf = NULL;
1469 #endif
1470 if (mp->m_flag & MNOFROM) {
1471 if (doign != allignore && doign != fwdignore && action != SEND_RFC822)
1472 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1473 (cpre != NULL ? cpre->s : ""),
1474 (int)qf.qf_pfix_len, (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
1475 fakefrom(mp), fakedate(mp->m_time),
1476 (csuf != NULL ? csuf->s : ""));
1477 } else {
1478 if (doign != allignore && doign != fwdignore && action != SEND_RFC822) {
1479 if (qf.qf_pfix_len > 0) {
1480 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1481 if (i != qf.qf_pfix_len)
1482 goto jleave;
1483 sz += i;
1485 #ifdef HAVE_COLOUR
1486 if (cpre != NULL) {
1487 fputs(cpre->s, obuf);
1488 cpre = (struct str const*)0x1;
1490 #endif
1493 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1494 if (doign != allignore && doign != fwdignore &&
1495 action != SEND_RFC822) {
1496 #ifdef HAVE_COLOUR
1497 if (c == '\n' && csuf != NULL) {
1498 cpre = (struct str const*)0x1;
1499 fputs(csuf->s, obuf);
1501 #endif
1502 putc(c, obuf);
1503 sz++;
1505 --cnt;
1506 if (c == '\n')
1507 break;
1510 #ifdef HAVE_COLOUR
1511 if (csuf != NULL && cpre != (struct str const*)0x1)
1512 fputs(csuf->s, obuf);
1513 #endif
1516 if (sz > 0)
1517 _addstats(stats, 1, sz);
1519 pf = 0;
1520 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1521 pf |= PARSE_DECRYPT | PARSE_PARTS;
1522 if ((ip = parsemsg(mp, pf)) == NULL)
1523 goto jleave;
1525 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1526 jleave:
1527 quoteflt_destroy(&qf);
1528 NYD_LEAVE;
1529 return rv;
1532 /* vim:set fenc=utf-8:s-it-mode */