Drop undocumented *broken-mbox* variable..
[s-mailx.git] / send.c
blob350152d62c46a43dc7f542ab413dbab7f992a93d
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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 #include "rcv.h"
41 #include "extern.h"
42 #include <time.h>
43 #include <unistd.h>
44 #include <sys/stat.h>
47 * Mail -- a mail program
49 * Mail to mail folders and displays.
52 /* TODO longjmp() globbering as in cmd1.c and cmd3.c (see there)
53 * TODO Problem: Popen doesn't encapsulate all cases of open failures,
54 * TODO may leave child running if fdopen() fails! */
56 enum parseflags {
57 PARSE_DEFAULT = 0,
58 PARSE_DECRYPT = 01,
59 PARSE_PARTS = 02
62 static void onpipe(int signo);
63 extern void brokpipe(int signo);
64 static int sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
65 struct ignoretab *doign, char *prefix, size_t prefixlen,
66 enum sendaction action, off_t *stats, int level);
67 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
68 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
69 enum parseflags pf, int level);
70 static void parsemultipart(struct message *zmp, struct mimepart *ip,
71 enum parseflags pf, int level);
72 static void newpart(struct mimepart *ip, struct mimepart **np, off_t offs,
73 int *part);
74 static void endpart(struct mimepart **np, off_t xoffs, long lines);
75 static void parse822(struct message *zmp, struct mimepart *ip,
76 enum parseflags pf, int level);
77 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
78 enum parseflags pf, int level);
79 static size_t out(char *buf, size_t len, FILE *fp,
80 enum conversion convert, enum sendaction action,
81 char *prefix, size_t prefixlen, off_t *stats,
82 char **restp, size_t *restsizep);
83 static void addstats(off_t *stats, off_t lines, off_t bytes);
84 static FILE *newfile(struct mimepart *ip, int *ispipe,
85 sighandler_type *oldpipe);
86 static char *getpipecmd(char *content);
87 static FILE *getpipefile(char *cmd, FILE **qbuf, int quote);
88 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
89 char *prefix, size_t prefixlen, off_t *stats);
90 static void statusput(const struct message *mp, FILE *obuf,
91 char *prefix, off_t *stats);
92 static void xstatusput(const struct message *mp, FILE *obuf,
93 char *prefix, off_t *stats);
94 static void put_from_(FILE *fp, struct mimepart *ip);
96 static sigjmp_buf pipejmp;
98 /*ARGSUSED*/
99 static void
100 onpipe(int signo)
102 (void)signo;
103 siglongjmp(pipejmp, 1);
107 * Send message described by the passed pointer to the
108 * passed output buffer. Return -1 on error.
109 * Adjust the status: field if need be.
110 * If doign is given, suppress ignored header fields.
111 * prefix is a string to prepend to each output line.
112 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
113 * stats[0] is line count, stats[1] is character count. stats may be NULL.
114 * Note that stats[0] is valid for SEND_MBOX only.
117 send(struct message *mp, FILE *obuf, struct ignoretab *doign,
118 char *prefix, enum sendaction action, off_t *stats)
120 size_t count;
121 FILE *ibuf;
122 size_t prefixlen, sz;
123 int c;
124 enum parseflags pf;
125 struct mimepart *ip;
126 char *cp, *cp2;
128 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
129 did_print_dot = 1;
130 if (stats)
131 stats[0] = stats[1] = 0;
133 * Compute the prefix string, without trailing whitespace
135 if (prefix != NULL) {
136 cp2 = 0;
137 for (cp = prefix; *cp; cp++)
138 if (!blankchar(*cp & 0377))
139 cp2 = cp;
140 prefixlen = cp2 == 0 ? 0 : cp2 - prefix + 1;
141 } else
142 prefixlen = 0;
144 * First line is the From_ line, so no headers there to worry about.
146 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
147 return -1;
148 count = mp->m_size;
149 sz = 0;
150 if (mp->m_flag & MNOFROM) {
151 if (doign != allignore && doign != fwdignore &&
152 action != SEND_RFC822)
153 sz = fprintf(obuf, "%sFrom %s %s\n",
154 prefix ? prefix : "",
155 fakefrom(mp), fakedate(mp->m_time));
156 } else {
157 if (prefix && doign != allignore && doign != fwdignore &&
158 action != SEND_RFC822) {
159 fputs(prefix, obuf);
160 sz += strlen(prefix);
162 while (count && (c = getc(ibuf)) != EOF) {
163 if (doign != allignore && doign != fwdignore &&
164 action != SEND_RFC822) {
165 putc(c, obuf);
166 sz++;
168 count--;
169 if (c == '\n')
170 break;
173 if (sz)
174 addstats(stats, 1, sz);
175 pf = 0;
176 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
177 pf |= PARSE_DECRYPT|PARSE_PARTS;
178 if ((ip = parsemsg(mp, pf)) == NULL)
179 return -1;
180 return sendpart(mp, ip, obuf, doign, prefix, prefixlen, action, stats,
184 static int
185 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
186 struct ignoretab *doign, char *prefix, size_t prefixlen,
187 enum sendaction action, off_t *stats, int level)
189 char *line = NULL;
190 size_t linesize = 0, linelen, count, len;
191 int dostat, infld = 0, ignoring = 1, isenc;
192 char *cp, *cp2, *start;
193 int c;
194 struct mimepart *np;
195 FILE *ibuf = NULL, *pbuf = obuf, *qbuf = obuf, *origobuf = obuf;
196 char *tcs, *pipecmd = NULL;
197 enum conversion convert;
198 sighandler_type oldpipe = SIG_DFL;
199 int rt = 0;
200 long lineno = 0;
201 int ispipe = 0;
202 char *rest;
203 size_t restsize;
204 int eof;
206 (void)&ibuf;
207 (void)&pbuf;
208 (void)&convert;
209 (void)&oldpipe;
210 (void)&rt;
211 (void)&obuf;
212 (void)&stats;
213 (void)&action;
214 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
215 action != SEND_MBOX && action != SEND_RFC822 &&
216 action != SEND_SHOW)
217 goto skip;
218 dostat = 0;
219 if (level == 0) {
220 if (doign != NULL) {
221 if (!is_ign("status", 6, doign))
222 dostat |= 1;
223 if (!is_ign("x-status", 8, doign))
224 dostat |= 2;
225 } else
226 dostat = 3;
228 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
229 return -1;
230 count = ip->m_size;
231 if (ip->m_mimecontent == MIME_DISCARD)
232 goto skip;
233 if ((ip->m_flag&MNOFROM) == 0)
234 while (count && (c = getc(ibuf)) != EOF) {
235 count--;
236 if (c == '\n')
237 break;
239 isenc = 0;
240 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
241 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
242 action == SEND_TOSRCH || action == SEND_TOFLTR ?
243 CONV_FROMHDR : CONV_NONE;
244 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
245 lineno++;
246 if (line[0] == '\n') {
248 * If line is blank, we've reached end of
249 * headers, so force out status: field
250 * and note that we are no longer in header
251 * fields
253 if (dostat & 1)
254 statusput(zmp, obuf, prefix, stats);
255 if (dostat & 2)
256 xstatusput(zmp, obuf, prefix, stats);
257 if (doign != allignore)
258 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
259 prefix, prefixlen, stats,
260 NULL, NULL);
261 break;
263 isenc &= ~1;
264 if (infld && blankchar(line[0]&0377)) {
266 * If this line is a continuation (via space or tab)
267 * of a previous header field, determine if the start
268 * of the line is a MIME encoded word.
270 if (isenc & 2) {
271 for (cp = line; blankchar(*cp&0377); cp++);
272 if (cp > line && linelen - (cp - line) > 8 &&
273 cp[0] == '=' && cp[1] == '?')
274 isenc |= 1;
276 } else {
278 * Pick up the header field if we have one.
280 for (cp = line; (c = *cp&0377) && c != ':' &&
281 !spacechar(c); cp++);
282 cp2 = cp;
283 while (spacechar(*cp&0377))
284 cp++;
285 if (cp[0] != ':' && level == 0 && lineno == 1) {
287 * Not a header line, force out status:
288 * This happens in uucp style mail where
289 * there are no headers at all.
291 if (dostat & 1)
292 statusput(zmp, obuf, prefix, stats);
293 if (dostat & 2)
294 xstatusput(zmp, obuf, prefix, stats);
295 if (doign != allignore)
296 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
297 prefix, prefixlen, stats,
298 NULL, NULL);
299 break;
302 * If it is an ignored field and
303 * we care about such things, skip it.
305 c = *cp2;
306 *cp2 = 0; /* temporarily null terminate */
307 if (doign && is_ign(line, cp2 - line, doign))
308 ignoring = 1;
309 else if (asccasecmp(line, "status") == 0) {
311 * If the field is "status," go compute
312 * and print the real Status: field
314 if (dostat & 1) {
315 statusput(zmp, obuf, prefix, stats);
316 dostat &= ~1;
317 ignoring = 1;
319 } else if (asccasecmp(line, "x-status") == 0) {
321 * If the field is "status," go compute
322 * and print the real Status: field
324 if (dostat & 2) {
325 xstatusput(zmp, obuf, prefix, stats);
326 dostat &= ~2;
327 ignoring = 1;
329 } else
330 ignoring = 0;
331 *cp2 = c;
332 infld = 1;
335 * Determine if the end of the line is a MIME encoded word.
337 isenc &= ~2;
338 if (count && (c = getc(ibuf)) != EOF) {
339 if (blankchar(c)) {
340 if (linelen > 0 && line[linelen-1] == '\n')
341 cp = &line[linelen-2];
342 else
343 cp = &line[linelen-1];
344 while (cp >= line && whitechar(*cp&0377))
345 cp++;
346 if (cp - line > 8 && cp[0] == '=' &&
347 cp[-1] == '?')
348 isenc |= 2;
350 ungetc(c, ibuf);
352 if (!ignoring) {
353 start = line;
354 len = linelen;
355 if (action == SEND_TODISP ||
356 action == SEND_TODISP_ALL ||
357 action == SEND_QUOTE ||
358 action == SEND_QUOTE_ALL ||
359 action == SEND_TOSRCH ||
360 action == SEND_TOFLTR) {
362 * Strip blank characters if two MIME-encoded
363 * words follow on continuing lines.
365 if (isenc & 1)
366 while (len>0&&blankchar(*start&0377)) {
367 start++;
368 len--;
370 if (isenc & 2)
371 if (len > 0 && start[len-1] == '\n')
372 len--;
373 while (len > 0 && blankchar(start[len-1]&0377))
374 len--;
376 out(start, len, obuf, convert,
377 action, prefix, prefixlen, stats,
378 NULL, NULL);
379 if (ferror(obuf)) {
380 free(line);
381 return -1;
385 free(line);
386 line = NULL;
387 skip: switch (ip->m_mimecontent) {
388 case MIME_822:
389 switch (action) {
390 case SEND_TOFLTR:
391 putc('\0', obuf);
392 /*FALLTHRU*/
393 case SEND_TODISP:
394 case SEND_TODISP_ALL:
395 case SEND_QUOTE:
396 case SEND_QUOTE_ALL:
397 put_from_(obuf, ip->m_multipart);
398 /*FALLTHRU*/
399 case SEND_TOSRCH:
400 case SEND_DECRYPT:
401 goto multi;
402 case SEND_TOFILE:
403 case SEND_TOPIPE:
404 put_from_(obuf, ip->m_multipart);
405 /*FALLTHRU*/
406 case SEND_MBOX:
407 case SEND_RFC822:
408 case SEND_SHOW:
409 break;
411 break;
412 case MIME_TEXT_HTML:
413 if (action == SEND_TOFLTR)
414 putc('\b', obuf);
415 /*FALLTHRU*/
416 case MIME_TEXT:
417 case MIME_TEXT_PLAIN:
418 switch (action) {
419 case SEND_TODISP:
420 case SEND_TODISP_ALL:
421 case SEND_QUOTE:
422 case SEND_QUOTE_ALL:
423 pipecmd = getpipecmd(ip->m_ct_type_plain);
424 /*FALLTHRU*/
425 default:
426 break;
428 break;
429 case MIME_DISCARD:
430 if (action != SEND_DECRYPT)
431 return rt;
432 break;
433 case MIME_PKCS7:
434 if (action != SEND_MBOX && action != SEND_RFC822 &&
435 action != SEND_SHOW && ip->m_multipart)
436 goto multi;
437 /*FALLTHRU*/
438 default:
439 switch (action) {
440 case SEND_TODISP:
441 case SEND_TODISP_ALL:
442 case SEND_QUOTE:
443 case SEND_QUOTE_ALL:
444 if ((pipecmd = getpipecmd(ip->m_ct_type_plain)) != NULL)
445 break;
446 if (level == 0 && count) {
447 cp = "[Binary content]\n\n";
448 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
449 prefix, prefixlen, stats,
450 NULL, NULL);
452 /*FALLTHRU*/
453 case SEND_TOFLTR:
454 return rt;
455 case SEND_TOFILE:
456 case SEND_TOPIPE:
457 case SEND_TOSRCH:
458 case SEND_DECRYPT:
459 case SEND_MBOX:
460 case SEND_RFC822:
461 case SEND_SHOW:
462 break;
464 break;
465 case MIME_ALTERNATIVE:
466 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
467 value("print-alternatives") == NULL)
468 for (np = ip->m_multipart; np; np = np->m_nextpart)
469 if (np->m_mimecontent == MIME_TEXT_PLAIN) {
470 if (sendpart(zmp, np, obuf,
471 doign, prefix,
472 prefixlen,
473 action, stats,
474 level+1) < 0)
475 return -1;
476 return rt;
478 /*FALLTHRU*/
479 case MIME_MULTI:
480 case MIME_DIGEST:
481 switch (action) {
482 case SEND_TODISP:
483 case SEND_TODISP_ALL:
484 case SEND_QUOTE:
485 case SEND_QUOTE_ALL:
486 case SEND_TOFILE:
487 case SEND_TOPIPE:
488 case SEND_TOSRCH:
489 case SEND_TOFLTR:
490 case SEND_DECRYPT:
491 multi:
492 if ((action == SEND_TODISP ||
493 action == SEND_TODISP_ALL) &&
494 ip->m_multipart != NULL &&
495 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
496 ip->m_multipart->m_nextpart == NULL) {
497 cp = "[Missing multipart boundary - "
498 "use \"show\" to display "
499 "the raw message]\n\n";
500 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
501 prefix, prefixlen, stats,
502 NULL, NULL);
504 for (np = ip->m_multipart; np; np = np->m_nextpart) {
505 if (np->m_mimecontent == MIME_DISCARD &&
506 action != SEND_DECRYPT)
507 continue;
508 switch (action) {
509 case SEND_TOFILE:
510 if (np->m_partstring &&
511 strcmp(np->m_partstring,
512 "1") == 0)
513 break;
514 stats = NULL;
515 if ((obuf = newfile(np, &ispipe,
516 &oldpipe)) == NULL)
517 continue;
518 break;
519 case SEND_TODISP:
520 case SEND_TODISP_ALL:
521 case SEND_QUOTE_ALL:
522 if ((ip->m_mimecontent == MIME_MULTI ||
523 ip->m_mimecontent ==
524 MIME_ALTERNATIVE ||
525 ip->m_mimecontent ==
526 MIME_DIGEST) &&
527 np->m_partstring) {
528 len = strlen(np->m_partstring) +
530 cp = ac_alloc(len);
531 snprintf(cp, len,
532 "%sPart %s:\n", level ||
533 strcmp(np->m_partstring,
534 "1") ?
535 "\n" : "",
536 np->m_partstring);
537 out(cp, strlen(cp), obuf,
538 CONV_NONE, SEND_MBOX,
539 prefix, prefixlen,
540 stats,
541 NULL, NULL);
542 ac_free(cp);
544 break;
545 case SEND_TOFLTR:
546 putc('\0', obuf);
547 /*FALLTHRU*/
548 case SEND_MBOX:
549 case SEND_RFC822:
550 case SEND_SHOW:
551 case SEND_TOSRCH:
552 case SEND_QUOTE:
553 case SEND_DECRYPT:
554 case SEND_TOPIPE:
555 break;
557 if (sendpart(zmp, np, obuf,
558 doign, prefix, prefixlen,
559 action, stats, level+1) < 0)
560 rt = -1;
561 else if (action == SEND_QUOTE)
562 break;
563 if (action == SEND_TOFILE && obuf != origobuf) {
564 if (ispipe == 0)
565 Fclose(obuf);
566 else {
567 safe_signal(SIGPIPE, SIG_IGN);
568 Pclose(obuf);
569 safe_signal(SIGPIPE, oldpipe);
573 return rt;
574 case SEND_MBOX:
575 case SEND_RFC822:
576 case SEND_SHOW:
577 break;
581 * Copy out message body
583 if (doign == allignore && level == 0) /* skip final blank line */
584 count--;
585 switch (ip->m_mimeenc) {
586 case MIME_BIN:
587 if (stats)
588 stats[0] = -1;
589 /*FALLTHRU*/
590 case MIME_7B:
591 case MIME_8B:
592 convert = CONV_NONE;
593 break;
594 case MIME_QP:
595 convert = CONV_FROMQP;
596 break;
597 case MIME_B64:
598 switch (ip->m_mimecontent) {
599 case MIME_TEXT:
600 case MIME_TEXT_PLAIN:
601 case MIME_TEXT_HTML:
602 convert = CONV_FROMB64_T;
603 break;
604 default:
605 convert = CONV_FROMB64;
607 break;
608 default:
609 convert = CONV_NONE;
611 if (action == SEND_DECRYPT || action == SEND_MBOX ||
612 action == SEND_RFC822 || action == SEND_SHOW)
613 convert = CONV_NONE;
614 tcs = gettcharset();
615 #ifdef HAVE_ICONV
616 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
617 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
618 action == SEND_TOSRCH) &&
619 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
620 ip->m_mimecontent == MIME_TEXT_HTML ||
621 ip->m_mimecontent == MIME_TEXT)) {
622 if (iconvd != (iconv_t)-1)
623 iconv_close(iconvd);
624 if (asccasecmp(tcs, ip->m_charset) &&
625 asccasecmp(us_ascii, ip->m_charset))
626 iconvd = iconv_open_ft(tcs, ip->m_charset);
627 else
628 iconvd = (iconv_t)-1;
630 #endif /* HAVE_ICONV */
631 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
632 action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
633 pipecmd != NULL) {
634 qbuf = obuf;
635 pbuf = getpipefile(pipecmd, &qbuf,
636 action == SEND_QUOTE || action == SEND_QUOTE_ALL);
637 action = SEND_TOPIPE;
638 if (pbuf != qbuf) {
639 oldpipe = safe_signal(SIGPIPE, onpipe);
640 if (sigsetjmp(pipejmp, 1))
641 goto end;
643 } else
644 pbuf = qbuf = obuf;
645 eof = 0;
646 while (!eof && foldergets(&line, &linesize, &count, &linelen, ibuf)) {
647 lineno++;
648 while (convert == CONV_FROMQP && linelen >= 2 &&
649 line[linelen-2] == '=') {
650 char *line2;
651 size_t linesize2, linelen2;
652 nextl:
653 line2 = NULL;
654 linesize2 = 0;
655 if (foldergets(&line2, &linesize2, &count, &linelen2,
656 ibuf) == NULL) {
657 eof = 1;
658 break;
660 if (linelen + linelen2 + 1 > linesize)
661 line = srealloc(line, linesize = linelen +
662 linelen2 + 1);
663 memcpy(&line[linelen], line2, linelen2+1);
664 linelen += linelen2;
665 free(line2);
667 rest = NULL;
668 restsize = 0;
669 out(line, linelen, pbuf, convert, action,
670 pbuf == origobuf ? prefix : NULL,
671 pbuf == origobuf ? prefixlen : 0,
672 pbuf == origobuf ? stats : NULL,
673 eof ? NULL : &rest, eof ? NULL : &restsize);
674 if (ferror(pbuf)) {
675 rt = -1;
676 break;
678 if (restsize) {
679 if (line != rest)
680 memmove(line, rest, restsize);
681 linelen = restsize;
682 goto nextl;
685 end: free(line);
686 if (pbuf != qbuf) {
687 safe_signal(SIGPIPE, SIG_IGN);
688 Pclose(pbuf);
689 safe_signal(SIGPIPE, oldpipe);
690 if (qbuf != obuf)
691 pipecpy(qbuf, obuf, origobuf, prefix, prefixlen, stats);
693 #ifdef HAVE_ICONV
694 if (iconvd != (iconv_t)-1) {
695 iconv_close(iconvd);
696 iconvd = (iconv_t)-1;
698 #endif
699 return rt;
702 static struct mimepart *
703 parsemsg(struct message *mp, enum parseflags pf)
705 struct mimepart *ip;
707 ip = csalloc(1, sizeof *ip);
708 ip->m_flag = mp->m_flag;
709 ip->m_have = mp->m_have;
710 ip->m_block = mp->m_block;
711 ip->m_offset = mp->m_offset;
712 ip->m_size = mp->m_size;
713 ip->m_xsize = mp->m_xsize;
714 ip->m_lines = mp->m_lines;
715 ip->m_xlines = mp->m_lines;
716 if (parsepart(mp, ip, pf, 0) != OKAY)
717 return NULL;
718 return ip;
721 static enum okay
722 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
723 int level)
725 char *cp;
727 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
728 if (ip->m_ct_type != NULL) {
729 ip->m_ct_type_plain = savestr(ip->m_ct_type);
730 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
731 *cp = '\0';
732 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
733 ip->m_ct_type_plain = "message/rfc822";
734 else
735 ip->m_ct_type_plain = "text/plain";
736 ip->m_mimecontent = mime_getcontent(ip->m_ct_type_plain);
737 if (ip->m_ct_type)
738 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
739 if (ip->m_charset == NULL)
740 ip->m_charset = us_ascii;
741 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
742 (struct message *)ip);
743 ip->m_mimeenc = ip->m_ct_transfer_enc ?
744 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
745 if ((cp = hfield1("content-disposition", (struct message *)ip)) == 0 ||
746 (ip->m_filename = mime_getparam("filename", cp)) == 0)
747 if (ip->m_ct_type != NULL)
748 ip->m_filename = mime_getparam("name", ip->m_ct_type);
749 if (pf & PARSE_PARTS) {
750 if (level > 9999) {
751 fprintf(stderr, "MIME content too deeply nested.\n");
752 return STOP;
754 switch (ip->m_mimecontent) {
755 case MIME_PKCS7:
756 if (pf & PARSE_DECRYPT) {
757 parsepkcs7(zmp, ip, pf, level);
758 break;
760 /*FALLTHRU*/
761 default:
762 break;
763 case MIME_MULTI:
764 case MIME_ALTERNATIVE:
765 case MIME_DIGEST:
766 parsemultipart(zmp, ip, pf, level);
767 break;
768 case MIME_822:
769 parse822(zmp, ip, pf, level);
770 break;
773 return OKAY;
776 static void
777 parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
778 int level)
780 char *boundary;
781 char *line = NULL;
782 size_t linesize = 0, linelen, count, boundlen;
783 FILE *ibuf;
784 struct mimepart *np = NULL;
785 off_t offs;
786 int part = 0;
787 long lines = 0;
789 if ((boundary = mime_getboundary(ip->m_ct_type)) == NULL)
790 return;
791 boundlen = strlen(boundary);
792 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
793 return;
794 count = ip->m_size;
795 while (foldergets(&line, &linesize, &count, &linelen, ibuf))
796 if (line[0] == '\n')
797 break;
798 offs = ftell(ibuf);
799 newpart(ip, &np, offs, NULL);
800 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
801 if ((lines > 0 || part == 0) && linelen >= boundlen + 1 &&
802 strncmp(line, boundary, boundlen) == 0) {
803 if (line[boundlen] == '\n') {
804 offs = ftell(ibuf);
805 if (part != 0) {
806 endpart(&np, offs-boundlen-2, lines);
807 newpart(ip, &np, offs-boundlen-2, NULL);
809 endpart(&np, offs, 2);
810 newpart(ip, &np, offs, &part);
811 lines = 0;
812 } else if (line[boundlen] == '-' &&
813 line[boundlen+1] == '-' &&
814 line[boundlen+2] == '\n') {
815 offs = ftell(ibuf);
816 if (part != 0) {
817 endpart(&np, offs-boundlen-4, lines);
818 newpart(ip, &np, offs-boundlen-4, NULL);
820 endpart(&np, offs+count, 2);
821 break;
822 } else
823 lines++;
824 } else
825 lines++;
827 if (np) {
828 offs = ftell(ibuf);
829 endpart(&np, offs, lines);
831 for (np = ip->m_multipart; np; np = np->m_nextpart)
832 if (np->m_mimecontent != MIME_DISCARD)
833 parsepart(zmp, np, pf, level+1);
834 free(line);
837 static void
838 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
840 struct mimepart *pp;
841 size_t sz;
843 *np = csalloc(1, sizeof **np);
844 (*np)->m_flag = MNOFROM;
845 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
846 (*np)->m_block = mailx_blockof(offs);
847 (*np)->m_offset = mailx_offsetof(offs);
848 if (part) {
849 (*part)++;
850 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
851 sz += 20;
852 (*np)->m_partstring = salloc(sz);
853 if (ip->m_partstring)
854 snprintf((*np)->m_partstring, sz, "%s.%u",
855 ip->m_partstring, *part);
856 else
857 snprintf((*np)->m_partstring, sz, "%u", *part);
858 } else
859 (*np)->m_mimecontent = MIME_DISCARD;
860 (*np)->m_parent = ip;
861 if (ip->m_multipart) {
862 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
863 pp->m_nextpart = *np;
864 } else
865 ip->m_multipart = *np;
868 static void
869 endpart(struct mimepart **np, off_t xoffs, long lines)
871 off_t offs;
873 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
874 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
875 (*np)->m_lines = (*np)->m_xlines = lines;
876 *np = NULL;
879 static void
880 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
881 int level)
883 int c, lastc = '\n';
884 size_t count;
885 FILE *ibuf;
886 off_t offs;
887 struct mimepart *np;
888 long lines;
890 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
891 return;
892 count = ip->m_size;
893 lines = ip->m_lines;
894 while (count && ((c = getc(ibuf)) != EOF)) {
895 count--;
896 if (c == '\n') {
897 lines--;
898 if (lastc == '\n')
899 break;
901 lastc = c;
903 offs = ftell(ibuf);
904 np = csalloc(1, sizeof *np);
905 np->m_flag = MNOFROM;
906 np->m_have = HAVE_HEADER|HAVE_BODY;
907 np->m_block = mailx_blockof(offs);
908 np->m_offset = mailx_offsetof(offs);
909 np->m_size = np->m_xsize = count;
910 np->m_lines = np->m_xlines = lines;
911 np->m_partstring = ip->m_partstring;
912 np->m_parent = ip;
913 ip->m_multipart = np;
914 substdate((struct message *)np);
915 np->m_from = fakefrom((struct message *)np);
916 parsepart(zmp, np, pf, level+1);
919 static void
920 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
921 int level)
923 struct message m, *xmp;
924 struct mimepart *np;
925 char *to, *cc;
927 memcpy(&m, ip, sizeof m);
928 to = hfield1("to", zmp);
929 cc = hfield1("cc", zmp);
930 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
931 np = csalloc(1, sizeof *np);
932 np->m_flag = xmp->m_flag;
933 np->m_have = xmp->m_have;
934 np->m_block = xmp->m_block;
935 np->m_offset = xmp->m_offset;
936 np->m_size = xmp->m_size;
937 np->m_xsize = xmp->m_xsize;
938 np->m_lines = xmp->m_lines;
939 np->m_xlines = xmp->m_xlines;
940 np->m_partstring = ip->m_partstring;
941 if (parsepart(zmp, np, pf, level+1) == OKAY) {
942 np->m_parent = ip;
943 ip->m_multipart = np;
948 static size_t
949 out(char *buf, size_t len, FILE *fp,
950 enum conversion convert, enum sendaction action,
951 char *prefix, size_t prefixlen, off_t *stats,
952 char **restp, size_t *restsizep)
954 size_t sz, n;
955 char *cp;
956 long lines;
958 sz = 0;
959 if (action == SEND_MBOX || action == SEND_DECRYPT) {
960 cp = buf;
961 n = len;
962 while (n && cp[0] == '>')
963 cp++, n--;
964 if (n >= 5 && cp[0] == 'F' && cp[1] == 'r' && cp[2] == 'o' &&
965 cp[3] == 'm' && cp[4] == ' ') {
966 putc('>', fp);
967 sz++;
970 sz += mime_write(buf, len, fp,
971 action == SEND_MBOX ? CONV_NONE : convert,
972 action == SEND_TODISP || action == SEND_TODISP_ALL ||
973 action == SEND_QUOTE ||
974 action == SEND_QUOTE_ALL ?
975 TD_ISPR|TD_ICONV :
976 action == SEND_TOSRCH || action == SEND_TOPIPE ?
977 TD_ICONV :
978 action == SEND_TOFLTR ?
979 TD_DELCTRL :
980 action == SEND_SHOW ?
981 TD_ISPR : TD_NONE,
982 prefix, prefixlen,
983 restp, restsizep);
984 lines = 0;
985 if (stats && stats[0] != -1) {
986 for (cp = buf; cp < &buf[sz]; cp++)
987 if (*cp == '\n')
988 lines++;
990 addstats(stats, lines, sz);
991 return sz;
994 static void
995 addstats(off_t *stats, off_t lines, off_t bytes)
997 if (stats) {
998 if (stats[0] >= 0)
999 stats[0] += lines;
1000 stats[1] += bytes;
1005 * Get a file for an attachment.
1007 static FILE *
1008 newfile(struct mimepart *ip, int *ispipe, sighandler_type *oldpipe)
1010 char *f = ip->m_filename;
1011 struct str in, out;
1012 FILE *fp;
1014 *ispipe = 0;
1015 if (f != NULL && f != (char *)-1) {
1016 in.s = f;
1017 in.l = strlen(f);
1018 mime_fromhdr(&in, &out, TD_ISPR);
1019 memcpy(f, out.s, out.l);
1020 *(f + out.l) = '\0';
1021 free(out.s);
1023 if (value("interactive") != NULL) {
1024 printf("Enter filename for part %s (%s)",
1025 ip->m_partstring ? ip->m_partstring : "?",
1026 ip->m_ct_type_plain);
1027 f = readtty(catgets(catd, CATSET, 173, ": "),
1028 f != (char *)-1 ? f : NULL);
1030 if (f == NULL || f == (char *)-1)
1031 return NULL;
1033 if (*f == '|') {
1034 char *cp;
1035 cp = value("SHELL");
1036 if (cp == NULL)
1037 cp = SHELL;
1038 fp = Popen(f+1, "w", cp, 1);
1039 if (fp == NULL) {
1040 perror(f);
1041 fp = stdout;
1042 } else {
1043 *oldpipe = safe_signal(SIGPIPE, brokpipe);
1044 *ispipe = 1;
1046 } else {
1047 if ((fp = Fopen(f, "w")) == NULL)
1048 fprintf(stderr, "Cannot open %s\n", f);
1050 return fp;
1053 static char *
1054 getpipecmd(char *content)
1056 char *penv, *cp, *cq, *pipecmd;
1058 if (content == NULL)
1059 return NULL;
1060 penv = ac_alloc(strlen(content) + 6);
1061 strcpy(penv, "pipe-");
1062 cp = &penv[5];
1063 cq = content;
1065 *cp++ = lowerconv(*cq & 0377);
1066 while (*cq++);
1067 pipecmd = value(penv);
1068 ac_free(penv);
1069 return pipecmd;
1072 static FILE *
1073 getpipefile(char *pipecmd, FILE **qbuf, int quote)
1075 char *shell;
1076 FILE *rbuf = *qbuf;
1078 if (pipecmd != NULL) {
1079 if (quote) {
1080 char *tempPipe;
1082 if ((*qbuf = Ftemp(&tempPipe, "Rp", "w+", 0600, 1))
1083 == NULL) {
1084 perror(catgets(catd, CATSET, 173, "tmpfile"));
1085 *qbuf = rbuf;
1087 unlink(tempPipe);
1088 Ftfree(&tempPipe);
1090 if ((shell = value("SHELL")) == NULL)
1091 shell = SHELL;
1092 if ((rbuf = Popen(pipecmd, "W", shell, fileno(*qbuf)))
1093 == NULL) {
1094 perror(pipecmd);
1095 } else {
1096 fflush(*qbuf);
1097 if (*qbuf != stdout)
1098 fflush(stdout);
1101 return rbuf;
1104 static void
1105 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
1106 char *prefix, size_t prefixlen, off_t *stats)
1108 char *line = NULL;
1109 size_t linesize = 0, linelen, sz, count;
1111 fflush(pipebuf);
1112 rewind(pipebuf);
1113 count = fsize(pipebuf);
1114 while (fgetline(&line, &linesize, &count, &linelen, pipebuf, 0)
1115 != NULL) {
1116 sz = prefixwrite(line, sizeof *line, linelen, outbuf,
1117 prefix, prefixlen);
1118 if (outbuf == origobuf)
1119 addstats(stats, 1, sz);
1121 if (line)
1122 free(line);
1123 fclose(pipebuf);
1127 * Output a reasonable looking status field.
1129 static void
1130 statusput(const struct message *mp, FILE *obuf, char *prefix, off_t *stats)
1132 char statout[3];
1133 char *cp = statout;
1135 if (mp->m_flag & MREAD)
1136 *cp++ = 'R';
1137 if ((mp->m_flag & MNEW) == 0)
1138 *cp++ = 'O';
1139 *cp = 0;
1140 if (statout[0])
1141 fprintf(obuf, "%sStatus: %s\n",
1142 prefix == NULL ? "" : prefix, statout);
1143 addstats(stats, 1, (prefix ? strlen(prefix) : 0) + 9 + cp - statout);
1146 static void
1147 xstatusput(const struct message *mp, FILE *obuf, char *prefix, off_t *stats)
1149 char xstatout[4];
1150 char *xp = xstatout;
1152 if (mp->m_flag & MFLAGGED)
1153 *xp++ = 'F';
1154 if (mp->m_flag & MANSWERED)
1155 *xp++ = 'A';
1156 if (mp->m_flag & MDRAFTED)
1157 *xp++ = 'T';
1158 *xp = 0;
1159 if (xstatout[0])
1160 fprintf(obuf, "%sX-Status: %s\n",
1161 prefix == NULL ? "" : prefix, xstatout);
1162 addstats(stats, 1, (prefix ? strlen(prefix) : 0) + 11 + xp - xstatout);
1165 static void
1166 put_from_(FILE *fp, struct mimepart *ip)
1168 time_t now;
1170 if (ip && ip->m_from)
1171 fprintf(fp, "From %s %s\n", ip->m_from, fakedate(ip->m_time));
1172 else {
1173 time(&now);
1174 fprintf(fp, "From %s %s", myname, ctime(&now));
1179 * This is fgetline for mbox lines.
1181 char *
1182 foldergets(char **s, size_t *size, size_t *count, size_t *llen, FILE *stream)
1184 char *p, *top;
1186 if ((p = fgetline(s, size, count, llen, stream, 0)) == NULL)
1187 return NULL;
1188 if (*p == '>') {
1189 p++;
1190 while (*p == '>') p++;
1191 if (strncmp(p, "From ", 5) == 0) {
1192 /* we got a masked From line */
1193 top = &(*s)[*llen];
1194 p = *s;
1196 p[0] = p[1];
1197 while (++p < top);
1198 (*llen)--;
1201 return *s;