c_help(): puts(3) appends NL, do not add another one
[s-mailx.git] / send.c
bloba3c51e087fc36f40bc4f0bcb54b06ed4c69654d9
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 *result = NULL;
538 /* Do we have any handler for this part? */
539 if ((cp = mimepart_get_handler(mpp)) == NULL)
540 ret = PIPE_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;
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(_("[Directly address message only to display this]\n"));
554 } else {
555 /* Viewing a single message only */
556 /* TODO send/MIME layer rewrite: when we have a single-pass parser
557 * TODO then the parsing phase and the send phase will be separated;
558 * TODO that allows us to ask a user *before* we start the send, i.e.,
559 * TODO *before* a pager pipe is setup */
560 if (cp[0] == '&')
561 /* Asynchronous command, normal command line */
562 ret = PIPE_ASYNC, *result = ++cp;
563 else
564 ret = PIPE_COMM, *result = cp;
566 NYD_LEAVE;
567 return ret;
570 static FILE *
571 _pipefile(char const *pipecomm, struct mimepart const *mpp, FILE **qbuf,
572 bool_t quote, bool_t async)
574 struct str s;
575 char const *env_addon[8], *sh;
576 FILE *rbuf;
577 char *cp;
578 NYD_ENTER;
580 rbuf = *qbuf;
582 if (quote) {
583 if ((*qbuf = Ftmp(NULL, "sendp", OF_RDWR | OF_UNLINK | OF_REGISTER,
584 0600)) == NULL) {
585 perror(_("tmpfile"));
586 *qbuf = rbuf;
588 async = FAL0;
591 /* NAIL_FILENAME */
592 if (mpp == NULL || (cp = mpp->m_filename) == NULL)
593 cp = UNCONST("");
594 env_addon[0] = str_concat_csvl(&s, PIPEHOOK_FILENAME, "=", cp, NULL)->s;
596 /* NAIL_FILENAME_GENERATED */
597 s.s = getrandstring(8);
598 if (mpp == NULL)
599 cp = s.s;
600 else if (*cp == '\0') {
601 if ( (((cp = mpp->m_ct_type_usr_ovwr) == NULL || *cp == '\0') &&
602 ((cp = mpp->m_ct_type_plain) == NULL || *cp == '\0')) ||
603 ((sh = strrchr(cp, '/')) == NULL || *++sh == '\0'))
604 cp = s.s;
605 else {
606 (cp = s.s)[7] = '.';
607 cp = savecat(cp, sh);
610 env_addon[1] = str_concat_csvl(&s, PIPEHOOK_FILENAME_GENERATED, "=", cp,
611 NULL)->s;
613 /* NAIL_CONTENT{,_EVIDENCE} */
614 if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
615 cp = UNCONST("");
616 env_addon[2] = str_concat_csvl(&s, PIPEHOOK_CONTENT, "=", cp, NULL)->s;
618 if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
619 cp = mpp->m_ct_type_usr_ovwr;
620 env_addon[3] = str_concat_csvl(&s, PIPEHOOK_CONTENT_EVIDENCE, "=", cp,
621 NULL)->s;
623 env_addon[4] = NULL;
625 if ((sh = ok_vlook(SHELL)) == NULL)
626 sh = XSHELL;
627 if ((rbuf = Popen(pipecomm, "W", sh, env_addon, (async ? -1 : fileno(*qbuf)))
628 ) == NULL)
629 perror(pipecomm);
630 else {
631 fflush(*qbuf);
632 if (*qbuf != stdout)
633 fflush(stdout);
635 NYD_LEAVE;
636 return rbuf;
639 SINLINE void
640 _addstats(off_t *stats, off_t lines, off_t bytes)
642 NYD_ENTER;
643 if (stats != NULL) {
644 if (stats[0] >= 0)
645 stats[0] += lines;
646 stats[1] += bytes;
648 NYD_LEAVE;
651 SINLINE ssize_t
652 _out(char const *buf, size_t len, FILE *fp, enum conversion convert, enum
653 sendaction action, struct quoteflt *qf, off_t *stats, struct str *rest)
655 ssize_t sz = 0, n;
656 int flags;
657 char const *cp;
658 NYD_ENTER;
660 #if 0
661 Well ... it turns out to not work like that since of course a valid
662 RFC 4155 compliant parser, like S-nail, takes care for From_ lines only
663 after an empty line has been seen, which cannot be detected that easily
664 right here!
665 ifdef HAVE_DEBUG /* TODO assert legacy */
666 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
667 * TODO other input situations handle RFC 4155 OR, if newly generated,
668 * TODO enforce quoted-printable if there is From_, as "required" by
669 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
670 * TODO if it may happen in this path, we should just treat decryption
671 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
672 if (action == SEND_MBOX || action == SEND_DECRYPT)
673 assert(!is_head(buf, len));
674 #else
675 if ((/*action == SEND_MBOX ||*/ action == SEND_DECRYPT) &&
676 is_head(buf, len)) {
677 putc('>', fp);
678 ++sz;
680 #endif
682 flags = ((int)action & _TD_EOF);
683 action &= ~_TD_EOF;
684 n = mime_write(buf, len, fp,
685 action == SEND_MBOX ? CONV_NONE : convert,
686 flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
687 action == SEND_QUOTE || action == SEND_QUOTE_ALL)
688 ? TD_ISPR | TD_ICONV
689 : (action == SEND_TOSRCH || action == SEND_TOPIPE)
690 ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
691 qf, rest);
692 if (n < 0)
693 sz = n;
694 else if (n > 0) {
695 sz = (ssize_t)((size_t)sz + n);
696 n = 0;
697 if (stats != NULL && stats[0] != -1)
698 for (cp = buf; PTRCMP(cp, <, buf + sz); ++cp)
699 if (*cp == '\n')
700 ++n;
701 _addstats(stats, n, sz);
703 NYD_LEAVE;
704 return sz;
707 static void
708 _send_onpipe(int signo)
710 NYD_X; /* Signal handler */
711 UNUSED(signo);
712 siglongjmp(_send_pipejmp, 1);
715 static int
716 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
717 struct ignoretab *doign, struct quoteflt *qf,
718 enum sendaction volatile action, off_t *volatile stats, int level)
720 int volatile ispipe, rv = 0;
721 struct str rest;
722 char *line = NULL, *cp, *cp2, *start, *pipecomm = NULL;
723 size_t linesize = 0, linelen, cnt;
724 int dostat, infld = 0, ignoring = 1, isenc, c;
725 struct mimepart *volatile np;
726 FILE * volatile ibuf = NULL, * volatile pbuf = obuf, * volatile qbuf = obuf,
727 *origobuf = obuf;
728 enum conversion volatile convert;
729 sighandler_type volatile oldpipe = SIG_DFL;
730 long lineno = 0;
731 NYD_ENTER;
733 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
734 action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
735 goto jskip;
737 dostat = 0;
738 if (level == 0) {
739 if (doign != NULL) {
740 if (!is_ign("status", 6, doign))
741 dostat |= 1;
742 if (!is_ign("x-status", 8, doign))
743 dostat |= 2;
744 } else
745 dostat = 3;
747 if ((ibuf = setinput(&mb, (struct message*)ip, NEED_BODY)) == NULL) {
748 rv = -1;
749 goto jleave;
751 cnt = ip->m_size;
753 if (ip->m_mimecontent == MIME_DISCARD)
754 goto jskip;
756 if (!(ip->m_flag & MNOFROM))
757 while (cnt && (c = getc(ibuf)) != EOF) {
758 cnt--;
759 if (c == '\n')
760 break;
762 isenc = 0;
763 convert = (action == SEND_TODISP || action == SEND_TODISP_ALL ||
764 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
765 action == SEND_TOSRCH)
766 ? CONV_FROMHDR : CONV_NONE;
768 /* Work the headers */
769 quoteflt_reset(qf, obuf);
770 while (fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
771 ++lineno;
772 if (line[0] == '\n') {
773 /* If line is blank, we've reached end of headers, so force out
774 * status: field and note that we are no longer in header fields */
775 if (dostat & 1)
776 statusput(zmp, obuf, qf, stats);
777 if (dostat & 2)
778 xstatusput(zmp, obuf, qf, stats);
779 if (doign != allignore)
780 _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
781 break;
784 isenc &= ~1;
785 if (infld && blankchar(line[0])) {
786 /* If this line is a continuation (SP / HT) of a previous header
787 * field, determine if the start of the line is a MIME encoded word */
788 if (isenc & 2) {
789 for (cp = line; blankchar(*cp); ++cp);
790 if (cp > line && linelen - PTR2SIZE(cp - line) > 8 &&
791 cp[0] == '=' && cp[1] == '?')
792 isenc |= 1;
794 } else {
795 /* Pick up the header field if we have one */
796 for (cp = line; (c = *cp & 0377) && c != ':' && !spacechar(c); ++cp)
798 cp2 = cp;
799 while (spacechar(*cp))
800 ++cp;
801 if (cp[0] != ':' && level == 0 && lineno == 1) {
802 /* Not a header line, force out status: This happens in uucp style
803 * mail where there are no headers at all */
804 if (dostat & 1)
805 statusput(zmp, obuf, qf, stats);
806 if (dostat & 2)
807 xstatusput(zmp, obuf, qf, stats);
808 if (doign != allignore)
809 _out("\n", 1, obuf, CONV_NONE,SEND_MBOX, qf, stats, NULL);
810 break;
813 /* If it is an ignored field and we care about such things, skip it.
814 * Misuse dostat also for another bit xxx use a bitenum + for more */
815 if (ok_blook(keep_content_length))
816 dostat |= 1 << 2;
817 c = *cp2;
818 *cp2 = 0; /* temporarily null terminate */
819 if ((doign && is_ign(line, PTR2SIZE(cp2 - line), doign)) ||
820 (action == SEND_MBOX && !(dostat & (1 << 2)) &&
821 (!asccasecmp(line, "content-length") ||
822 !asccasecmp(line, "lines"))))
823 ignoring = 1;
824 else if (!asccasecmp(line, "status")) {
825 /* If field is "status," go compute and print real Status: field */
826 if (dostat & 1) {
827 statusput(zmp, obuf, qf, stats);
828 dostat &= ~1;
829 ignoring = 1;
831 } else if (!asccasecmp(line, "x-status")) {
832 /* If field is "status," go compute and print real Status: field */
833 if (dostat & 2) {
834 xstatusput(zmp, obuf, qf, stats);
835 dostat &= ~2;
836 ignoring = 1;
838 } else {
839 ignoring = 0;
840 /* For colourization we need the complete line, so save it */
841 /* XXX This is all temporary (colour belongs into backend), so
842 * XXX use pipecomm as a temporary storage in the meanwhile */
843 #ifdef HAVE_COLOUR
844 if (colour_table != NULL)
845 pipecomm = savestrbuf(line, PTR2SIZE(cp2 - line));
846 #endif
848 *cp2 = c;
849 dostat &= ~(1 << 2);
850 infld = 1;
853 /* Determine if the end of the line is a MIME encoded word */
854 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
855 * TODO with header follow lines, and it should be up to the backend
856 * TODO what happens and what not, i.e., it doesn't matter wether it's
857 * TODO a MIME-encoded word or not, as long as a single separating space
858 * TODO remains in between lines (the MIME stuff will correctly remove
859 * TODO whitespace in between multiple adjacent encoded words) */
860 isenc &= ~2;
861 if (cnt && (c = getc(ibuf)) != EOF) {
862 if (blankchar(c)) {
863 cp = line + linelen - 1;
864 if (linelen > 0 && *cp == '\n')
865 --cp;
866 while (cp >= line && whitechar(*cp))
867 --cp;
868 if (PTR2SIZE(cp - line > 8) && cp[0] == '=' && cp[-1] == '?')
869 isenc |= 2;
871 ungetc(c, ibuf);
874 if (!ignoring) {
875 size_t len = linelen;
876 start = line;
877 if (action == SEND_TODISP || action == SEND_TODISP_ALL ||
878 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
879 action == SEND_TOSRCH) {
880 /* Strip blank characters if two MIME-encoded words follow on
881 * continuing lines */
882 if (isenc & 1)
883 while (len > 0 && blankchar(*start)) {
884 ++start;
885 --len;
887 if (isenc & 2)
888 if (len > 0 && start[len - 1] == '\n')
889 --len;
890 while (len > 0 && blankchar(start[len - 1]))
891 --len;
893 #ifdef HAVE_COLOUR
895 bool_t colour_stripped = FAL0;
896 if (pipecomm != NULL) {
897 colour_put_header(obuf, pipecomm);
898 if (len > 0 && start[len - 1] == '\n') {
899 colour_stripped = TRU1;
900 --len;
903 #endif
904 _out(start, len, obuf, convert, action, qf, stats, NULL);
905 #ifdef HAVE_COLOUR
906 if (pipecomm != NULL) {
907 colour_reset(obuf); /* XXX reset after \n!! */
908 if (colour_stripped)
909 fputc('\n', obuf);
912 #endif
913 if (ferror(obuf)) {
914 free(line);
915 rv = -1;
916 goto jleave;
920 quoteflt_flush(qf);
921 free(line);
922 line = NULL;
923 pipecomm = NULL;
925 jskip:
926 switch (ip->m_mimecontent) {
927 case MIME_822:
928 switch (action) {
929 case SEND_TODISP:
930 case SEND_TODISP_ALL:
931 case SEND_QUOTE:
932 case SEND_QUOTE_ALL:
933 if (ok_blook(rfc822_body_from_)) {
934 if (qf->qf_pfix_len > 0) {
935 size_t i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix,
936 qf->qf_pfix_len, obuf);
937 if (i == qf->qf_pfix_len)
938 _addstats(stats, 0, i);
940 put_from_(obuf, ip->m_multipart, stats);
942 /* FALLTHRU */
943 case SEND_TOSRCH:
944 case SEND_DECRYPT:
945 goto jmulti;
946 case SEND_TOFILE:
947 case SEND_TOPIPE:
948 if (ok_blook(rfc822_body_from_))
949 put_from_(obuf, ip->m_multipart, stats);
950 /* FALLTHRU */
951 case SEND_MBOX:
952 case SEND_RFC822:
953 case SEND_SHOW:
954 break;
956 break;
957 case MIME_TEXT_HTML:
958 case MIME_TEXT:
959 case MIME_TEXT_PLAIN:
960 switch (action) {
961 case SEND_TODISP:
962 case SEND_TODISP_ALL:
963 case SEND_QUOTE:
964 case SEND_QUOTE_ALL:
965 ispipe = TRU1;
966 switch (_pipecmd(&pipecomm, ip)) {
967 case PIPE_MSG:
968 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
969 stats, NULL);
970 /* We would print this as plain text, so better force going home */
971 goto jleave;
972 case PIPE_TEXT:
973 case PIPE_COMM:
974 case PIPE_NULL:
975 break;
976 case PIPE_ASYNC:
977 ispipe = FAL0;
978 break;
980 /* FALLTRHU */
981 default:
982 break;
984 break;
985 case MIME_DISCARD:
986 if (action != SEND_DECRYPT)
987 goto jleave;
988 break;
989 case MIME_PKCS7:
990 if (action != SEND_MBOX && action != SEND_RFC822 &&
991 action != SEND_SHOW && ip->m_multipart != NULL)
992 goto jmulti;
993 /* FALLTHRU */
994 default:
995 switch (action) {
996 case SEND_TODISP:
997 case SEND_TODISP_ALL:
998 case SEND_QUOTE:
999 case SEND_QUOTE_ALL:
1000 ispipe = TRU1;
1001 switch (_pipecmd(&pipecomm, ip)) {
1002 case PIPE_MSG:
1003 _out(pipecomm, strlen(pipecomm), obuf, CONV_NONE, SEND_MBOX, qf,
1004 stats, NULL);
1005 pipecomm = NULL;
1006 break;
1007 case PIPE_ASYNC:
1008 ispipe = FAL0;
1009 /* FALLTHRU */
1010 case PIPE_COMM:
1011 case PIPE_NULL:
1012 break;
1013 case PIPE_TEXT:
1014 goto jcopyout; /* break; break; */
1016 if (pipecomm != NULL)
1017 break;
1018 if (level == 0 && cnt) {
1019 char const *x = _("[Binary content]\n");
1020 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1022 goto jleave;
1023 case SEND_TOFILE:
1024 case SEND_TOPIPE:
1025 case SEND_TOSRCH:
1026 case SEND_DECRYPT:
1027 case SEND_MBOX:
1028 case SEND_RFC822:
1029 case SEND_SHOW:
1030 break;
1032 break;
1033 case MIME_ALTERNATIVE:
1034 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
1035 !ok_blook(print_alternatives)) {
1036 bool_t doact = FAL0;
1038 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart)
1039 if (np->m_mimecontent == MIME_TEXT_PLAIN)
1040 doact = TRU1;
1041 if (doact) {
1042 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1043 if (np->m_ct_type_plain != NULL && action != SEND_QUOTE) {
1044 _print_part_info(&rest, np, doign, level);
1045 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1046 NULL);
1048 if (doact && np->m_mimecontent == MIME_TEXT_PLAIN) {
1049 doact = FAL0;
1050 rv = sendpart(zmp, np, obuf, doign, qf, action, stats,
1051 level + 1);
1052 quoteflt_reset(qf, origobuf);
1053 if (rv < 0)
1054 break;
1057 goto jleave;
1060 /* FALLTHRU */
1061 case MIME_MULTI:
1062 case MIME_DIGEST:
1063 switch (action) {
1064 case SEND_TODISP:
1065 case SEND_TODISP_ALL:
1066 case SEND_QUOTE:
1067 case SEND_QUOTE_ALL:
1068 case SEND_TOFILE:
1069 case SEND_TOPIPE:
1070 case SEND_TOSRCH:
1071 case SEND_DECRYPT:
1072 jmulti:
1073 if ((action == SEND_TODISP || action == SEND_TODISP_ALL) &&
1074 ip->m_multipart != NULL &&
1075 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
1076 ip->m_multipart->m_nextpart == NULL) {
1077 char const *x = _("[Missing multipart boundary - use \"show\" "
1078 "to display the raw message]\n");
1079 _out(x, strlen(x), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL);
1082 for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
1083 if (np->m_mimecontent == MIME_DISCARD && action != SEND_DECRYPT)
1084 continue;
1085 ispipe = FAL0;
1086 switch (action) {
1087 case SEND_TOFILE:
1088 if (np->m_partstring && !strcmp(np->m_partstring, "1"))
1089 break;
1090 stats = NULL;
1091 if ((obuf = newfile(np, UNVOLATILE(&ispipe))) == NULL)
1092 continue;
1093 if (!ispipe)
1094 break;
1095 if (sigsetjmp(_send_pipejmp, 1)) {
1096 rv = -1;
1097 goto jpipe_close;
1099 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1100 break;
1101 case SEND_TODISP:
1102 case SEND_TODISP_ALL:
1103 case SEND_QUOTE_ALL:
1104 if (ip->m_mimecontent != MIME_MULTI &&
1105 ip->m_mimecontent != MIME_ALTERNATIVE &&
1106 ip->m_mimecontent != MIME_DIGEST)
1107 break;
1108 _print_part_info(&rest, np, doign, level);
1109 _out(rest.s, rest.l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
1110 NULL);
1111 break;
1112 case SEND_MBOX:
1113 case SEND_RFC822:
1114 case SEND_SHOW:
1115 case SEND_TOSRCH:
1116 case SEND_QUOTE:
1117 case SEND_DECRYPT:
1118 case SEND_TOPIPE:
1119 break;
1122 quoteflt_flush(qf);
1123 if (sendpart(zmp, np, obuf, doign, qf, action, stats, level+1) < 0)
1124 rv = -1;
1125 quoteflt_reset(qf, origobuf);
1126 if (action == SEND_QUOTE)
1127 break;
1128 if (action == SEND_TOFILE && obuf != origobuf) {
1129 if (!ispipe)
1130 Fclose(obuf);
1131 else {
1132 jpipe_close:
1133 safe_signal(SIGPIPE, SIG_IGN);
1134 Pclose(obuf, TRU1);
1135 safe_signal(SIGPIPE, oldpipe);
1139 goto jleave;
1140 case SEND_MBOX:
1141 case SEND_RFC822:
1142 case SEND_SHOW:
1143 break;
1147 /* Copy out message body */
1148 jcopyout:
1149 if (doign == allignore && level == 0) /* skip final blank line */
1150 --cnt;
1151 switch (ip->m_mimeenc) {
1152 case MIME_BIN:
1153 if (stats != NULL)
1154 stats[0] = -1;
1155 /* FALLTHRU */
1156 case MIME_7B:
1157 case MIME_8B:
1158 convert = CONV_NONE;
1159 break;
1160 case MIME_QP:
1161 convert = CONV_FROMQP;
1162 break;
1163 case MIME_B64:
1164 switch (ip->m_mimecontent) {
1165 case MIME_TEXT:
1166 case MIME_TEXT_PLAIN:
1167 case MIME_TEXT_HTML:
1168 convert = CONV_FROMB64_T;
1169 break;
1170 default:
1171 convert = CONV_FROMB64;
1173 break;
1174 default:
1175 convert = CONV_NONE;
1178 if (action == SEND_DECRYPT || action == SEND_MBOX ||
1179 action == SEND_RFC822 || action == SEND_SHOW)
1180 convert = CONV_NONE;
1181 #ifdef HAVE_ICONV
1182 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
1183 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
1184 action == SEND_TOSRCH) &&
1185 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
1186 ip->m_mimecontent == MIME_TEXT_HTML ||
1187 ip->m_mimecontent == MIME_TEXT)) {
1188 char const *tcs = charset_get_lc();
1190 if (iconvd != (iconv_t)-1)
1191 n_iconv_close(iconvd);
1192 /* TODO Since Base64 has an odd 4:3 relation in between input
1193 * TODO and output an input line may end with a partial
1194 * TODO multibyte character; this is no problem at all unless
1195 * TODO we send to the display or whatever, i.e., ensure
1196 * TODO makeprint() or something; to avoid this trap, *force*
1197 * TODO iconv(), in which case this layer will handle leftovers
1198 * TODO correctly */
1199 if (convert == CONV_FROMB64_T || (asccasecmp(tcs, ip->m_charset) &&
1200 asccasecmp(charset_get_7bit(), ip->m_charset))) {
1201 iconvd = n_iconv_open(tcs, ip->m_charset);
1202 /* XXX Don't bail out if we cannot iconv(3) here;
1203 * XXX alternatively we could avoid trying to open
1204 * XXX if ip->m_charset is "unknown-8bit", which was
1205 * XXX the one that has bitten me?? */
1207 * TODO errors should DEFINETELY not be scrolled away!
1208 * TODO what about an error buffer (think old shsp(1)),
1209 * TODO re-dump errors since last snapshot when the
1210 * TODO command loop enters again? i.e., at least print
1211 * TODO "There were errors ?" before the next prompt,
1212 * TODO so that the user can look at the error buffer?
1214 if (iconvd == (iconv_t)-1 && errno == EINVAL) {
1215 fprintf(stderr, _("Cannot convert from %s to %s\n"),
1216 ip->m_charset, tcs);
1217 /*rv = 1; goto jleave;*/
1221 #endif
1223 if (pipecomm != NULL && (action == SEND_TODISP ||
1224 action == SEND_TODISP_ALL || action == SEND_QUOTE ||
1225 action == SEND_QUOTE_ALL)) {
1226 qbuf = obuf;
1227 pbuf = _pipefile(pipecomm, ip, UNVOLATILE(&qbuf),
1228 (action == SEND_QUOTE || action == SEND_QUOTE_ALL), !ispipe);
1229 action = SEND_TOPIPE;
1230 if (pbuf != qbuf) {
1231 oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1232 if (sigsetjmp(_send_pipejmp, 1))
1233 goto jend;
1235 } else
1236 pbuf = qbuf = obuf;
1239 bool_t eof;
1240 ui32_t save_qf_pfix_len = qf->qf_pfix_len;
1241 off_t *save_stats = stats;
1243 if (pbuf != origobuf) {
1244 qf->qf_pfix_len = 0; /* XXX legacy (remove filter instead) */
1245 stats = NULL;
1247 eof = FAL0;
1248 rest.s = NULL;
1249 rest.l = 0;
1251 quoteflt_reset(qf, pbuf);
1252 while (!eof && fgetline(&line, &linesize, &cnt, &linelen, ibuf, 0)) {
1253 joutln:
1254 if (_out(line, linelen, pbuf, convert, action, qf, stats, &rest) < 0 ||
1255 ferror(pbuf)) {
1256 rv = -1; /* XXX Should bail away?! */
1257 break;
1260 if (!eof && rest.l != 0) {
1261 linelen = 0;
1262 eof = TRU1;
1263 action |= _TD_EOF;
1264 goto joutln;
1266 quoteflt_flush(qf);
1267 if (rest.s != NULL)
1268 free(rest.s);
1270 if (pbuf != origobuf) {
1271 qf->qf_pfix_len = save_qf_pfix_len;
1272 stats = save_stats;
1276 jend:
1277 free(line);
1278 if (pbuf != qbuf) {
1279 safe_signal(SIGPIPE, SIG_IGN);
1280 Pclose(pbuf, ispipe);
1281 safe_signal(SIGPIPE, oldpipe);
1282 if (qbuf != obuf)
1283 pipecpy(qbuf, obuf, origobuf, qf, stats);
1285 #ifdef HAVE_ICONV
1286 if (iconvd != (iconv_t)-1)
1287 n_iconv_close(iconvd);
1288 #endif
1289 jleave:
1290 NYD_LEAVE;
1291 return rv;
1294 static FILE *
1295 newfile(struct mimepart *ip, int *ispipe)
1297 struct str in, out;
1298 char *f;
1299 FILE *fp;
1300 NYD_ENTER;
1302 f = ip->m_filename;
1303 *ispipe = 0;
1305 if (f != NULL && f != (char*)-1) {
1306 in.s = f;
1307 in.l = strlen(f);
1308 mime_fromhdr(&in, &out, TD_ISPR);
1309 memcpy(f, out.s, out.l);
1310 *(f + out.l) = '\0';
1311 free(out.s);
1314 if (options & OPT_INTERACTIVE) {
1315 char *f2, *f3;
1316 jgetname:
1317 printf(_("Enter filename for part %s (%s)"),
1318 (ip->m_partstring != NULL) ? ip->m_partstring : "?",
1319 ip->m_ct_type_plain);
1320 f2 = readstr_input(": ", (f != (char*)-1) ? f : NULL);
1321 if (f2 == NULL || *f2 == '\0') {
1322 fprintf(stderr, _("... skipping this\n"));
1323 fp = NULL;
1324 goto jleave;
1325 } else if (*f2 == '|')
1326 /* Pipes are expanded by the shell */
1327 f = f2;
1328 else if ((f3 = file_expand(f2)) == NULL)
1329 /* (Error message written by file_expand()) */
1330 goto jgetname;
1331 else
1332 f = f3;
1334 if (f == NULL || f == (char*)-1) {
1335 fp = NULL;
1336 goto jleave;
1339 if (*f == '|') {
1340 char const *cp;
1341 cp = ok_vlook(SHELL);
1342 if (cp == NULL)
1343 cp = XSHELL;
1344 fp = Popen(f + 1, "w", cp, NULL, 1);
1345 if (!(*ispipe = (fp != NULL)))
1346 perror(f);
1347 } else {
1348 if ((fp = Fopen(f, "w")) == NULL)
1349 fprintf(stderr, _("Cannot open `%s'\n"), f);
1351 jleave:
1352 NYD_LEAVE;
1353 return fp;
1356 static void
1357 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf, struct quoteflt *qf,
1358 off_t *stats)
1360 char *line = NULL; /* TODO line pool */
1361 size_t linesize = 0, linelen, cnt;
1362 ssize_t all_sz, sz;
1363 NYD_ENTER;
1365 fflush(pipebuf);
1366 rewind(pipebuf);
1367 cnt = fsize(pipebuf);
1368 all_sz = 0;
1370 quoteflt_reset(qf, outbuf);
1371 while (fgetline(&line, &linesize, &cnt, &linelen, pipebuf, 0) != NULL) {
1372 if ((sz = quoteflt_push(qf, line, linelen)) < 0)
1373 break;
1374 all_sz += sz;
1376 if ((sz = quoteflt_flush(qf)) > 0)
1377 all_sz += sz;
1378 if (line)
1379 free(line);
1381 if (all_sz > 0 && outbuf == origobuf)
1382 _addstats(stats, 1, all_sz);
1383 fclose(pipebuf);
1384 NYD_LEAVE;
1387 static void
1388 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1389 off_t *stats)
1391 char statout[3], *cp = statout;
1392 NYD_ENTER;
1394 if (mp->m_flag & MREAD)
1395 *cp++ = 'R';
1396 if (!(mp->m_flag & MNEW))
1397 *cp++ = 'O';
1398 *cp = 0;
1399 if (statout[0]) {
1400 int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1401 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), statout);
1402 if (i > 0)
1403 _addstats(stats, 1, i);
1405 NYD_LEAVE;
1408 static void
1409 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1410 off_t *stats)
1412 char xstatout[4];
1413 char *xp = xstatout;
1414 NYD_ENTER;
1416 if (mp->m_flag & MFLAGGED)
1417 *xp++ = 'F';
1418 if (mp->m_flag & MANSWERED)
1419 *xp++ = 'A';
1420 if (mp->m_flag & MDRAFTED)
1421 *xp++ = 'T';
1422 *xp = 0;
1423 if (xstatout[0]) {
1424 int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1425 (qf->qf_pfix_len > 0 ? qf->qf_pfix : 0), xstatout);
1426 if (i > 0)
1427 _addstats(stats, 1, i);
1429 NYD_LEAVE;
1432 static void
1433 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1435 char const *froma, *date, *nl;
1436 int i;
1437 NYD_ENTER;
1439 if (ip != NULL && ip->m_from != NULL) {
1440 froma = ip->m_from;
1441 date = fakedate(ip->m_time);
1442 nl = "\n";
1443 } else {
1444 froma = myname;
1445 date = time_current.tc_ctime;
1446 nl = "";
1449 colour_put(fp, COLOURSPEC_FROM_);
1450 i = fprintf(fp, "From %s %s%s", froma, date, nl);
1451 colour_reset(fp);
1452 if (i > 0)
1453 _addstats(stats, (*nl != '\0'), i);
1454 NYD_LEAVE;
1457 FL int
1458 sendmp(struct message *mp, FILE *obuf, struct ignoretab *doign,
1459 char const *prefix, enum sendaction action, off_t *stats)
1461 struct quoteflt qf;
1462 size_t cnt, sz, i;
1463 FILE *ibuf;
1464 enum parseflags pf;
1465 struct mimepart *ip;
1466 int rv = -1, c;
1467 NYD_ENTER;
1469 if (mp == dot && action != SEND_TOSRCH)
1470 did_print_dot = 1;
1471 if (stats != NULL)
1472 stats[0] = stats[1] = 0;
1473 quoteflt_init(&qf, prefix);
1475 /* First line is the From_ line, so no headers there to worry about */
1476 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1477 goto jleave;
1479 cnt = mp->m_size;
1480 sz = 0;
1482 struct str const *cpre, *csuf;
1483 #ifdef HAVE_COLOUR
1484 cpre = colour_get(COLOURSPEC_FROM_);
1485 csuf = colour_get(COLOURSPEC_RESET);
1486 #else
1487 cpre = csuf = NULL;
1488 #endif
1489 if (mp->m_flag & MNOFROM) {
1490 if (doign != allignore && doign != fwdignore && action != SEND_RFC822)
1491 sz = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1492 (cpre != NULL ? cpre->s : ""),
1493 (int)qf.qf_pfix_len, (qf.qf_pfix_len != 0 ? qf.qf_pfix : ""),
1494 fakefrom(mp), fakedate(mp->m_time),
1495 (csuf != NULL ? csuf->s : ""));
1496 } else {
1497 if (doign != allignore && doign != fwdignore && action != SEND_RFC822) {
1498 if (qf.qf_pfix_len > 0) {
1499 i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1500 if (i != qf.qf_pfix_len)
1501 goto jleave;
1502 sz += i;
1504 #ifdef HAVE_COLOUR
1505 if (cpre != NULL) {
1506 fputs(cpre->s, obuf);
1507 cpre = (struct str const*)0x1;
1509 #endif
1512 while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1513 if (doign != allignore && doign != fwdignore &&
1514 action != SEND_RFC822) {
1515 #ifdef HAVE_COLOUR
1516 if (c == '\n' && csuf != NULL) {
1517 cpre = (struct str const*)0x1;
1518 fputs(csuf->s, obuf);
1520 #endif
1521 putc(c, obuf);
1522 sz++;
1524 --cnt;
1525 if (c == '\n')
1526 break;
1529 #ifdef HAVE_COLOUR
1530 if (csuf != NULL && cpre != (struct str const*)0x1)
1531 fputs(csuf->s, obuf);
1532 #endif
1535 if (sz > 0)
1536 _addstats(stats, 1, sz);
1538 pf = 0;
1539 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1540 pf |= PARSE_DECRYPT | PARSE_PARTS;
1541 if ((ip = parsemsg(mp, pf)) == NULL)
1542 goto jleave;
1544 rv = sendpart(mp, ip, obuf, doign, &qf, action, stats, 0);
1545 jleave:
1546 quoteflt_destroy(&qf);
1547 NYD_LEAVE;
1548 return rv;
1551 /* s-it-mode */