skipre(): do not AND, lowerconv() takes care
[s-mailx.git] / send.c
blobbe143cc6286699cb6475d4d4d0f71426a3612386
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 *volatile stats, int level)
189 char *line = NULL, *cp, *cp2, *start, *tcs, *pipecmd = NULL, *rest;
190 size_t linesize = 0, linelen, count, len, restsize;
191 int dostat, infld = 0, ignoring = 1, isenc, c, rt = 0, eof, ispipe = 0;
192 struct mimepart *np;
193 FILE *volatile ibuf = NULL, *volatile pbuf = obuf,
194 *volatile qbuf = obuf, *origobuf = obuf;
195 enum conversion volatile convert;
196 sighandler_type volatile oldpipe = SIG_DFL;
197 long lineno = 0;
200 if (ip->m_mimecontent == MIME_PKCS7 && ip->m_multipart &&
201 action != SEND_MBOX && action != SEND_RFC822 &&
202 action != SEND_SHOW)
203 goto skip;
204 dostat = 0;
205 if (level == 0) {
206 if (doign != NULL) {
207 if (!is_ign("status", 6, doign))
208 dostat |= 1;
209 if (!is_ign("x-status", 8, doign))
210 dostat |= 2;
211 } else
212 dostat = 3;
214 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
215 return -1;
216 count = ip->m_size;
217 if (ip->m_mimecontent == MIME_DISCARD)
218 goto skip;
219 if ((ip->m_flag&MNOFROM) == 0)
220 while (count && (c = getc(ibuf)) != EOF) {
221 count--;
222 if (c == '\n')
223 break;
225 isenc = 0;
226 convert = action == SEND_TODISP || action == SEND_TODISP_ALL ||
227 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
228 action == SEND_TOSRCH || action == SEND_TOFLTR ?
229 CONV_FROMHDR : CONV_NONE;
230 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
231 lineno++;
232 if (line[0] == '\n') {
234 * If line is blank, we've reached end of
235 * headers, so force out status: field
236 * and note that we are no longer in header
237 * fields
239 if (dostat & 1)
240 statusput(zmp, obuf, prefix, stats);
241 if (dostat & 2)
242 xstatusput(zmp, obuf, prefix, stats);
243 if (doign != allignore)
244 out("\n", 1, obuf, CONV_NONE, SEND_MBOX,
245 prefix, prefixlen, stats,
246 NULL, NULL);
247 break;
249 isenc &= ~1;
250 if (infld && blankchar(line[0]&0377)) {
252 * If this line is a continuation (via space or tab)
253 * of a previous header field, determine if the start
254 * of the line is a MIME encoded word.
256 if (isenc & 2) {
257 for (cp = line; blankchar(*cp&0377); cp++);
258 if (cp > line && linelen - (cp - line) > 8 &&
259 cp[0] == '=' && cp[1] == '?')
260 isenc |= 1;
262 } else {
264 * Pick up the header field if we have one.
266 for (cp = line; (c = *cp&0377) && c != ':' &&
267 !spacechar(c); cp++);
268 cp2 = cp;
269 while (spacechar(*cp&0377))
270 cp++;
271 if (cp[0] != ':' && level == 0 && lineno == 1) {
273 * Not a header line, force out status:
274 * This happens in uucp style mail where
275 * there are no headers at all.
277 if (dostat & 1)
278 statusput(zmp, obuf, prefix, stats);
279 if (dostat & 2)
280 xstatusput(zmp, obuf, prefix, 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, stats);
302 dostat &= ~1;
303 ignoring = 1;
305 } else if (asccasecmp(line, "x-status") == 0) {
307 * If the field is "status," go compute
308 * and print the real Status: field
310 if (dostat & 2) {
311 xstatusput(zmp, obuf, prefix, stats);
312 dostat &= ~2;
313 ignoring = 1;
315 } else
316 ignoring = 0;
317 *cp2 = c;
318 infld = 1;
321 * Determine if the end of the line is a MIME encoded word.
323 isenc &= ~2;
324 if (count && (c = getc(ibuf)) != EOF) {
325 if (blankchar(c)) {
326 if (linelen > 0 && line[linelen-1] == '\n')
327 cp = &line[linelen-2];
328 else
329 cp = &line[linelen-1];
330 while (cp >= line && whitechar(*cp&0377))
331 cp++;
332 if (cp - line > 8 && cp[0] == '=' &&
333 cp[-1] == '?')
334 isenc |= 2;
336 ungetc(c, ibuf);
338 if (!ignoring) {
339 start = line;
340 len = linelen;
341 if (action == SEND_TODISP ||
342 action == SEND_TODISP_ALL ||
343 action == SEND_QUOTE ||
344 action == SEND_QUOTE_ALL ||
345 action == SEND_TOSRCH ||
346 action == SEND_TOFLTR) {
348 * Strip blank characters if two MIME-encoded
349 * words follow on continuing lines.
351 if (isenc & 1)
352 while (len>0&&blankchar(*start&0377)) {
353 start++;
354 len--;
356 if (isenc & 2)
357 if (len > 0 && start[len-1] == '\n')
358 len--;
359 while (len > 0 && blankchar(start[len-1]&0377))
360 len--;
362 out(start, len, obuf, convert,
363 action, prefix, prefixlen, stats,
364 NULL, NULL);
365 if (ferror(obuf)) {
366 free(line);
367 return -1;
371 free(line);
372 line = NULL;
373 skip: switch (ip->m_mimecontent) {
374 case MIME_822:
375 switch (action) {
376 case SEND_TOFLTR:
377 putc('\0', obuf);
378 /*FALLTHRU*/
379 case SEND_TODISP:
380 case SEND_TODISP_ALL:
381 case SEND_QUOTE:
382 case SEND_QUOTE_ALL:
383 put_from_(obuf, ip->m_multipart);
384 /*FALLTHRU*/
385 case SEND_TOSRCH:
386 case SEND_DECRYPT:
387 goto multi;
388 case SEND_TOFILE:
389 case SEND_TOPIPE:
390 put_from_(obuf, ip->m_multipart);
391 /*FALLTHRU*/
392 case SEND_MBOX:
393 case SEND_RFC822:
394 case SEND_SHOW:
395 break;
397 break;
398 case MIME_TEXT_HTML:
399 if (action == SEND_TOFLTR)
400 putc('\b', obuf);
401 /*FALLTHRU*/
402 case MIME_TEXT:
403 case MIME_TEXT_PLAIN:
404 switch (action) {
405 case SEND_TODISP:
406 case SEND_TODISP_ALL:
407 case SEND_QUOTE:
408 case SEND_QUOTE_ALL:
409 pipecmd = getpipecmd(ip->m_ct_type_plain);
410 /*FALLTHRU*/
411 default:
412 break;
414 break;
415 case MIME_DISCARD:
416 if (action != SEND_DECRYPT)
417 return rt;
418 break;
419 case MIME_PKCS7:
420 if (action != SEND_MBOX && action != SEND_RFC822 &&
421 action != SEND_SHOW && ip->m_multipart)
422 goto multi;
423 /*FALLTHRU*/
424 default:
425 switch (action) {
426 case SEND_TODISP:
427 case SEND_TODISP_ALL:
428 case SEND_QUOTE:
429 case SEND_QUOTE_ALL:
430 if ((pipecmd = getpipecmd(ip->m_ct_type_plain)) != NULL)
431 break;
432 if (level == 0 && count) {
433 cp = "[Binary content]\n\n";
434 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
435 prefix, prefixlen, stats,
436 NULL, NULL);
438 /*FALLTHRU*/
439 case SEND_TOFLTR:
440 return rt;
441 case SEND_TOFILE:
442 case SEND_TOPIPE:
443 case SEND_TOSRCH:
444 case SEND_DECRYPT:
445 case SEND_MBOX:
446 case SEND_RFC822:
447 case SEND_SHOW:
448 break;
450 break;
451 case MIME_ALTERNATIVE:
452 if ((action == SEND_TODISP || action == SEND_QUOTE) &&
453 value("print-alternatives") == NULL)
454 for (np = ip->m_multipart; np; np = np->m_nextpart)
455 if (np->m_mimecontent == MIME_TEXT_PLAIN) {
456 if (sendpart(zmp, np, obuf,
457 doign, prefix,
458 prefixlen,
459 action, stats,
460 level+1) < 0)
461 return -1;
462 return rt;
464 /*FALLTHRU*/
465 case MIME_MULTI:
466 case MIME_DIGEST:
467 switch (action) {
468 case SEND_TODISP:
469 case SEND_TODISP_ALL:
470 case SEND_QUOTE:
471 case SEND_QUOTE_ALL:
472 case SEND_TOFILE:
473 case SEND_TOPIPE:
474 case SEND_TOSRCH:
475 case SEND_TOFLTR:
476 case SEND_DECRYPT:
477 multi:
478 if ((action == SEND_TODISP ||
479 action == SEND_TODISP_ALL) &&
480 ip->m_multipart != NULL &&
481 ip->m_multipart->m_mimecontent == MIME_DISCARD &&
482 ip->m_multipart->m_nextpart == NULL) {
483 cp = "[Missing multipart boundary - "
484 "use \"show\" to display "
485 "the raw message]\n\n";
486 out(cp, strlen(cp), obuf, CONV_NONE, SEND_MBOX,
487 prefix, prefixlen, stats,
488 NULL, NULL);
490 for (np = ip->m_multipart; np; np = np->m_nextpart) {
491 if (np->m_mimecontent == MIME_DISCARD &&
492 action != SEND_DECRYPT)
493 continue;
494 switch (action) {
495 case SEND_TOFILE:
496 if (np->m_partstring &&
497 strcmp(np->m_partstring,
498 "1") == 0)
499 break;
500 stats = NULL;
501 if ((obuf = newfile(np, &ispipe,
502 (sighandler_type*)
503 &oldpipe))
504 == NULL)
505 continue;
506 break;
507 case SEND_TODISP:
508 case SEND_TODISP_ALL:
509 case SEND_QUOTE_ALL:
510 if ((ip->m_mimecontent == MIME_MULTI ||
511 ip->m_mimecontent ==
512 MIME_ALTERNATIVE ||
513 ip->m_mimecontent ==
514 MIME_DIGEST) &&
515 np->m_partstring) {
516 len = strlen(np->m_partstring) +
518 cp = ac_alloc(len);
519 snprintf(cp, len,
520 "%sPart %s:\n", level ||
521 strcmp(np->m_partstring,
522 "1") ?
523 "\n" : "",
524 np->m_partstring);
525 out(cp, strlen(cp), obuf,
526 CONV_NONE, SEND_MBOX,
527 prefix, prefixlen,
528 stats,
529 NULL, NULL);
530 ac_free(cp);
532 break;
533 case SEND_TOFLTR:
534 putc('\0', obuf);
535 /*FALLTHRU*/
536 case SEND_MBOX:
537 case SEND_RFC822:
538 case SEND_SHOW:
539 case SEND_TOSRCH:
540 case SEND_QUOTE:
541 case SEND_DECRYPT:
542 case SEND_TOPIPE:
543 break;
545 if (sendpart(zmp, np, obuf,
546 doign, prefix, prefixlen,
547 action, stats, level+1) < 0)
548 rt = -1;
549 else if (action == SEND_QUOTE)
550 break;
551 if (action == SEND_TOFILE && obuf != origobuf) {
552 if (ispipe == 0)
553 Fclose(obuf);
554 else {
555 safe_signal(SIGPIPE, SIG_IGN);
556 Pclose(obuf);
557 safe_signal(SIGPIPE, oldpipe);
561 return rt;
562 case SEND_MBOX:
563 case SEND_RFC822:
564 case SEND_SHOW:
565 break;
569 * Copy out message body
571 if (doign == allignore && level == 0) /* skip final blank line */
572 count--;
573 switch (ip->m_mimeenc) {
574 case MIME_BIN:
575 if (stats)
576 stats[0] = -1;
577 /*FALLTHRU*/
578 case MIME_7B:
579 case MIME_8B:
580 convert = CONV_NONE;
581 break;
582 case MIME_QP:
583 convert = CONV_FROMQP;
584 break;
585 case MIME_B64:
586 switch (ip->m_mimecontent) {
587 case MIME_TEXT:
588 case MIME_TEXT_PLAIN:
589 case MIME_TEXT_HTML:
590 convert = CONV_FROMB64_T;
591 break;
592 default:
593 convert = CONV_FROMB64;
595 break;
596 default:
597 convert = CONV_NONE;
599 if (action == SEND_DECRYPT || action == SEND_MBOX ||
600 action == SEND_RFC822 || action == SEND_SHOW)
601 convert = CONV_NONE;
602 tcs = gettcharset();
603 #ifdef HAVE_ICONV
604 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
605 action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
606 action == SEND_TOSRCH) &&
607 (ip->m_mimecontent == MIME_TEXT_PLAIN ||
608 ip->m_mimecontent == MIME_TEXT_HTML ||
609 ip->m_mimecontent == MIME_TEXT)) {
610 if (iconvd != (iconv_t)-1)
611 iconv_close(iconvd);
612 if (asccasecmp(tcs, ip->m_charset) &&
613 asccasecmp(us_ascii, ip->m_charset))
614 iconvd = iconv_open_ft(tcs, ip->m_charset);
615 else
616 iconvd = (iconv_t)-1;
618 #endif /* HAVE_ICONV */
619 if ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
620 action == SEND_QUOTE || action == SEND_QUOTE_ALL) &&
621 pipecmd != NULL) {
622 qbuf = obuf;
623 pbuf = getpipefile(pipecmd, (FILE**)&qbuf,
624 action == SEND_QUOTE || action == SEND_QUOTE_ALL);
625 action = SEND_TOPIPE;
626 if (pbuf != qbuf) {
627 oldpipe = safe_signal(SIGPIPE, onpipe);
628 if (sigsetjmp(pipejmp, 1))
629 goto end;
631 } else
632 pbuf = qbuf = obuf;
633 eof = 0;
634 while (!eof && foldergets(&line, &linesize, &count, &linelen, ibuf)) {
635 lineno++;
636 while (convert == CONV_FROMQP && linelen >= 2 &&
637 line[linelen-2] == '=') {
638 char *line2;
639 size_t linesize2, linelen2;
640 nextl:
641 line2 = NULL;
642 linesize2 = 0;
643 if (foldergets(&line2, &linesize2, &count, &linelen2,
644 ibuf) == NULL) {
645 eof = 1;
646 break;
648 if (linelen + linelen2 + 1 > linesize)
649 line = srealloc(line, linesize = linelen +
650 linelen2 + 1);
651 memcpy(&line[linelen], line2, linelen2+1);
652 linelen += linelen2;
653 free(line2);
655 rest = NULL;
656 restsize = 0;
657 out(line, linelen, pbuf, convert, action,
658 pbuf == origobuf ? prefix : NULL,
659 pbuf == origobuf ? prefixlen : 0,
660 pbuf == origobuf ? stats : NULL,
661 eof ? NULL : &rest, eof ? NULL : &restsize);
662 if (ferror(pbuf)) {
663 rt = -1;
664 break;
666 if (restsize) {
667 if (line != rest)
668 memmove(line, rest, restsize);
669 linelen = restsize;
670 goto nextl;
673 end: free(line);
674 if (pbuf != qbuf) {
675 safe_signal(SIGPIPE, SIG_IGN);
676 Pclose(pbuf);
677 safe_signal(SIGPIPE, oldpipe);
678 if (qbuf != obuf)
679 pipecpy(qbuf, obuf, origobuf, prefix, prefixlen, stats);
681 #ifdef HAVE_ICONV
682 if (iconvd != (iconv_t)-1) {
683 iconv_close(iconvd);
684 iconvd = (iconv_t)-1;
686 #endif
687 return rt;
690 static struct mimepart *
691 parsemsg(struct message *mp, enum parseflags pf)
693 struct mimepart *ip;
695 ip = csalloc(1, sizeof *ip);
696 ip->m_flag = mp->m_flag;
697 ip->m_have = mp->m_have;
698 ip->m_block = mp->m_block;
699 ip->m_offset = mp->m_offset;
700 ip->m_size = mp->m_size;
701 ip->m_xsize = mp->m_xsize;
702 ip->m_lines = mp->m_lines;
703 ip->m_xlines = mp->m_lines;
704 if (parsepart(mp, ip, pf, 0) != OKAY)
705 return NULL;
706 return ip;
709 static enum okay
710 parsepart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
711 int level)
713 char *cp;
715 ip->m_ct_type = hfield1("content-type", (struct message *)ip);
716 if (ip->m_ct_type != NULL) {
717 ip->m_ct_type_plain = savestr(ip->m_ct_type);
718 if ((cp = strchr(ip->m_ct_type_plain, ';')) != NULL)
719 *cp = '\0';
720 } else if (ip->m_parent && ip->m_parent->m_mimecontent == MIME_DIGEST)
721 ip->m_ct_type_plain = "message/rfc822";
722 else
723 ip->m_ct_type_plain = "text/plain";
724 ip->m_mimecontent = mime_getcontent(ip->m_ct_type_plain);
725 if (ip->m_ct_type)
726 ip->m_charset = mime_getparam("charset", ip->m_ct_type);
727 if (ip->m_charset == NULL)
728 ip->m_charset = us_ascii;
729 ip->m_ct_transfer_enc = hfield1("content-transfer-encoding",
730 (struct message *)ip);
731 ip->m_mimeenc = ip->m_ct_transfer_enc ?
732 mime_getenc(ip->m_ct_transfer_enc) : MIME_7B;
733 if ((cp = hfield1("content-disposition", (struct message *)ip)) == 0 ||
734 (ip->m_filename = mime_getparam("filename", cp)) == 0)
735 if (ip->m_ct_type != NULL)
736 ip->m_filename = mime_getparam("name", ip->m_ct_type);
737 if (pf & PARSE_PARTS) {
738 if (level > 9999) {
739 fprintf(stderr, "MIME content too deeply nested.\n");
740 return STOP;
742 switch (ip->m_mimecontent) {
743 case MIME_PKCS7:
744 if (pf & PARSE_DECRYPT) {
745 parsepkcs7(zmp, ip, pf, level);
746 break;
748 /*FALLTHRU*/
749 default:
750 break;
751 case MIME_MULTI:
752 case MIME_ALTERNATIVE:
753 case MIME_DIGEST:
754 parsemultipart(zmp, ip, pf, level);
755 break;
756 case MIME_822:
757 parse822(zmp, ip, pf, level);
758 break;
761 return OKAY;
764 static void
765 parsemultipart(struct message *zmp, struct mimepart *ip, enum parseflags pf,
766 int level)
768 char *boundary;
769 char *line = NULL;
770 size_t linesize = 0, linelen, count, boundlen;
771 FILE *ibuf;
772 struct mimepart *np = NULL;
773 off_t offs;
774 int part = 0;
775 long lines = 0;
777 if ((boundary = mime_getboundary(ip->m_ct_type)) == NULL)
778 return;
779 boundlen = strlen(boundary);
780 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
781 return;
782 count = ip->m_size;
783 while (foldergets(&line, &linesize, &count, &linelen, ibuf))
784 if (line[0] == '\n')
785 break;
786 offs = ftell(ibuf);
787 newpart(ip, &np, offs, NULL);
788 while (foldergets(&line, &linesize, &count, &linelen, ibuf)) {
789 if ((lines > 0 || part == 0) && linelen >= boundlen + 1 &&
790 strncmp(line, boundary, boundlen) == 0) {
791 if (line[boundlen] == '\n') {
792 offs = ftell(ibuf);
793 if (part != 0) {
794 endpart(&np, offs-boundlen-2, lines);
795 newpart(ip, &np, offs-boundlen-2, NULL);
797 endpart(&np, offs, 2);
798 newpart(ip, &np, offs, &part);
799 lines = 0;
800 } else if (line[boundlen] == '-' &&
801 line[boundlen+1] == '-' &&
802 line[boundlen+2] == '\n') {
803 offs = ftell(ibuf);
804 if (part != 0) {
805 endpart(&np, offs-boundlen-4, lines);
806 newpart(ip, &np, offs-boundlen-4, NULL);
808 endpart(&np, offs+count, 2);
809 break;
810 } else
811 lines++;
812 } else
813 lines++;
815 if (np) {
816 offs = ftell(ibuf);
817 endpart(&np, offs, lines);
819 for (np = ip->m_multipart; np; np = np->m_nextpart)
820 if (np->m_mimecontent != MIME_DISCARD)
821 parsepart(zmp, np, pf, level+1);
822 free(line);
825 static void
826 newpart(struct mimepart *ip, struct mimepart **np, off_t offs, int *part)
828 struct mimepart *pp;
829 size_t sz;
831 *np = csalloc(1, sizeof **np);
832 (*np)->m_flag = MNOFROM;
833 (*np)->m_have = HAVE_HEADER|HAVE_BODY;
834 (*np)->m_block = mailx_blockof(offs);
835 (*np)->m_offset = mailx_offsetof(offs);
836 if (part) {
837 (*part)++;
838 sz = ip->m_partstring ? strlen(ip->m_partstring) : 0;
839 sz += 20;
840 (*np)->m_partstring = salloc(sz);
841 if (ip->m_partstring)
842 snprintf((*np)->m_partstring, sz, "%s.%u",
843 ip->m_partstring, *part);
844 else
845 snprintf((*np)->m_partstring, sz, "%u", *part);
846 } else
847 (*np)->m_mimecontent = MIME_DISCARD;
848 (*np)->m_parent = ip;
849 if (ip->m_multipart) {
850 for (pp = ip->m_multipart; pp->m_nextpart; pp = pp->m_nextpart);
851 pp->m_nextpart = *np;
852 } else
853 ip->m_multipart = *np;
856 static void
857 endpart(struct mimepart **np, off_t xoffs, long lines)
859 off_t offs;
861 offs = mailx_positionof((*np)->m_block, (*np)->m_offset);
862 (*np)->m_size = (*np)->m_xsize = xoffs - offs;
863 (*np)->m_lines = (*np)->m_xlines = lines;
864 *np = NULL;
867 static void
868 parse822(struct message *zmp, struct mimepart *ip, enum parseflags pf,
869 int level)
871 int c, lastc = '\n';
872 size_t count;
873 FILE *ibuf;
874 off_t offs;
875 struct mimepart *np;
876 long lines;
878 if ((ibuf = setinput(&mb, (struct message *)ip, NEED_BODY)) == NULL)
879 return;
880 count = ip->m_size;
881 lines = ip->m_lines;
882 while (count && ((c = getc(ibuf)) != EOF)) {
883 count--;
884 if (c == '\n') {
885 lines--;
886 if (lastc == '\n')
887 break;
889 lastc = c;
891 offs = ftell(ibuf);
892 np = csalloc(1, sizeof *np);
893 np->m_flag = MNOFROM;
894 np->m_have = HAVE_HEADER|HAVE_BODY;
895 np->m_block = mailx_blockof(offs);
896 np->m_offset = mailx_offsetof(offs);
897 np->m_size = np->m_xsize = count;
898 np->m_lines = np->m_xlines = lines;
899 np->m_partstring = ip->m_partstring;
900 np->m_parent = ip;
901 ip->m_multipart = np;
902 substdate((struct message *)np);
903 np->m_from = fakefrom((struct message *)np);
904 parsepart(zmp, np, pf, level+1);
907 static void
908 parsepkcs7(struct message *zmp, struct mimepart *ip, enum parseflags pf,
909 int level)
911 struct message m, *xmp;
912 struct mimepart *np;
913 char *to, *cc;
915 memcpy(&m, ip, sizeof m);
916 to = hfield1("to", zmp);
917 cc = hfield1("cc", zmp);
918 if ((xmp = smime_decrypt(&m, to, cc, 0)) != NULL) {
919 np = csalloc(1, sizeof *np);
920 np->m_flag = xmp->m_flag;
921 np->m_have = xmp->m_have;
922 np->m_block = xmp->m_block;
923 np->m_offset = xmp->m_offset;
924 np->m_size = xmp->m_size;
925 np->m_xsize = xmp->m_xsize;
926 np->m_lines = xmp->m_lines;
927 np->m_xlines = xmp->m_xlines;
928 np->m_partstring = ip->m_partstring;
929 if (parsepart(zmp, np, pf, level+1) == OKAY) {
930 np->m_parent = ip;
931 ip->m_multipart = np;
936 static size_t
937 out(char *buf, size_t len, FILE *fp,
938 enum conversion convert, enum sendaction action,
939 char *prefix, size_t prefixlen, off_t *stats,
940 char **restp, size_t *restsizep)
942 size_t sz, n;
943 char *cp;
944 long lines;
946 sz = 0;
947 if (action == SEND_MBOX || action == SEND_DECRYPT) {
948 cp = buf;
949 n = len;
950 while (n && cp[0] == '>')
951 cp++, n--;
952 if (n >= 5 && cp[0] == 'F' && cp[1] == 'r' && cp[2] == 'o' &&
953 cp[3] == 'm' && cp[4] == ' ') {
954 putc('>', fp);
955 sz++;
958 sz += mime_write(buf, len, fp,
959 action == SEND_MBOX ? CONV_NONE : convert,
960 action == SEND_TODISP || action == SEND_TODISP_ALL ||
961 action == SEND_QUOTE ||
962 action == SEND_QUOTE_ALL ?
963 TD_ISPR|TD_ICONV :
964 action == SEND_TOSRCH || action == SEND_TOPIPE ?
965 TD_ICONV :
966 action == SEND_TOFLTR ?
967 TD_DELCTRL :
968 action == SEND_SHOW ?
969 TD_ISPR : TD_NONE,
970 prefix, prefixlen,
971 restp, restsizep);
972 lines = 0;
973 if (stats && stats[0] != -1) {
974 for (cp = buf; cp < &buf[sz]; cp++)
975 if (*cp == '\n')
976 lines++;
978 addstats(stats, lines, sz);
979 return sz;
982 static void
983 addstats(off_t *stats, off_t lines, off_t bytes)
985 if (stats) {
986 if (stats[0] >= 0)
987 stats[0] += lines;
988 stats[1] += bytes;
993 * Get a file for an attachment.
995 static FILE *
996 newfile(struct mimepart *ip, int *ispipe, sighandler_type *oldpipe)
998 char *f = ip->m_filename;
999 struct str in, out;
1000 FILE *fp;
1002 *ispipe = 0;
1003 if (f != NULL && f != (char *)-1) {
1004 in.s = f;
1005 in.l = strlen(f);
1006 mime_fromhdr(&in, &out, TD_ISPR);
1007 memcpy(f, out.s, out.l);
1008 *(f + out.l) = '\0';
1009 free(out.s);
1012 if (value("interactive") != NULL) {
1013 char *f2, *f3;
1014 jgetname: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1015 ip->m_partstring ? ip->m_partstring : "?",
1016 ip->m_ct_type_plain);
1017 f2 = readtty(": ", f != (char *)-1 ? f : NULL);
1018 if (f2 == NULL || *f2 == '\0') {
1019 fprintf(stderr, tr(279, "... skipping this\n"));
1020 return (NULL);
1021 } else if (*f2 == '|')
1022 /* Pipes are expanded by the shell */
1023 f = f2;
1024 else if ((f3 = expand(f2)) == NULL)
1025 /* (Error message written by expand()) */
1026 goto jgetname;
1027 else
1028 f = f3;
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, tr(176, "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;