Simplify ccred_lookup()..
[s-mailx.git] / send.c
blobbb710b0edf5c5bde50ab9b678db7a257d1026621
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 part */
81 static enum pipeflags _pipecmd(char **result, struct mimepart const *mpp);
83 /* Create a pipe; if mpp is not NULL, place some PIPEHOOK_* environment
84 * variables accordingly */
85 static FILE * _pipefile(char const *pipecomm, struct mimepart const *mpp,
86 FILE **qbuf, bool_t quote, bool_t async);
88 /* Adjust output statistics */
89 SINLINE void _addstats(off_t *stats, off_t lines, off_t bytes);
91 /* Call mime_write() as approbiate and adjust statistics */
92 SINLINE ssize_t _out(char const *buf, size_t len, FILE *fp,
93 enum conversion convert, enum sendaction action,
94 struct quoteflt *qf, off_t *stats,
95 struct str *rest);
97 /* SIGPIPE handler */
98 static void _send_onpipe(int signo);
100 /* Send one part */
101 static int sendpart(struct message *zmp, struct mimepart *ip,
102 FILE *obuf, struct ignoretab *doign,
103 struct quoteflt *qf, enum sendaction action,
104 off_t *stats, int level);
106 /* Get a file for an attachment */
107 static FILE * newfile(struct mimepart *ip, int *ispipe);
109 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
110 struct quoteflt *qf, off_t *stats);
112 /* Output a reasonable looking status field */
113 static void statusput(const struct message *mp, FILE *obuf,
114 struct quoteflt *qf, off_t *stats);
115 static void xstatusput(const struct message *mp, FILE *obuf,
116 struct quoteflt *qf, off_t *stats);
118 static void put_from_(FILE *fp, struct mimepart *ip, off_t *stats);
120 static struct mimepart *
121 parsemsg(struct message *mp, enum parseflags pf)
123 struct mimepart *ip;
124 NYD_ENTER;
126 ip = csalloc(1, sizeof *ip);
127 ip->m_flag = mp->m_flag;
128 ip->m_have = mp->m_have;
129 ip->m_block = mp->m_block;
130 ip->m_offset = mp->m_offset;
131 ip->m_size = mp->m_size;
132 ip->m_xsize = mp->m_xsize;
133 ip->m_lines = mp->m_lines;
134 ip->m_xlines = mp->m_lines;
135 if (parsepart(mp, ip, pf, 0) != OKAY)
136 ip = NULL;
137 NYD_LEAVE;
138 return ip;
141 static enum okay
142 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
143 int level)
145 char *cp_b, *cp;
146 enum okay rv = STOP;
147 NYD_ENTER;
149 ip->m_ct_type = hfield1("content-type", (struct message*)ip);
150 if (ip->m_ct_type != NULL) {
151 cp_b = ip->m_ct_type_plain = savestr(ip->m_ct_type);
152 if ((cp = strchr(cp_b, ';')) != NULL)
153 *cp = '\0';
154 cp = cp_b + strlen(cp_b);
155 while (cp > cp_b && blankchar(cp[-1]))
156 --cp;
157 *cp = '\0';
158 } else if (ip->m_parent != NULL &&
159 ip->m_parent->m_mimecontent == MIME_DIGEST)
160 ip->m_ct_type_plain = UNCONST("message/rfc822");
161 else
162 ip->m_ct_type_plain = UNCONST("text/plain");
163 ip->m_ct_type_usr_ovwr = NULL;
165 if (ip->m_ct_type != NULL)
166 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
167 if (ip->m_charset == NULL)
168 ip->m_charset = charset_get_7bit();
170 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
171 (struct message*)ip);
172 ip->m_mimeenc = (ip->m_ct_transfer_enc != NULL)
173 ? mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
175 if (((cp = hfield1("content-disposition", (struct message*)ip)) == NULL ||
176 (ip->m_filename = mime_getparam("filename", cp)) == NULL) &&
177 ip->m_ct_type != NULL)
178 ip->m_filename = mime_getparam("name", ip->m_ct_type);
180 ip->m_mimecontent = mime_classify_content_of_part(ip);
182 if (pf & PARSE_PARTS) {
183 if (level > 9999) { /* TODO MAGIC */
184 fprintf(stderr, _("MIME content too deeply nested\n"));
185 goto jleave;
187 switch (ip->m_mimecontent) {
188 case MIME_PKCS7:
189 if (pf & PARSE_DECRYPT) {
190 #ifdef HAVE_SSL
191 parsepkcs7(zmp, ip, pf, level);
192 break;
193 #else
194 fprintf(stderr, _("No SSL support compiled in.\n"));
195 goto jleave;
196 #endif
198 /* FALLTHRU */
199 default:
200 break;
201 case MIME_MULTI:
202 case MIME_ALTERNATIVE:
203 case MIME_DIGEST:
204 _parsemultipart(zmp, ip, pf, level);
205 break;
206 case MIME_822:
207 parse822(zmp, ip, pf, level);
208 break;
211 rv = OKAY;
212 jleave:
213 NYD_LEAVE;
214 return rv;
217 static void
218 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
219 int level)
221 int c, lastc = '\n';
222 size_t cnt;
223 FILE *ibuf;
224 off_t offs;
225 struct mimepart *np;
226 long lines;
227 NYD_ENTER;
229 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
230 goto jleave;
232 cnt = ip->m_size;
233 lines = ip->m_lines;
234 while (cnt && ((c = getc(ibuf)) != EOF)) {
235 --cnt;
236 if (c == '\n') {
237 --lines;
238 if (lastc == '\n')
239 break;
241 lastc = c;
243 offs = ftell(ibuf);
245 np = csalloc(1, sizeof *np);
246 np->m_flag = MNOFROM;
247 np->m_have = HAVE_HEADER | HAVE_BODY;
248 np->m_block = mailx_blockof(offs);
249 np->m_offset = mailx_offsetof(offs);
250 np->m_size = np->m_xsize = cnt;
251 np->m_lines = np->m_xlines = lines;
252 np->m_partstring = ip->m_partstring;
253 np->m_parent = ip;
254 ip->m_multipart = np;
256 if (ok_blook(rfc822_body_from_)) {
257 substdate((struct message*)np);
258 np->m_from = fakefrom((struct message*)np);/* TODO strip MNOFROM flag? */
261 parsepart(zmp, np, pf, level + 1);
262 jleave:
263 NYD_LEAVE;
266 #ifdef HAVE_SSL
267 static void
268 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
269 int level)
271 struct message m, *xmp;
272 struct mimepart *np;
273 char *to, *cc;
274 NYD_ENTER;
276 memcpy(&m, ip, sizeof m);
277 to = hfield1("to", zmp);
278 cc = hfield1("cc", zmp);
280 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
281 np = csalloc(1, sizeof *np);
282 np->m_flag = xmp->m_flag;
283 np->m_have = xmp->m_have;
284 np->m_block = xmp->m_block;
285 np->m_offset = xmp->m_offset;
286 np->m_size = xmp->m_size;
287 np->m_xsize = xmp->m_xsize;
288 np->m_lines = xmp->m_lines;
289 np->m_xlines = xmp->m_xlines;
290 np->m_partstring = ip->m_partstring;
292 if (parsepart(zmp, np, pf, level + 1) == OKAY) {
293 np->m_parent = ip;
294 ip->m_multipart = np;
297 NYD_LEAVE;
299 #endif
301 static void
302 _parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
303 int level)
305 /* TODO Instead of the recursive multiple run parse we have today,
306 * TODO the send/MIME layer rewrite must create a "tree" of parts with
307 * TODO a single-pass parse, then address each part directly as
308 * TODO necessary; since boundaries start with -- and the content
309 * TODO rather forms a stack this is pretty cheap indeed! */
310 struct mimepart *np = NULL;
311 char *boundary, *line = NULL;
312 size_t linesize = 0, linelen, cnt, boundlen;
313 FILE *ibuf;
314 off_t offs;
315 int part = 0;
316 long lines = 0;
317 NYD_ENTER;
319 if ((boundary = mime_get_boundary(ip->m_ct_type, &linelen)) == NULL)
320 goto jleave;
322 boundlen = linelen;
323 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL)
324 goto jleave;
326 cnt = ip->m_size;
327 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0))
328 if (line[0] == '\n')
329 break;
330 offs = ftell(ibuf);
332 __newpart(ip, &np, offs, NULL);
333 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
334 /* XXX linelen includes LF */
335 if (!((lines > 0 || part == 0) && linelen > boundlen &&
336 !strncmp(line, boundary, boundlen))) {
337 ++lines;
338 continue;
341 /* Subpart boundary? */
342 if (line[boundlen] == '\n') {
343 offs = ftell(ibuf);
344 if (part > 0) {
345 __endpart(&np, offs - boundlen - 2, lines);
346 __newpart(ip, &np, offs - boundlen - 2, NULL);
348 __endpart(&np, offs, 2);
349 __newpart(ip, &np, offs, &part);
350 lines = 0;
351 continue;
354 /* Final boundary? Be aware of cases where there is no separating
355 * newline in between boundaries, as has been seen in a message with
356 * "Content-Type: multipart/appledouble;" */
357 if (linelen < boundlen + 2)
358 continue;
359 linelen -= boundlen + 2;
360 if (line[boundlen] != '-' || line[boundlen + 1] != '-' ||
361 (linelen > 0 && line[boundlen + 2] != '\n'))
362 continue;
363 offs = ftell(ibuf);
364 if (part != 0) {
365 __endpart(&np, offs - boundlen - 4, lines);
366 __newpart(ip, &np, offs - boundlen - 4, NULL);
368 __endpart(&np, offs + cnt, 2);
369 break;
371 if (np) {
372 offs = ftell(ibuf);
373 __endpart(&np, offs, lines);
376 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
377 if (np->m_mimecontent != MIME_DISCARD)
378 parsepart(zmp, np, pf, level + 1);
379 free(line);
380 jleave:
381 NYD_LEAVE;
384 static void
385 __newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
387 struct mimepart *pp;
388 size_t sz;
389 NYD_ENTER;
391 *np = csalloc(1, sizeof **np);
392 (*np)->m_flag = MNOFROM;
393 (*np)->m_have = HAVE_HEADER | HAVE_BODY;
394 (*np)->m_block = mailx_blockof(offs);
395 (*np)->m_offset = mailx_offsetof(offs);
397 if (part) {
398 ++(*part);
399 sz = (ip->m_partstring != NULL) ? strlen(ip->m_partstring) : 0;
400 sz += 20;
401 (*np)->m_partstring = salloc(sz);
402 if (ip->m_partstring)
403 snprintf((*np)->m_partstring, sz, "%s.%u", ip->m_partstring, *part);
404 else
405 snprintf((*np)->m_partstring, sz, "%u", *part);
406 } else
407 (*np)->m_mimecontent = MIME_DISCARD;
408 (*np)->m_parent = ip;
410 if (ip->m_multipart) {
411 for (pp = ip->m_multipart; pp->m_nextpart != NULL; pp = pp->m_nextpart)
413 pp->m_nextpart = *np;
414 } else
415 ip->m_multipart = *np;
416 NYD_LEAVE;
419 static void
420 __endpart(struct mimepart **np, off_t xoffs, long lines)
422 off_t offs;
423 NYD_ENTER;
425 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
426 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
427 (*np)->m_lines = (*np)->m_xlines = lines;
428 *np = NULL;
429 NYD_LEAVE;
432 static void
433 _print_part_info(struct str *out, struct mimepart *mip,
434 struct ignoretab *doign, int level)
436 struct str ct = {NULL, 0}, cd = {NULL, 0};
437 char const *ps;
438 struct str const *cpre, *csuf;
439 NYD_ENTER;
441 /* Max. 24 */
442 if (is_ign("content-type", 12, doign)) {
443 size_t addon;
445 if ((out->s = mip->m_ct_type_usr_ovwr) != NULL)
446 addon = 2;
447 else {
448 addon = 0;
449 out->s = mip->m_ct_type_plain;
451 out->l = strlen(out->s);
453 ct.s = ac_alloc(out->l + 2 + addon +1);
454 ct.s[0] = ',';
455 ct.s[1] = ' ';
456 ct.l = 2;
457 if (addon) {
458 ct.s[ct.l++] = '+';
459 ct.s[ct.l++] = ' ';
462 if (is_prefix("application/", out->s)) {
463 memcpy(ct.s + ct.l, "appl../", 7);
464 ct.l += 7;
465 out->l -= 12;
466 out->s += 12;
467 out->l = MIN(out->l, 17 - addon);
468 } else
469 out->l = MIN(out->l, 24 - addon);
470 memcpy(ct.s + ct.l, out->s, out->l);
471 ct.l += out->l;
472 ct.s[ct.l] = '\0';
475 /* Max. 32 */
476 if (is_ign("content-disposition", 19, doign) && mip->m_filename != NULL) {
477 struct str ti, to;
479 ti.l = strlen(ti.s = mip->m_filename);
480 mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV | TD_DELCTRL);
482 cd.s = ac_alloc(2 + 32 +1); /* FIXME was 25.. UNI: USE VISUAL WIDTH!!! */
483 cd.s[0] = ',';
484 cd.s[1] = ' ';
485 cd.l = 2 + field_put_bidi_clip(cd.s + 2, 32 +1, to.s, to.l);
487 free(to.s);
490 /* Take care of "99.99", i.e., 5 */
491 if ((ps = mip->m_partstring) == NULL || ps[0] == '\0')
492 ps = "?";
494 #ifdef HAVE_COLOUR
495 cpre = colour_get(COLOURSPEC_PARTINFO);
496 csuf = colour_get(COLOURSPEC_RESET);
497 #else
498 cpre = csuf = NULL;
499 #endif
501 /* Assume maximum possible sizes for 64 bit integers here to avoid any
502 * buffer overflows just in case we have a bug somewhere and / or the
503 * snprintf() is our internal version that doesn't really provide hard
504 * buffer cuts TODO ensure upper bound on numbers, use 9999999 else */
505 #define __msg "%s%s[-- #%s : %lu/%lu%s%s --]%s\n"
506 out->l = sizeof(__msg) +
507 #ifdef HAVE_COLOUR
508 (cpre != NULL ? cpre->l + csuf->l : 0) +
509 #endif
510 strlen(ps) + 2*21 + ct.l + cd.l +1;
511 out->s = salloc(out->l);
512 out->l = snprintf(out->s, out->l, __msg,
513 (level || (ps[0] != '1' && ps[1] == '\0') ? "\n" : ""),
514 (cpre != NULL ? cpre->s : ""),
515 ps, (ul_it)mip->m_lines, (ul_it)mip->m_size,
516 (ct.s != NULL ? ct.s : ""),
517 (cd.s != NULL ? cd.s : ""),
518 (csuf != NULL ? csuf->s : ""));
519 out->s[out->l] = '\0';
520 #undef __msg
522 if (cd.s != NULL)
523 ac_free(cd.s);
524 if (ct.s != NULL)
525 ac_free(ct.s);
526 NYD_LEAVE;
529 static enum pipeflags
530 _pipecmd(char **result, struct mimepart const *mpp)
532 enum pipeflags ret;
533 char *cp;
534 NYD_ENTER;
536 /* Do we have any handler for this part? */
537 if ((cp = mimepart_get_handler(mpp)) == NULL) {
538 ret = PIPE_NULL;
539 *result = NULL;
541 /* User specified a command, inspect for special cases */
542 else if (cp[0] != '@') {
543 /* Normal command line */
544 ret = PIPE_COMM;
545 *result = cp;
546 } else if (*++cp == '\0') {
547 /* Treat as plain text */
548 ret = PIPE_TEXT;
549 } else if (!msglist_is_single) {
550 /* Viewing multiple messages in one go, don't block system */
551 ret = PIPE_MSG;
552 *result = UNCONST(_("[Directly address message only to display this]\n"));
553 } else {
554 /* Viewing a single message only */
555 /* TODO send/MIME layer rewrite: when we have a single-pass parser
556 * TODO then the parsing phase and the send phase will be separated;
557 * TODO that allows us to ask a user *before* we start the send, i.e.,
558 * TODO *before* a pager pipe is setup */
559 if (cp[0] == '&')
560 /* Asynchronous command, normal command line */
561 ret = PIPE_ASYNC, *result = ++cp;
562 else
563 ret = PIPE_COMM, *result = cp;
565 NYD_LEAVE;
566 return ret;
569 static FILE *
570 _pipefile(char const *pipecomm, struct mimepart const *mpp, FILE **qbuf,
571 bool_t quote, bool_t async)
573 struct str s;
574 char const *env_addon[8], *sh;
575 FILE *rbuf;
576 char *cp;
577 NYD_ENTER;
579 rbuf = *qbuf;
581 if (quote) {
582 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER,
583 0600)) == NULL) {
584 perror(_("tmpfile"));
585 *qbuf = rbuf;
587 async = FAL0;
590 /* NAIL_FILENAME */
591 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
592 cp = UNCONST("");
593 env_addon[0] = str_concat_csvl(&s, PIPEHOOK_FILENAME, "=", cp, NULL)->s;
595 /* NAIL_FILENAME_GENERATED */
596 s.s = getrandstring(8);
597 if (mpp == NULL)
598 cp = s.s;
599 else if (*cp == '\0') {
600 if ( (((cp = mpp->m_ct_type_usr_ovwr) == NULL || *cp == '\0') &&
601 ((cp = mpp->m_ct_type_plain) == NULL || *cp == '\0')) ||
602 ((sh = strrchr(cp, '/')) == NULL || *++sh == '\0'))
603 cp = s.s;
604 else {
605 (cp = s.s)[7] = '.';
606 cp = savecat(cp, sh);
609 env_addon[1] = str_concat_csvl(&s, PIPEHOOK_FILENAME_GENERATED, "=", cp,
610 NULL)->s;
612 /* NAIL_CONTENT{,_EVIDENCE} */
613 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
614 cp = UNCONST("");
615 env_addon[2] = str_concat_csvl(&s, PIPEHOOK_CONTENT, "=", cp, NULL)->s;
617 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
618 cp = mpp->m_ct_type_usr_ovwr;
619 env_addon[3] = str_concat_csvl(&s, PIPEHOOK_CONTENT_EVIDENCE, "=", cp,
620 NULL)->s;
622 env_addon[4] = NULL;
624 if ((sh = ok_vlook(SHELL)) == NULL)
625 sh = XSHELL;
626 if ((rbuf = Popen(pipecomm, "W", sh, env_addon, (async ? -1 : fileno(*qbuf)))
627 ) == NULL)
628 perror(pipecomm);
629 else {
630 fflush(*qbuf);
631 if (*qbuf != stdout)
632 fflush(stdout);
634 NYD_LEAVE;
635 return rbuf;
638 SINLINE void
639 _addstats(off_t *stats, off_t lines, off_t bytes)
641 NYD_ENTER;
642 if (stats != NULL) {
643 if (stats[0] >= 0)
644 stats[0] += lines;
645 stats[1] += bytes;
647 NYD_LEAVE;
650 SINLINE ssize_t
651 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
652 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
654 ssize_t sz = 0, n;
655 int flags;
656 char const *cp;
657 NYD_ENTER;
659 #if 0
660 Well ... it turns out to not work like that since of course a valid
661 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
662 after an empty line has been seen, which cannot be detected that easily
663 right here!
664 ifdef HAVE_DEBUG /* TODO assert legacy */
665 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
666 * TODO other input situations handle RFC 4155 OR, if newly generated,
667 * TODO enforce quoted-printable if there is From_, as "required" by
668 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
669 * TODO if it may happen in this path, we should just treat decryption
670 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
671 if (action == SEND_MBOX || action == SEND_DECRYPT)
672 assert(!is_head(buf, len));
673 #else
674 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
675 is_head(buf, len)) {
676 putc('>', fp);
677 ++sz;
679 #endif
681 flags = ((int)action & _TD_EOF);
682 action &= ~_TD_EOF;
683 n = mime_write(buf, len, fp,
684 action == SEND_MBOX ? CONV_NONE : convert,
685 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
686 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
687 ? TD_ISPR | TD_ICONV
688 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
689 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
690 qf, rest);
691 if (n < 0)
692 sz = n;
693 else if (n > 0) {
694 sz = (ssize_t)((size_t)sz + n);
695 n = 0;
696 if (stats != NULL && stats[0] != -1)
697 for (cp = buf; PTRCMP(cp, <, buf + sz); ++cp)
698 if (*cp == '\n')
699 ++n;
700 _addstats(stats, n, sz);
702 NYD_LEAVE;
703 return sz;
706 static void
707 _send_onpipe(int signo)
709 NYD_X; /* Signal handler */
710 UNUSED(signo);
711 siglongjmp(_send_pipejmp, 1);
714 static int
715 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
716 struct ignoretab *doign, struct quoteflt *qf,
717 enum sendaction volatile action, off_t *volatile stats, int level)
719 int volatile ispipe, rv = 0;
720 struct str rest;
721 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
722 size_t linesize = 0, linelen, cnt;
723 int dostat, infld = 0, ignoring = 1, isenc, c;
724 struct mimepart *volatile np;
725 FILE * volatile ibuf = NULL, * volatile pbuf = obuf, * volatile qbuf = obuf,
726 *origobuf = obuf;
727 enum conversion volatile convert;
728 sighandler_type volatile oldpipe = SIG_DFL;
729 long lineno = 0;
730 NYD_ENTER;
732 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
733 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
734 goto jskip;
736 dostat = 0;
737 if (level == 0) {
738 if (doign != NULL) {
739 if (!is_ign("status", 6, doign))
740 dostat |= 1;
741 if (!is_ign("x-status", 8, doign))
742 dostat |= 2;
743 } else
744 dostat = 3;
746 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
747 rv = -1;
748 goto jleave;
750 cnt = ip->m_size;
752 if (ip->m_mimecontent == MIME_DISCARD)
753 goto jskip;
755 if (!(ip->m_flag & MNOFROM))
756 while (cnt && (c = getc(ibuf)) != EOF) {
757 cnt--;
758 if (c == '\n')
759 break;
761 isenc = 0;
762 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
763 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
764 action == SEND_TOSRCH)
765 ? CONV_FROMHDR : CONV_NONE;
767 /* Work the headers */
768 quoteflt_reset(qf, obuf);
769 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
770 ++lineno;
771 if (line[0] == '\n') {
772 /* If line is blank, we've reached end of headers, so force out
773 * status: field and note that we are no longer in header fields */
774 if (dostat & 1)
775 statusput(zmp, obuf, qf, stats);
776 if (dostat & 2)
777 xstatusput(zmp, obuf, qf, stats);
778 if (doign != allignore)
779 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
780 break;
783 isenc &= ~1;
784 if (infld && blankchar(line[0])) {
785 /* If this line is a continuation (SP / HT) of a previous header
786 * field, determine if the start of the line is a MIME encoded word */
787 if (isenc & 2) {
788 for (cp = line; blankchar(*cp); ++cp);
789 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
790 cp[0] == '=' && cp[1] == '?')
791 isenc |= 1;
793 } else {
794 /* Pick up the header field if we have one */
795 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
797 cp2 = cp;
798 while (spacechar(*cp))
799 ++cp;
800 if (cp[0] != ':' && level == 0 && lineno == 1) {
801 /* Not a header line, force out status: This happens in uucp style
802 * mail where there are no headers at all */
803 if (dostat & 1)
804 statusput(zmp, obuf, qf, stats);
805 if (dostat & 2)
806 xstatusput(zmp, obuf, qf, stats);
807 if (doign != allignore)
808 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
809 break;
812 /* If it is an ignored field and we care about such things, skip it.
813 * Misuse dostat also for another bit xxx use a bitenum + for more */
814 if (ok_blook(keep_content_length))
815 dostat |= 1 << 2;
816 c = *cp2;
817 *cp2 = 0; /* temporarily null terminate */
818 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
819 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
820 (!asccasecmp(line, "content-length") ||
821 !asccasecmp(line, "lines"))))
822 ignoring = 1;
823 else if (!asccasecmp(line, "status")) {
824 /* If field is "status," go compute and print real Status: field */
825 if (dostat & 1) {
826 statusput(zmp, obuf, qf, stats);
827 dostat &= ~1;
828 ignoring = 1;
830 } else if (!asccasecmp(line, "x-status")) {
831 /* If field is "status," go compute and print real Status: field */
832 if (dostat & 2) {
833 xstatusput(zmp, obuf, qf, stats);
834 dostat &= ~2;
835 ignoring = 1;
837 } else {
838 ignoring = 0;
839 #ifdef HAVE_COLOUR
840 pipecomm = savestrbuf(line, PTR2SIZE(cp2 - line));
841 #endif
843 *cp2 = c;
844 dostat &= ~(1 << 2);
845 infld = 1;
848 /* Determine if the end of the line is a MIME encoded word */
849 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
850 * TODO with header follow lines, and it should be up to the backend
851 * TODO what happens and what not, i.e., it doesn't matter wether it's
852 * TODO a MIME-encoded word or not, as long as a single separating space
853 * TODO remains in between lines (the MIME stuff will correctly remove
854 * TODO whitespace in between multiple adjacent encoded words) */
855 isenc &= ~2;
856 if (cnt && (c = getc(ibuf)) != EOF) {
857 if (blankchar(c)) {
858 cp = line + linelen - 1;
859 if (linelen > 0 && *cp == '\n')
860 --cp;
861 while (cp >= line && whitechar(*cp))
862 --cp;
863 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
864 isenc |= 2;
866 ungetc(c, ibuf);
869 if (!ignoring) {
870 size_t len = linelen;
871 start = line;
872 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
873 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
874 action == SEND_TOSRCH) {
875 /* Strip blank characters if two MIME-encoded words follow on
876 * continuing lines */
877 if (isenc & 1)
878 while (len > 0 && blankchar(*start)) {
879 ++start;
880 --len;
882 if (isenc & 2)
883 if (len > 0 && start[len - 1] == '\n')
884 --len;
885 while (len > 0 && blankchar(start[len - 1]))
886 --len;
888 #ifdef HAVE_COLOUR
890 bool_t colour_stripped = FAL0;
891 if (pipecomm != NULL) {
892 colour_put_header(obuf, pipecomm);
893 if (len > 0 && start[len - 1] == '\n') {
894 colour_stripped = TRU1;
895 --len;
898 #endif
899 _out(start, len, obuf, convert, action, qf, stats, NULL);
900 #ifdef HAVE_COLOUR
901 if (pipecomm != NULL) {
902 colour_reset(obuf); /* XXX reset after \n!! */
903 if (colour_stripped)
904 fputc('\n', obuf);
907 #endif
908 if (ferror(obuf)) {
909 free(line);
910 rv = -1;
911 goto jleave;
915 quoteflt_flush(qf);
916 free(line);
917 line = NULL;
919 jskip:
920 switch (ip->m_mimecontent) {
921 case MIME_822:
922 switch (action) {
923 case SEND_TODISP:
924 case SEND_TODISP_ALL:
925 case SEND_QUOTE:
926 case SEND_QUOTE_ALL:
927 if (ok_blook(rfc822_body_from_)) {
928 if (qf->qf_pfix_len > 0) {
929 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
930 qf->qf_pfix_len, obuf);
931 if (i == qf->qf_pfix_len)
932 _addstats(stats, 0, i);
934 put_from_(obuf, ip->m_multipart, stats);
936 /* FALLTHRU */
937 case SEND_TOSRCH:
938 case SEND_DECRYPT:
939 goto jmulti;
940 case SEND_TOFILE:
941 case SEND_TOPIPE:
942 if (ok_blook(rfc822_body_from_))
943 put_from_(obuf, ip->m_multipart, stats);
944 /* FALLTHRU */
945 case SEND_MBOX:
946 case SEND_RFC822:
947 case SEND_SHOW:
948 break;
950 break;
951 case MIME_TEXT_HTML:
952 case MIME_TEXT:
953 case MIME_TEXT_PLAIN:
954 switch (action) {
955 case SEND_TODISP:
956 case SEND_TODISP_ALL:
957 case SEND_QUOTE:
958 case SEND_QUOTE_ALL:
959 ispipe = TRU1;
960 switch (_pipecmd(&pipecomm, ip)) {
961 case PIPE_MSG:
962 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
963 stats, NULL);
964 /* We would print this as plain text, so better force going home */
965 goto jleave;
966 case PIPE_TEXT:
967 case PIPE_COMM:
968 case PIPE_NULL:
969 break;
970 case PIPE_ASYNC:
971 ispipe = FAL0;
972 break;
974 /* FALLTRHU */
975 default:
976 break;
978 break;
979 case MIME_DISCARD:
980 if (action != SEND_DECRYPT)
981 goto jleave;
982 break;
983 case MIME_PKCS7:
984 if (action != SEND_MBOX && action != SEND_RFC822 &&
985 action != SEND_SHOW && ip->m_multipart != NULL)
986 goto jmulti;
987 /* FALLTHRU */
988 default:
989 switch (action) {
990 case SEND_TODISP:
991 case SEND_TODISP_ALL:
992 case SEND_QUOTE:
993 case SEND_QUOTE_ALL:
994 ispipe = TRU1;
995 switch (_pipecmd(&pipecomm, ip)) {
996 case PIPE_MSG:
997 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
998 stats, NULL);
999 pipecomm = NULL;
1000 break;
1001 case PIPE_ASYNC:
1002 ispipe = FAL0;
1003 /* FALLTHRU */
1004 case PIPE_COMM:
1005 case PIPE_NULL:
1006 break;
1007 case PIPE_TEXT:
1008 goto jcopyout; /* break; break; */
1010 if (pipecomm != NULL)
1011 break;
1012 if (level == 0 && cnt) {
1013 char const *x = _("[Binary content]\n");
1014 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1016 goto jleave;
1017 case SEND_TOFILE:
1018 case SEND_TOPIPE:
1019 case SEND_TOSRCH:
1020 case SEND_DECRYPT:
1021 case SEND_MBOX:
1022 case SEND_RFC822:
1023 case SEND_SHOW:
1024 break;
1026 break;
1027 case MIME_ALTERNATIVE:
1028 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
1029 !ok_blook(print_alternatives)) {
1030 bool_t doact = FAL0;
1032 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
1033 if (np->m_mimecontent == MIME_TEXT_PLAIN)
1034 doact = TRU1;
1035 if (doact) {
1036 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1037 if (np->m_ct_type_plain != NULL && action != SEND_QUOTE) {
1038 _print_part_info(&rest, np, doign, level);
1039 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1040 NULL);
1042 if (doact && np->m_mimecontent == MIME_TEXT_PLAIN) {
1043 doact = FAL0;
1044 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
1045 level + 1);
1046 quoteflt_reset(qf, origobuf);
1047 if (rv < 0)
1048 break;
1051 goto jleave;
1054 /* FALLTHRU */
1055 case MIME_MULTI:
1056 case MIME_DIGEST:
1057 switch (action) {
1058 case SEND_TODISP:
1059 case SEND_TODISP_ALL:
1060 case SEND_QUOTE:
1061 case SEND_QUOTE_ALL:
1062 case SEND_TOFILE:
1063 case SEND_TOPIPE:
1064 case SEND_TOSRCH:
1065 case SEND_DECRYPT:
1066 jmulti:
1067 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
1068 ip->m_multipart != NULL &&
1069 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
1070 ip->m_multipart->m_nextpart == NULL) {
1071 char const *x = _("[Missing multipart boundary - use \"show\" "
1072 "to display the raw message]\n");
1073 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1076 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1077 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
1078 continue;
1079 ispipe = FAL0;
1080 switch (action) {
1081 case SEND_TOFILE:
1082 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
1083 break;
1084 stats = NULL;
1085 if ((obuf = newfile(np, UNVOLATILE(&ispipe))) == NULL)
1086 continue;
1087 if (!ispipe)
1088 break;
1089 if (sigsetjmp(_send_pipejmp, 1)) {
1090 rv = -1;
1091 goto jpipe_close;
1093 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1094 break;
1095 case SEND_TODISP:
1096 case SEND_TODISP_ALL:
1097 case SEND_QUOTE_ALL:
1098 if (ip->m_mimecontent != MIME_MULTI &&
1099 ip->m_mimecontent != MIME_ALTERNATIVE &&
1100 ip->m_mimecontent != MIME_DIGEST)
1101 break;
1102 _print_part_info(&rest, np, doign, level);
1103 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1104 NULL);
1105 break;
1106 case SEND_MBOX:
1107 case SEND_RFC822:
1108 case SEND_SHOW:
1109 case SEND_TOSRCH:
1110 case SEND_QUOTE:
1111 case SEND_DECRYPT:
1112 case SEND_TOPIPE:
1113 break;
1116 quoteflt_flush(qf);
1117 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
1118 rv = -1;
1119 quoteflt_reset(qf, origobuf);
1120 if (action == SEND_QUOTE)
1121 break;
1122 if (action == SEND_TOFILE && obuf != origobuf) {
1123 if (!ispipe)
1124 Fclose(obuf);
1125 else {
1126 jpipe_close:
1127 safe_signal(SIGPIPE, SIG_IGN);
1128 Pclose(obuf, TRU1);
1129 safe_signal(SIGPIPE, oldpipe);
1133 goto jleave;
1134 case SEND_MBOX:
1135 case SEND_RFC822:
1136 case SEND_SHOW:
1137 break;
1141 /* Copy out message body */
1142 jcopyout:
1143 if (doign == allignore && level == 0) /* skip final blank line */
1144 --cnt;
1145 switch (ip->m_mimeenc) {
1146 case MIME_BIN:
1147 if (stats != NULL)
1148 stats[0] = -1;
1149 /* FALLTHRU */
1150 case MIME_7B:
1151 case MIME_8B:
1152 convert = CONV_NONE;
1153 break;
1154 case MIME_QP:
1155 convert = CONV_FROMQP;
1156 break;
1157 case MIME_B64:
1158 switch (ip->m_mimecontent) {
1159 case MIME_TEXT:
1160 case MIME_TEXT_PLAIN:
1161 case MIME_TEXT_HTML:
1162 convert = CONV_FROMB64_T;
1163 break;
1164 default:
1165 convert = CONV_FROMB64;
1167 break;
1168 default:
1169 convert = CONV_NONE;
1172 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1173 action == SEND_RFC822 || action == SEND_SHOW)
1174 convert = CONV_NONE;
1175 #ifdef HAVE_ICONV
1176 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1177 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1178 action == SEND_TOSRCH) &&
1179 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1180 ip->m_mimecontent == MIME_TEXT_HTML ||
1181 ip->m_mimecontent == MIME_TEXT)) {
1182 char const *tcs = charset_get_lc();
1184 if (iconvd != (iconv_t)-1)
1185 n_iconv_close(iconvd);
1186 /* TODO Since Base64 has an odd 4:3 relation in between input
1187 * TODO and output an input line may end with a partial
1188 * TODO multibyte character; this is no problem at all unless
1189 * TODO we send to the display or whatever, i.e., ensure
1190 * TODO makeprint() or something; to avoid this trap, *force*
1191 * TODO iconv(), in which case this layer will handle leftovers
1192 * TODO correctly */
1193 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
1194 asccasecmp(charset_get_7bit(), ip->m_charset))) {
1195 iconvd = n_iconv_open(tcs, ip->m_charset);
1196 /* XXX Don't bail out if we cannot iconv(3) here;
1197 * XXX alternatively we could avoid trying to open
1198 * XXX if ip->m_charset is "unknown-8bit", which was
1199 * XXX the one that has bitten me?? */
1201 * TODO errors should DEFINETELY not be scrolled away!
1202 * TODO what about an error buffer (think old shsp(1)),
1203 * TODO re-dump errors since last snapshot when the
1204 * TODO command loop enters again? i.e., at least print
1205 * TODO "There were errors ?" before the next prompt,
1206 * TODO so that the user can look at the error buffer?
1208 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1209 fprintf(stderr, _("Cannot convert from %s to %s\n"),
1210 ip->m_charset, tcs);
1211 /*rv = 1; goto jleave;*/
1215 #endif
1217 if (pipecomm != NULL && (action == SEND_TODISP ||
1218 action == SEND_TODISP_ALL || action == SEND_QUOTE ||
1219 action == SEND_QUOTE_ALL)) {
1220 qbuf = obuf;
1221 pbuf = _pipefile(pipecomm, ip, UNVOLATILE(&qbuf),
1222 (action == SEND_QUOTE || action == SEND_QUOTE_ALL), !ispipe);
1223 action = SEND_TOPIPE;
1224 if (pbuf != qbuf) {
1225 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1226 if (sigsetjmp(_send_pipejmp, 1))
1227 goto jend;
1229 } else
1230 pbuf = qbuf = obuf;
1233 bool_t eof;
1234 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1235 off_t *save_stats = stats;
1237 if (pbuf != origobuf) {
1238 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1239 stats = NULL;
1241 eof = FAL0;
1242 rest.s = NULL;
1243 rest.l = 0;
1245 quoteflt_reset(qf, pbuf);
1246 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1247 joutln:
1248 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1249 ferror(pbuf)) {
1250 rv = -1; /* XXX Should bail away?! */
1251 break;
1254 if (!eof && rest.l != 0) {
1255 linelen = 0;
1256 eof = TRU1;
1257 action |= _TD_EOF;
1258 goto joutln;
1260 quoteflt_flush(qf);
1261 if (rest.s != NULL)
1262 free(rest.s);
1264 if (pbuf != origobuf) {
1265 qf->qf_pfix_len = save_qf_pfix_len;
1266 stats = save_stats;
1270 jend:
1271 free(line);
1272 if (pbuf != qbuf) {
1273 safe_signal(SIGPIPE, SIG_IGN);
1274 Pclose(pbuf, ispipe);
1275 safe_signal(SIGPIPE, oldpipe);
1276 if (qbuf != obuf)
1277 pipecpy(qbuf, obuf, origobuf, qf, stats);
1279 #ifdef HAVE_ICONV
1280 if (iconvd != (iconv_t)-1)
1281 n_iconv_close(iconvd);
1282 #endif
1283 jleave:
1284 NYD_LEAVE;
1285 return rv;
1288 static FILE *
1289 newfile(struct mimepart *ip, int *ispipe)
1291 struct str in, out;
1292 char *f;
1293 FILE *fp;
1294 NYD_ENTER;
1296 f = ip->m_filename;
1297 *ispipe = 0;
1299 if (f != NULL && f != (char*)-1) {
1300 in.s = f;
1301 in.l = strlen(f);
1302 mime_fromhdr(&in, &out, TD_ISPR);
1303 memcpy(f, out.s, out.l);
1304 *(f + out.l) = '\0';
1305 free(out.s);
1308 if (options & OPT_INTERACTIVE) {
1309 char *f2, *f3;
1310 jgetname:
1311 printf(_("Enter filename for part %s (%s)"),
1312 (ip->m_partstring != NULL) ? ip->m_partstring : "?",
1313 ip->m_ct_type_plain);
1314 f2 = readstr_input(": ", (f != (char*)-1) ? f : NULL);
1315 if (f2 == NULL || *f2 == '\0') {
1316 fprintf(stderr, _("... skipping this\n"));
1317 fp = NULL;
1318 goto jleave;
1319 } else if (*f2 == '|')
1320 /* Pipes are expanded by the shell */
1321 f = f2;
1322 else if ((f3 = file_expand(f2)) == NULL)
1323 /* (Error message written by file_expand()) */
1324 goto jgetname;
1325 else
1326 f = f3;
1328 if (f == NULL || f == (char*)-1) {
1329 fp = NULL;
1330 goto jleave;
1333 if (*f == '|') {
1334 char const *cp;
1335 cp = ok_vlook(SHELL);
1336 if (cp == NULL)
1337 cp = XSHELL;
1338 fp = Popen(f + 1, "w", cp, NULL, 1);
1339 if (!(*ispipe = (fp != NULL)))
1340 perror(f);
1341 } else {
1342 if ((fp = Fopen(f, "w")) == NULL)
1343 fprintf(stderr, _("Cannot open `%s'\n"), f);
1345 jleave:
1346 NYD_LEAVE;
1347 return fp;
1350 static void
1351 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1352 off_t *stats)
1354 char *line = NULL; /* TODO line pool */
1355 size_t linesize = 0, linelen, cnt;
1356 ssize_t all_sz, sz;
1357 NYD_ENTER;
1359 fflush(pipebuf);
1360 rewind(pipebuf);
1361 cnt = fsize(pipebuf);
1362 all_sz = 0;
1364 quoteflt_reset(qf, outbuf);
1365 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1366 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1367 break;
1368 all_sz += sz;
1370 if ((sz = quoteflt_flush(qf)) > 0)
1371 all_sz += sz;
1372 if (line)
1373 free(line);
1375 if (all_sz > 0 && outbuf == origobuf)
1376 _addstats(stats, 1, all_sz);
1377 fclose(pipebuf);
1378 NYD_LEAVE;
1381 static void
1382 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1383 off_t *stats)
1385 char statout[3], *cp = statout;
1386 NYD_ENTER;
1388 if (mp->m_flag & MREAD)
1389 *cp++ = 'R';
1390 if (!(mp->m_flag & MNEW))
1391 *cp++ = 'O';
1392 *cp = 0;
1393 if (statout[0]) {
1394 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1395 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1396 if (i > 0)
1397 _addstats(stats, 1, i);
1399 NYD_LEAVE;
1402 static void
1403 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1404 off_t *stats)
1406 char xstatout[4];
1407 char *xp = xstatout;
1408 NYD_ENTER;
1410 if (mp->m_flag & MFLAGGED)
1411 *xp++ = 'F';
1412 if (mp->m_flag & MANSWERED)
1413 *xp++ = 'A';
1414 if (mp->m_flag & MDRAFTED)
1415 *xp++ = 'T';
1416 *xp = 0;
1417 if (xstatout[0]) {
1418 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1419 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1420 if (i > 0)
1421 _addstats(stats, 1, i);
1423 NYD_LEAVE;
1426 static void
1427 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1429 char const *froma, *date, *nl;
1430 int i;
1431 NYD_ENTER;
1433 if (ip != NULL && ip->m_from != NULL) {
1434 froma = ip->m_from;
1435 date = fakedate(ip->m_time);
1436 nl = "\n";
1437 } else {
1438 froma = myname;
1439 date = time_current.tc_ctime;
1440 nl = "";
1443 colour_put(fp, COLOURSPEC_FROM_);
1444 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1445 colour_reset(fp);
1446 if (i > 0)
1447 _addstats(stats, (*nl != '\0'), i);
1448 NYD_LEAVE;
1451 FL int
1452 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1453 char const *prefix, enum sendaction action, off_t *stats)
1455 struct quoteflt qf;
1456 size_t cnt, sz, i;
1457 FILE *ibuf;
1458 enum parseflags pf;
1459 struct mimepart *ip;
1460 int rv = -1, c;
1461 NYD_ENTER;
1463 if (mp == dot && action != SEND_TOSRCH)
1464 did_print_dot = 1;
1465 if (stats != NULL)
1466 stats[0] = stats[1] = 0;
1467 quoteflt_init(&qf, prefix);
1469 /* First line is the From_ line, so no headers there to worry about */
1470 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1471 goto jleave;
1473 cnt = mp->m_size;
1474 sz = 0;
1476 struct str const *cpre, *csuf;
1477 #ifdef HAVE_COLOUR
1478 cpre = colour_get(COLOURSPEC_FROM_);
1479 csuf = colour_get(COLOURSPEC_RESET);
1480 #else
1481 cpre = csuf = NULL;
1482 #endif
1483 if (mp->m_flag & MNOFROM) {
1484 if (doign != allignore && doign != fwdignore && action != SEND_RFC822)
1485 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1486 (cpre != NULL ? cpre->s : ""),
1487 (int)qf.qf_pfix_len, (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
1488 fakefrom(mp), fakedate(mp->m_time),
1489 (csuf != NULL ? csuf->s : ""));
1490 } else {
1491 if (doign != allignore && doign != fwdignore && action != SEND_RFC822) {
1492 if (qf.qf_pfix_len > 0) {
1493 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1494 if (i != qf.qf_pfix_len)
1495 goto jleave;
1496 sz += i;
1498 #ifdef HAVE_COLOUR
1499 if (cpre != NULL) {
1500 fputs(cpre->s, obuf);
1501 cpre = (struct str const*)0x1;
1503 #endif
1506 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1507 if (doign != allignore && doign != fwdignore &&
1508 action != SEND_RFC822) {
1509 #ifdef HAVE_COLOUR
1510 if (c == '\n' && csuf != NULL) {
1511 cpre = (struct str const*)0x1;
1512 fputs(csuf->s, obuf);
1514 #endif
1515 putc(c, obuf);
1516 sz++;
1518 --cnt;
1519 if (c == '\n')
1520 break;
1523 #ifdef HAVE_COLOUR
1524 if (csuf != NULL && cpre != (struct str const*)0x1)
1525 fputs(csuf->s, obuf);
1526 #endif
1529 if (sz > 0)
1530 _addstats(stats, 1, sz);
1532 pf = 0;
1533 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1534 pf |= PARSE_DECRYPT | PARSE_PARTS;
1535 if ((ip = parsemsg(mp, pf)) == NULL)
1536 goto jleave;
1538 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1539 jleave:
1540 quoteflt_destroy(&qf);
1541 NYD_LEAVE;
1542 return rv;
1545 /* s-it-mode */