Reorder and "style-up" names.c..
[s-mailx.git] / send.c
blob1e6c686781f496f4cfd11cc3bc3431de34d25ee9
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 enum parseflags {
53 PARSE_DEFAULT = 0,
54 PARSE_DECRYPT = 01,
55 PARSE_PARTS = 02
58 static void onpipe(int signo);
59 extern void brokpipe(int signo);
60 static int sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
61 struct ignoretab *doign, char *prefix, size_t prefixlen,
62 enum sendaction action, off_t *stats, int level);
63 static struct mimepart *parsemsg(struct message *mp, enum parseflags pf);
64 static enum okay parsepart(struct message *zmp, struct mimepart *ip,
65 enum parseflags pf, int level);
66 static void parsemultipart(struct message *zmp, struct mimepart *ip,
67 enum parseflags pf, int level);
68 static void newpart(struct mimepart *ip, struct mimepart **np, off_t offs,
69 int *part);
70 static void endpart(struct mimepart **np, off_t xoffs, long lines);
71 static void parse822(struct message *zmp, struct mimepart *ip,
72 enum parseflags pf, int level);
73 static void parsepkcs7(struct message *zmp, struct mimepart *ip,
74 enum parseflags pf, int level);
75 static size_t out(char *buf, size_t len, FILE *fp,
76 enum conversion convert, enum sendaction action,
77 char *prefix, size_t prefixlen, off_t *stats,
78 char **restp, size_t *restsizep);
79 static void addstats(off_t *stats, off_t lines, off_t bytes);
80 static FILE *newfile(struct mimepart *ip, int *ispipe,
81 sighandler_type volatile*oldpipe);
82 static char *getpipecmd(char *content);
83 static FILE *getpipefile(char *cmd, FILE **qbuf, int quote);
84 static void pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
85 char *prefix, size_t prefixlen, off_t *stats);
86 static void statusput(const struct message *mp, FILE *obuf,
87 char *prefix, size_t prefixlen, off_t *stats);
88 static void xstatusput(const struct message *mp, FILE *obuf,
89 char *prefix, size_t prefixlen, off_t *stats);
90 static void put_from_(FILE *fp, struct mimepart *ip, off_t *stats);
92 static sigjmp_buf pipejmp;
94 /*ARGSUSED*/
95 static void
96 onpipe(int signo)
98 (void)signo;
99 siglongjmp(pipejmp, 1);
103 * Send message described by the passed pointer to the
104 * passed output buffer. Return -1 on error.
105 * Adjust the status: field if need be.
106 * If doign is given, suppress ignored header fields.
107 * prefix is a string to prepend to each output line.
108 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
109 * stats[0] is line count, stats[1] is character count. stats may be NULL.
110 * Note that stats[0] is valid for SEND_MBOX only.
113 send(struct message *mp, FILE *obuf, struct ignoretab *doign,
114 char *prefix, enum sendaction action, off_t *stats)
116 size_t prefixlen, count, sz, i;
117 FILE *ibuf;
118 int c;
119 enum parseflags pf;
120 struct mimepart *ip;
122 if (mp == dot && action != SEND_TOSRCH && action != SEND_TOFLTR)
123 did_print_dot = 1;
124 if (stats)
125 stats[0] = stats[1] = 0;
126 prefixlen = (prefix != NULL) ? strlen(prefix) : 0;
129 * First line is the From_ line, so no headers there to worry about.
131 if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
132 return (-1);
133 count = mp->m_size;
134 sz = 0;
135 if (mp->m_flag & MNOFROM) {
136 if (doign != allignore && doign != fwdignore &&
137 action != SEND_RFC822)
138 sz = fprintf(obuf, "%.*sFrom %s %s\n",
139 (int)prefixlen, prefixlen ? prefix :"",
140 fakefrom(mp), fakedate(mp->m_time));
141 } else {
142 if (prefixlen && doign != allignore && doign != fwdignore &&
143 action != SEND_RFC822) {
144 i = fwrite(prefix, sizeof *prefix, prefixlen, obuf);
145 if (i != prefixlen)
146 return (-1);
147 sz += i;
149 while (count && (c = getc(ibuf)) != EOF) {
150 if (doign != allignore && doign != fwdignore &&
151 action != SEND_RFC822) {
152 putc(c, obuf);
153 sz++;
155 count--;
156 if (c == '\n')
157 break;
160 if (sz)
161 addstats(stats, 1, sz);
162 pf = 0;
163 if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
164 pf |= PARSE_DECRYPT|PARSE_PARTS;
165 if ((ip = parsemsg(mp, pf)) == NULL)
166 return (-1);
167 return (sendpart(mp, ip, obuf, doign, prefix, prefixlen, action, stats,
168 0));
171 static int
172 sendpart(struct message *zmp, struct mimepart *ip, FILE *obuf,
173 struct ignoretab *doign, char *prefix, size_t prefixlen,
174 enum sendaction action, off_t *volatile stats, int level)
176 char *line = NULL, *cp, *cp2, *start, *tcs, *pipecmd = NULL, *rest;
177 size_t linesize = 0, linelen, count, len, restsize;
178 int dostat, infld = 0, ignoring = 1, isenc, c, rt = 0, eof, ispipe = 0;
179 struct mimepart *np;
180 FILE *volatile ibuf = NULL, *volatile pbuf = obuf,
181 *volatile qbuf = obuf, *origobuf = obuf;
182 enum conversion volatile convert;
183 sighandler_type volatile oldpipe = SIG_DFL;
184 long lineno = 0;
186 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
187 action != SEND_MBOX && action != SEND_RFC822 &&
188 action != SEND_SHOW)
189 goto skip;
190 dostat = 0;
191 if (level == 0) {
192 if (doign != NULL) {
193 if (!is_ign("status", 6, doign))
194 dostat |= 1;
195 if (!is_ign("x-status", 8, doign))
196 dostat |= 2;
197 } else
198 dostat = 3;
200 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
201 return -1;
202 count = ip->m_size;
203 if (ip->m_mimecontent == MIME_DISCARD)
204 goto skip;
205 if ((ip->m_flag&MNOFROM) == 0)
206 while (count && (c = getc(ibuf)) != EOF) {
207 count--;
208 if (c == '\n')
209 break;
211 isenc = 0;
212 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
213 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
214 action == SEND_TOSRCH || action == SEND_TOFLTR ?
215 CONV_FROMHDR : CONV_NONE;
218 * Normally headers included in "Content-Type: message/rfc822" messages
219 * will not show up in replies to the encapsulating envelope.
220 * This is nail(1) specific and thus may be configured differently.
222 if (ip->m_mimecontent == MIME_TEXT_PLAIN && ip->m_parent != NULL &&
223 ip->m_parent->m_mimecontent == MIME_822 &&
224 value("rfc822-show-all"))
225 goto skip;
227 /* Work the headers */
228 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
229 lineno++;
230 if (line[0] == '\n') {
232 * If line is blank, we've reached end of
233 * headers, so force out status: field
234 * and note that we are no longer in header
235 * fields
237 if (dostat & 1)
238 statusput(zmp, obuf, prefix, prefixlen, stats);
239 if (dostat & 2)
240 xstatusput(zmp, obuf, prefix, prefixlen, stats);
241 if (doign != allignore)
242 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
243 prefix, prefixlen, stats,
244 NULL, NULL);
245 break;
247 isenc &= ~1;
248 if (infld && blankchar(line[0]&0377)) {
250 * If this line is a continuation (via space or tab)
251 * of a previous header field, determine if the start
252 * of the line is a MIME encoded word.
254 if (isenc & 2) {
255 for (cp = line; blankchar(*cp&0377); cp++);
256 if (cp > line && linelen - (cp - line) > 8 &&
257 cp[0] == '=' && cp[1] == '?')
258 isenc |= 1;
260 } else {
262 * Pick up the header field if we have one.
264 for (cp = line; (c = *cp&0377) && c != ':' &&
265 !spacechar(c); cp++);
266 cp2 = cp;
267 while (spacechar(*cp&0377))
268 cp++;
269 if (cp[0] != ':' && level == 0 && lineno == 1) {
271 * Not a header line, force out status:
272 * This happens in uucp style mail where
273 * there are no headers at all.
275 if (dostat & 1)
276 statusput(zmp, obuf, prefix, prefixlen,
277 stats);
278 if (dostat & 2)
279 xstatusput(zmp, obuf, prefix,
280 prefixlen, stats);
281 if (doign != allignore)
282 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
283 prefix, prefixlen, stats,
284 NULL, NULL);
285 break;
288 * If it is an ignored field and
289 * we care about such things, skip it.
291 c = *cp2;
292 *cp2 = 0; /* temporarily null terminate */
293 if (doign && is_ign(line, cp2 - line, doign))
294 ignoring = 1;
295 else if (asccasecmp(line, "status") == 0) {
297 * If the field is "status," go compute
298 * and print the real Status: field
300 if (dostat & 1) {
301 statusput(zmp, obuf, prefix, prefixlen,
302 stats);
303 dostat &= ~1;
304 ignoring = 1;
306 } else if (asccasecmp(line, "x-status") == 0) {
308 * If the field is "status," go compute
309 * and print the real Status: field
311 if (dostat & 2) {
312 xstatusput(zmp, obuf, prefix,
313 prefixlen, stats);
314 dostat &= ~2;
315 ignoring = 1;
317 } else
318 ignoring = 0;
319 *cp2 = c;
320 infld = 1;
323 * Determine if the end of the line is a MIME encoded word.
325 isenc &= ~2;
326 if (count && (c = getc(ibuf)) != EOF) {
327 if (blankchar(c)) {
328 if (linelen > 0 && line[linelen-1] == '\n')
329 cp = &line[linelen-2];
330 else
331 cp = &line[linelen-1];
332 while (cp >= line && whitechar(*cp&0377))
333 cp++;
334 if (cp - line > 8 && cp[0] == '=' &&
335 cp[-1] == '?')
336 isenc |= 2;
338 ungetc(c, ibuf);
340 if (!ignoring) {
341 start = line;
342 len = linelen;
343 if (action == SEND_TODISP ||
344 action == SEND_TODISP_ALL ||
345 action == SEND_QUOTE ||
346 action == SEND_QUOTE_ALL ||
347 action == SEND_TOSRCH ||
348 action == SEND_TOFLTR) {
350 * Strip blank characters if two MIME-encoded
351 * words follow on continuing lines.
353 if (isenc & 1)
354 while (len>0&&blankchar(*start&0377)) {
355 start++;
356 len--;
358 if (isenc & 2)
359 if (len > 0 && start[len-1] == '\n')
360 len--;
361 while (len > 0 && blankchar(start[len-1]&0377))
362 len--;
364 out(start, len, obuf, convert,
365 action, prefix, prefixlen, stats,
366 NULL, NULL);
367 if (ferror(obuf)) {
368 free(line);
369 return -1;
373 free(line);
374 line = NULL;
376 skip: switch (ip->m_mimecontent) {
377 case MIME_822:
378 switch (action) {
379 case SEND_TOFLTR:
380 putc('\0', obuf);
381 /*FALLTHRU*/
382 case SEND_TODISP:
383 case SEND_TODISP_ALL:
384 case SEND_QUOTE:
385 case SEND_QUOTE_ALL:
386 if (! value("rfc822-no-body-from_")) {
387 if (prefixlen && value("rfc822-show-all")) {
388 size_t i = fwrite(prefix,
389 sizeof *prefix, prefixlen,
390 obuf);
391 if (i == prefixlen)
392 addstats(stats, 0, i);
394 put_from_(obuf, ip->m_multipart, stats);
396 /*FALLTHRU*/
397 case SEND_TOSRCH:
398 case SEND_DECRYPT:
399 goto multi;
400 case SEND_TOFILE:
401 case SEND_TOPIPE:
402 put_from_(obuf, ip->m_multipart, stats);
403 /*FALLTHRU*/
404 case SEND_MBOX:
405 case SEND_RFC822:
406 case SEND_SHOW:
407 break;
409 break;
410 case MIME_TEXT_HTML:
411 if (action == SEND_TOFLTR)
412 putc('\b', obuf);
413 /*FALLTHRU*/
414 case MIME_TEXT:
415 case MIME_TEXT_PLAIN:
416 switch (action) {
417 case SEND_TODISP:
418 case SEND_TODISP_ALL:
419 case SEND_QUOTE:
420 case SEND_QUOTE_ALL:
421 pipecmd = getpipecmd(ip->m_ct_type_plain);
422 /*FALLTHRU*/
423 default:
424 break;
426 break;
427 case MIME_DISCARD:
428 if (action != SEND_DECRYPT)
429 return rt;
430 break;
431 case MIME_PKCS7:
432 if (action != SEND_MBOX && action != SEND_RFC822 &&
433 action != SEND_SHOW && ip->m_multipart)
434 goto multi;
435 /*FALLTHRU*/
436 default:
437 switch (action) {
438 case SEND_TODISP:
439 case SEND_TODISP_ALL:
440 case SEND_QUOTE:
441 case SEND_QUOTE_ALL:
442 if ((pipecmd = getpipecmd(ip->m_ct_type_plain)) != NULL)
443 break;
444 if (level == 0 && count) {
445 cp = "[Binary content]\n\n";
446 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
447 prefix, prefixlen, stats,
448 NULL, NULL);
450 /*FALLTHRU*/
451 case SEND_TOFLTR:
452 return rt;
453 case SEND_TOFILE:
454 case SEND_TOPIPE:
455 case SEND_TOSRCH:
456 case SEND_DECRYPT:
457 case SEND_MBOX:
458 case SEND_RFC822:
459 case SEND_SHOW:
460 break;
462 break;
463 case MIME_ALTERNATIVE:
464 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
465 value("print-alternatives") == NULL)
466 for (np = ip->m_multipart; np; np = np->m_nextpart)
467 if (np->m_mimecontent == MIME_TEXT_PLAIN) {
468 if (sendpart(zmp, np, obuf,
469 doign, prefix,
470 prefixlen,
471 action, stats,
472 level+1) < 0)
473 return -1;
474 return rt;
476 /*FALLTHRU*/
477 case MIME_MULTI:
478 case MIME_DIGEST:
479 switch (action) {
480 case SEND_TODISP:
481 case SEND_TODISP_ALL:
482 case SEND_QUOTE:
483 case SEND_QUOTE_ALL:
484 case SEND_TOFILE:
485 case SEND_TOPIPE:
486 case SEND_TOSRCH:
487 case SEND_TOFLTR:
488 case SEND_DECRYPT:
489 multi:
490 if ((action == SEND_TODISP ||
491 action == SEND_TODISP_ALL) &&
492 ip->m_multipart != NULL &&
493 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
494 ip->m_multipart->m_nextpart == NULL) {
495 cp = "[Missing multipart boundary - "
496 "use \"show\" to display "
497 "the raw message]\n\n";
498 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
499 prefix, prefixlen, stats,
500 NULL, NULL);
502 for (np = ip->m_multipart; np; np = np->m_nextpart) {
503 if (np->m_mimecontent == MIME_DISCARD &&
504 action != SEND_DECRYPT)
505 continue;
506 switch (action) {
507 case SEND_TOFILE:
508 if (np->m_partstring &&
509 strcmp(np->m_partstring,
510 "1") == 0)
511 break;
512 stats = NULL;
513 if ((obuf = newfile(np, &ispipe,
514 &oldpipe))
515 == NULL)
516 continue;
517 break;
518 case SEND_TODISP:
519 case SEND_TODISP_ALL:
520 case SEND_QUOTE_ALL:
521 if ((ip->m_mimecontent == MIME_MULTI ||
522 ip->m_mimecontent ==
523 MIME_ALTERNATIVE ||
524 ip->m_mimecontent ==
525 MIME_DIGEST) &&
526 np->m_partstring) {
527 len = strlen(np->m_partstring) +
529 cp = ac_alloc(len);
530 snprintf(cp, len,
531 "%sPart %s:\n", level ||
532 strcmp(np->m_partstring,
533 "1") ?
534 "\n" : "",
535 np->m_partstring);
536 out(cp, strlen(cp), obuf,
537 CONV_NONE, SEND_MBOX,
538 prefix, prefixlen,
539 stats,
540 NULL, NULL);
541 ac_free(cp);
543 break;
544 case SEND_TOFLTR:
545 putc('\0', obuf);
546 /*FALLTHRU*/
547 case SEND_MBOX:
548 case SEND_RFC822:
549 case SEND_SHOW:
550 case SEND_TOSRCH:
551 case SEND_QUOTE:
552 case SEND_DECRYPT:
553 case SEND_TOPIPE:
554 break;
556 if (sendpart(zmp, np, obuf,
557 doign, prefix, prefixlen,
558 action, stats, level+1) < 0)
559 rt = -1;
560 else if (action == SEND_QUOTE)
561 break;
562 if (action == SEND_TOFILE && obuf != origobuf) {
563 if (ispipe == 0)
564 Fclose(obuf);
565 else {
566 safe_signal(SIGPIPE, SIG_IGN);
567 Pclose(obuf);
568 safe_signal(SIGPIPE, oldpipe);
572 return rt;
573 case SEND_MBOX:
574 case SEND_RFC822:
575 case SEND_SHOW:
576 break;
580 * Copy out message body
582 if (doign == allignore && level == 0) /* skip final blank line */
583 count--;
584 switch (ip->m_mimeenc) {
585 case MIME_BIN:
586 if (stats)
587 stats[0] = -1;
588 /*FALLTHRU*/
589 case MIME_7B:
590 case MIME_8B:
591 convert = CONV_NONE;
592 break;
593 case MIME_QP:
594 convert = CONV_FROMQP;
595 break;
596 case MIME_B64:
597 switch (ip->m_mimecontent) {
598 case MIME_TEXT:
599 case MIME_TEXT_PLAIN:
600 case MIME_TEXT_HTML:
601 convert = CONV_FROMB64_T;
602 break;
603 default:
604 convert = CONV_FROMB64;
606 break;
607 default:
608 convert = CONV_NONE;
610 if (action == SEND_DECRYPT || action == SEND_MBOX ||
611 action == SEND_RFC822 || action == SEND_SHOW)
612 convert = CONV_NONE;
613 tcs = gettcharset();
614 #ifdef HAVE_ICONV
615 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
616 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
617 action == SEND_TOSRCH) &&
618 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
619 ip->m_mimecontent == MIME_TEXT_HTML ||
620 ip->m_mimecontent == MIME_TEXT)) {
621 if (iconvd != (iconv_t)-1)
622 iconv_close(iconvd);
623 if (asccasecmp(tcs, ip->m_charset) &&
624 asccasecmp(us_ascii, ip->m_charset))
625 iconvd = iconv_open_ft(tcs, ip->m_charset);
626 else
627 iconvd = (iconv_t)-1;
629 #endif /* HAVE_ICONV */
630 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
631 action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
632 pipecmd != NULL) {
633 qbuf = obuf;
634 pbuf = getpipefile(pipecmd, (FILE**)&qbuf,
635 action == SEND_QUOTE || action == SEND_QUOTE_ALL);
636 action = SEND_TOPIPE;
637 if (pbuf != qbuf) {
638 oldpipe = safe_signal(SIGPIPE, onpipe);
639 if (sigsetjmp(pipejmp, 1))
640 goto end;
642 } else
643 pbuf = qbuf = obuf;
644 eof = 0;
645 while (!eof && foldergets(&line, &linesize, &count, &linelen, ibuf)) {
646 lineno++;
647 while (convert == CONV_FROMQP && linelen >= 2 &&
648 line[linelen-2] == '=') {
649 char *line2;
650 size_t linesize2, linelen2;
651 nextl:
652 line2 = NULL;
653 linesize2 = 0;
654 if (foldergets(&line2, &linesize2, &count, &linelen2,
655 ibuf) == NULL) {
656 eof = 1;
657 break;
659 if (linelen + linelen2 + 1 > linesize)
660 line = srealloc(line, linesize = linelen +
661 linelen2 + 1);
662 memcpy(&line[linelen], line2, linelen2+1);
663 linelen += linelen2;
664 free(line2);
666 rest = NULL;
667 restsize = 0;
668 out(line, linelen, pbuf, convert, action,
669 pbuf == origobuf ? prefix : NULL,
670 pbuf == origobuf ? prefixlen : 0,
671 pbuf == origobuf ? stats : NULL,
672 eof ? NULL : &rest, eof ? NULL : &restsize);
673 if (ferror(pbuf)) {
674 rt = -1;
675 break;
677 if (restsize) {
678 if (line != rest)
679 memmove(line, rest, restsize);
680 linelen = restsize;
681 goto nextl;
684 end: free(line);
685 if (pbuf != qbuf) {
686 safe_signal(SIGPIPE, SIG_IGN);
687 Pclose(pbuf);
688 safe_signal(SIGPIPE, oldpipe);
689 if (qbuf != obuf)
690 pipecpy(qbuf, obuf, origobuf, prefix, prefixlen, stats);
692 #ifdef HAVE_ICONV
693 if (iconvd != (iconv_t)-1) {
694 iconv_close(iconvd);
695 iconvd = (iconv_t)-1;
697 #endif
698 return rt;
701 static struct mimepart *
702 parsemsg(struct message *mp, enum parseflags pf)
704 struct mimepart *ip;
706 ip = csalloc(1, sizeof *ip);
707 ip->m_flag = mp->m_flag;
708 ip->m_have = mp->m_have;
709 ip->m_block = mp->m_block;
710 ip->m_offset = mp->m_offset;
711 ip->m_size = mp->m_size;
712 ip->m_xsize = mp->m_xsize;
713 ip->m_lines = mp->m_lines;
714 ip->m_xlines = mp->m_lines;
715 if (parsepart(mp, ip, pf, 0) != OKAY)
716 return NULL;
717 return ip;
720 static enum okay
721 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
722 int level)
724 char *cp;
726 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
727 if (ip->m_ct_type != NULL) {
728 ip->m_ct_type_plain = savestr(ip->m_ct_type);
729 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
730 *cp = '\0';
731 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
732 ip->m_ct_type_plain = "message/rfc822";
733 else
734 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 if (! value("rfc822-no-body-from_")) {
915 substdate((struct message *)np);
916 np->m_from = fakefrom((struct message *)np);
918 parsepart(zmp, np, pf, level+1);
921 static void
922 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
923 int level)
925 struct message m, *xmp;
926 struct mimepart *np;
927 char *to, *cc;
929 memcpy(&m, ip, sizeof m);
930 to = hfield1("to", zmp);
931 cc = hfield1("cc", zmp);
932 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
933 np = csalloc(1, sizeof *np);
934 np->m_flag = xmp->m_flag;
935 np->m_have = xmp->m_have;
936 np->m_block = xmp->m_block;
937 np->m_offset = xmp->m_offset;
938 np->m_size = xmp->m_size;
939 np->m_xsize = xmp->m_xsize;
940 np->m_lines = xmp->m_lines;
941 np->m_xlines = xmp->m_xlines;
942 np->m_partstring = ip->m_partstring;
943 if (parsepart(zmp, np, pf, level+1) == OKAY) {
944 np->m_parent = ip;
945 ip->m_multipart = np;
950 static size_t
951 out(char *buf, size_t len, FILE *fp,
952 enum conversion convert, enum sendaction action,
953 char *prefix, size_t prefixlen, off_t *stats,
954 char **restp, size_t *restsizep)
956 size_t sz, n;
957 char *cp;
958 long lines;
960 sz = 0;
961 if (action == SEND_MBOX || action == SEND_DECRYPT) {
962 cp = buf;
963 n = len;
964 while (n && cp[0] == '>')
965 cp++, n--;
966 if (n >= 5 && cp[0] == 'F' && cp[1] == 'r' && cp[2] == 'o' &&
967 cp[3] == 'm' && cp[4] == ' ') {
968 putc('>', fp);
969 sz++;
972 sz += mime_write(buf, len, fp,
973 action == SEND_MBOX ? CONV_NONE : convert,
974 action == SEND_TODISP || action == SEND_TODISP_ALL ||
975 action == SEND_QUOTE ||
976 action == SEND_QUOTE_ALL ?
977 TD_ISPR|TD_ICONV :
978 action == SEND_TOSRCH || action == SEND_TOPIPE ?
979 TD_ICONV :
980 action == SEND_TOFLTR ?
981 TD_DELCTRL :
982 action == SEND_SHOW ?
983 TD_ISPR : TD_NONE,
984 prefix, prefixlen,
985 restp, restsizep);
986 lines = 0;
987 if (stats && stats[0] != -1) {
988 for (cp = buf; cp < &buf[sz]; cp++)
989 if (*cp == '\n')
990 lines++;
992 addstats(stats, lines, sz);
993 return sz;
996 static void
997 addstats(off_t *stats, off_t lines, off_t bytes)
999 if (stats) {
1000 if (stats[0] >= 0)
1001 stats[0] += lines;
1002 stats[1] += bytes;
1007 * Get a file for an attachment.
1009 static FILE *
1010 newfile(struct mimepart *ip, int *ispipe, sighandler_type volatile*oldpipe)
1012 char *f = ip->m_filename;
1013 struct str in, out;
1014 FILE *fp;
1016 *ispipe = 0;
1017 if (f != NULL && f != (char *)-1) {
1018 in.s = f;
1019 in.l = strlen(f);
1020 mime_fromhdr(&in, &out, TD_ISPR);
1021 memcpy(f, out.s, out.l);
1022 *(f + out.l) = '\0';
1023 free(out.s);
1026 if (value("interactive") != NULL) {
1027 char *f2, *f3;
1028 jgetname: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1029 ip->m_partstring ? ip->m_partstring : "?",
1030 ip->m_ct_type_plain);
1031 f2 = readtty(": ", f != (char *)-1 ? f : NULL);
1032 if (f2 == NULL || *f2 == '\0') {
1033 fprintf(stderr, tr(279, "... skipping this\n"));
1034 return (NULL);
1035 } else if (*f2 == '|')
1036 /* Pipes are expanded by the shell */
1037 f = f2;
1038 else if ((f3 = file_expand(f2)) == NULL)
1039 /* (Error message written by file_expand()) */
1040 goto jgetname;
1041 else
1042 f = f3;
1044 if (f == NULL || f == (char *)-1)
1045 return NULL;
1047 if (*f == '|') {
1048 char *cp;
1049 cp = value("SHELL");
1050 if (cp == NULL)
1051 cp = SHELL;
1052 fp = Popen(f + 1, "w", cp, 1);
1053 if (fp == NULL) {
1054 perror(f);
1055 fp = stdout;
1056 } else {
1057 *oldpipe = safe_signal(SIGPIPE, brokpipe);
1058 *ispipe = 1;
1060 } else {
1061 if ((fp = Fopen(f, "w")) == NULL)
1062 fprintf(stderr, tr(176, "Cannot open %s\n"), f);
1064 return (fp);
1067 static char *
1068 getpipecmd(char *content)
1070 char *penv, *cp, *cq, *pipecmd;
1072 if (content == NULL)
1073 return NULL;
1074 penv = ac_alloc(strlen(content) + 6);
1075 strcpy(penv, "pipe-");
1076 cp = &penv[5];
1077 cq = content;
1079 *cp++ = lowerconv(*cq & 0377);
1080 while (*cq++);
1081 pipecmd = value(penv);
1082 ac_free(penv);
1083 return pipecmd;
1086 static FILE *
1087 getpipefile(char *pipecmd, FILE **qbuf, int quote)
1089 char *shell;
1090 FILE *rbuf = *qbuf;
1092 if (pipecmd != NULL) {
1093 if (quote) {
1094 char *tempPipe;
1096 if ((*qbuf = Ftemp(&tempPipe, "Rp", "w+", 0600, 1))
1097 == NULL) {
1098 perror(catgets(catd, CATSET, 173, "tmpfile"));
1099 *qbuf = rbuf;
1101 unlink(tempPipe);
1102 Ftfree(&tempPipe);
1104 if ((shell = value("SHELL")) == NULL)
1105 shell = SHELL;
1106 if ((rbuf = Popen(pipecmd, "W", shell, fileno(*qbuf)))
1107 == NULL) {
1108 perror(pipecmd);
1109 } else {
1110 fflush(*qbuf);
1111 if (*qbuf != stdout)
1112 fflush(stdout);
1115 return rbuf;
1118 static void
1119 pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
1120 char *prefix, size_t prefixlen, off_t *stats)
1122 char *line = NULL;
1123 size_t linesize = 0, linelen, sz, count;
1125 fflush(pipebuf);
1126 rewind(pipebuf);
1127 count = fsize(pipebuf);
1128 while (fgetline(&line, &linesize, &count, &linelen, pipebuf, 0)
1129 != NULL) {
1130 sz = prefixwrite(line, sizeof *line, linelen, outbuf,
1131 prefix, prefixlen);
1132 if (outbuf == origobuf)
1133 addstats(stats, 1, sz);
1135 if (line)
1136 free(line);
1137 fclose(pipebuf);
1141 * Output a reasonable looking status field.
1143 static void
1144 statusput(const struct message *mp, FILE *obuf, char *prefix, size_t prefixlen,
1145 off_t *stats)
1147 char statout[3];
1148 char *cp = statout;
1150 if (mp->m_flag & MREAD)
1151 *cp++ = 'R';
1152 if ((mp->m_flag & MNEW) == 0)
1153 *cp++ = 'O';
1154 *cp = 0;
1155 if (statout[0])
1156 fprintf(obuf, "%.*sStatus: %s\n",
1157 (int)prefixlen, (prefixlen ? prefix : ""), statout);
1158 addstats(stats, 1, prefixlen + 9 + cp - statout);
1161 static void
1162 xstatusput(const struct message *mp, FILE *obuf, char *prefix,
1163 size_t prefixlen, off_t *stats)
1165 char xstatout[4];
1166 char *xp = xstatout;
1168 if (mp->m_flag & MFLAGGED)
1169 *xp++ = 'F';
1170 if (mp->m_flag & MANSWERED)
1171 *xp++ = 'A';
1172 if (mp->m_flag & MDRAFTED)
1173 *xp++ = 'T';
1174 *xp = 0;
1175 if (xstatout[0])
1176 fprintf(obuf, "%.*sX-Status: %s\n",
1177 (int)prefixlen, (prefixlen ? prefix : ""), xstatout);
1178 addstats(stats, 1, prefixlen + 11 + xp - xstatout);
1181 static void
1182 put_from_(FILE *fp, struct mimepart *ip, off_t *stats)
1184 time_t now;
1185 char const *from, *date, *nl;
1186 int i;
1188 if (ip && ip->m_from) {
1189 from = ip->m_from;
1190 date = fakedate(ip->m_time);
1191 nl = "\n";
1192 } else {
1193 time(&now);
1194 from = myname;
1195 date = ctime(&now);
1196 nl = "";
1199 i = fprintf(fp, "From %s %s%s", from, date, nl);
1200 if (i > 0)
1201 addstats(stats, (*nl != '\0'), i);
1205 * This is fgetline for mbox lines.
1207 char *
1208 foldergets(char **s, size_t *size, size_t *count, size_t *llen, FILE *stream)
1210 char *p, *top;
1212 if ((p = fgetline(s, size, count, llen, stream, 0)) == NULL)
1213 return NULL;
1214 if (*p == '>') {
1215 p++;
1216 while (*p == '>') p++;
1217 if (strncmp(p, "From ", 5) == 0) {
1218 /* we got a masked From line */
1219 top = &(*s)[*llen];
1220 p = *s;
1222 p[0] = p[1];
1223 while (++p < top);
1224 (*llen)--;
1227 return *s;