1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mail to mail folders and displays.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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
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
40 #ifndef HAVE_AMALGAMATION
45 PIPE_NULL
, /* No pipe- mimetype handler */
46 PIPE_COMM
, /* Normal command */
47 PIPE_ASYNC
, /* Normal command, run asynchronous */
48 PIPE_TEXT
, /* @ special command to force treatment as text */
49 PIPE_MSG
/* Display message (returned as command string) */
58 static void onpipe(int signo
);
60 static void _parsemultipart(struct message
*zmp
, struct mimepart
*ip
,
61 enum parseflags pf
, int level
);
63 /* Going for user display, print Part: info string */
64 static void _print_part_info(struct str
*out
, struct mimepart
*mip
,
65 struct ignoretab
*doign
, int level
);
67 /* Adjust output statistics */
68 SINLINE
void _addstats(off_t
*stats
, off_t lines
, off_t bytes
);
70 /* Call mime_write() as approbiate and adjust statistics */
71 SINLINE ssize_t
_out(char const *buf
, size_t len
, FILE *fp
,
72 enum conversion convert
, enum sendaction action
,
73 struct quoteflt
*qf
, off_t
*stats
, struct str
*rest
);
75 /* Query possible pipe command for MIME type */
76 static enum pipeflags
_pipecmd(char **result
, char const *content_type
);
79 static FILE * _pipefile(char const *pipecomm
, FILE **qbuf
, bool_t quote
,
82 static int sendpart(struct message
*zmp
, struct mimepart
*ip
, FILE *obuf
,
83 struct ignoretab
*doign
, struct quoteflt
*qf
,
84 enum sendaction action
, off_t
*stats
, int level
);
85 static struct mimepart
*parsemsg(struct message
*mp
, enum parseflags pf
);
86 static enum okay
parsepart(struct message
*zmp
, struct mimepart
*ip
,
87 enum parseflags pf
, int level
);
88 static void newpart(struct mimepart
*ip
, struct mimepart
**np
, off_t offs
,
90 static void endpart(struct mimepart
**np
, off_t xoffs
, long lines
);
91 static void parse822(struct message
*zmp
, struct mimepart
*ip
,
92 enum parseflags pf
, int level
);
94 static void parsepkcs7(struct message
*zmp
, struct mimepart
*ip
,
95 enum parseflags pf
, int level
);
97 static FILE *newfile(struct mimepart
*ip
, int *ispipe
);
98 static void pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
,
99 struct quoteflt
*qf
, off_t
*stats
);
100 static void statusput(const struct message
*mp
, FILE *obuf
,
101 struct quoteflt
*qf
, off_t
*stats
);
102 static void xstatusput(const struct message
*mp
, FILE *obuf
,
103 struct quoteflt
*qf
, off_t
*stats
);
104 static void put_from_(FILE *fp
, struct mimepart
*ip
, off_t
*stats
);
106 static sigjmp_buf pipejmp
;
113 siglongjmp(pipejmp
, 1);
117 _parsemultipart(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
121 * TODO Instead of the recursive multiple run parse we have today,
122 * TODO the send/MIME layer rewrite must create a "tree" of parts with
123 * TODO a single-pass parse, then address each part directly as
124 * TODO necessary; since boundaries start with -- and the content
125 * TODO rather forms a stack this is pretty cheap indeed!
127 struct mimepart
*np
= NULL
;
128 char *boundary
, *line
= NULL
;
129 size_t linesize
= 0, linelen
, cnt
, boundlen
;
135 if ((boundary
= mime_get_boundary(ip
->m_ct_type
, &linelen
)) == NULL
)
138 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
)
141 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0))
145 newpart(ip
, &np
, offs
, NULL
);
146 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
147 /* XXX linelen includes LF */
148 if (! ((lines
> 0 || part
== 0) && linelen
> boundlen
&&
149 memcmp(line
, boundary
, boundlen
) == 0)) {
153 /* Subpart boundary? */
154 if (line
[boundlen
] == '\n') {
157 endpart(&np
, offs
- boundlen
- 2, lines
);
158 newpart(ip
, &np
, offs
- boundlen
- 2, NULL
);
160 endpart(&np
, offs
, 2);
161 newpart(ip
, &np
, offs
, &part
);
166 * Final boundary? Be aware of cases where there is no
167 * separating newline in between boundaries, as has been seen
168 * in a message with "Content-Type: multipart/appledouble;"
170 if (linelen
< boundlen
+ 2)
172 linelen
-= boundlen
+ 2;
173 if (line
[boundlen
] != '-' || line
[boundlen
+ 1] != '-' ||
174 (linelen
> 0 && line
[boundlen
+ 2] != '\n'))
178 endpart(&np
, offs
- boundlen
- 4, lines
);
179 newpart(ip
, &np
, offs
- boundlen
- 4, NULL
);
181 endpart(&np
, offs
+ cnt
, 2);
186 endpart(&np
, offs
, lines
);
188 for (np
= ip
->m_multipart
; np
; np
= np
->m_nextpart
)
189 if (np
->m_mimecontent
!= MIME_DISCARD
)
190 parsepart(zmp
, np
, pf
, level
+ 1);
195 _print_part_info(struct str
*out
, struct mimepart
*mip
,
196 struct ignoretab
*doign
, int level
)
198 struct str ct
= {NULL
, 0}, cd
= {NULL
, 0};
202 if (is_ign("content-type", 12, doign
)) {
203 out
->s
= mip
->m_ct_type_plain
;
204 out
->l
= strlen(out
->s
);
205 ct
.s
= ac_alloc(out
->l
+ 2 +1);
209 if (is_prefix("application/", out
->s
)) {
210 memcpy(ct
.s
+ 2, "appl../", 7);
214 out
->l
= smin(out
->l
, 17);
216 out
->l
= smin(out
->l
, 24);
217 memcpy(ct
.s
+ ct
.l
, out
->s
, out
->l
);
223 if (is_ign("content-disposition", 19, doign
) &&
224 mip
->m_filename
!= NULL
) {
227 ti
.l
= strlen(ti
.s
= mip
->m_filename
);
228 mime_fromhdr(&ti
, &to
, TD_ISPR
| TD_ICONV
| TD_DELCTRL
);
229 to
.l
= MIN(to
.l
, 25);
230 cd
.s
= ac_alloc(to
.l
+ 2 +1);
233 memcpy(cd
.s
+ 2, to
.s
, to
.l
);
239 /* Take care of "99.99", i.e., 5 */
240 if ((ps
= mip
->m_partstring
) == NULL
|| ps
[0] == '\0')
244 * Assume maximum possible sizes for 64 bit integers here to avoid any
245 * buffer overflows just in case we have a bug somewhere and / or the
246 * snprintf() is our internal version that doesn't really provide hard
249 #define __msg "%s[-- #%s : %lu/%lu%s%s --]\n"
250 out
->l
= sizeof(__msg
) + strlen(ps
) + 2*21 + ct
.l
+ cd
.l
+ 1;
251 out
->s
= salloc(out
->l
);
252 out
->l
= snprintf(out
->s
, out
->l
, __msg
,
253 (level
|| (ps
[0] != '1' && ps
[1] == '\0')) ? "\n" : "",
254 ps
, (ul_it
)mip
->m_lines
, (ul_it
)mip
->m_size
,
255 (ct
.s
!= NULL
? ct
.s
: ""), (cd
.s
!= NULL
? cd
.s
: ""));
256 out
->s
[out
->l
] = '\0';
266 _addstats(off_t
*stats
, off_t lines
, off_t bytes
)
276 _out(char const *buf
, size_t len
, FILE *fp
, enum conversion convert
, enum
277 sendaction action
, struct quoteflt
*qf
, off_t
*stats
, struct str
*rest
)
284 Well
... it turns out to
not work like that since of course a valid
285 RFC
4155 compliant parser
, like S
-nail
, takes care
for From_ lines only
286 after an empty line has been seen
, which cannot be detected that easily
288 ifdef HAVE_DEBUG
/* TODO assert legacy */
289 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
290 * TODO other input situations handle RFC 4155 OR, if newly generated,
291 * TODO enforce quoted-printable if there is From_, as "required" by
292 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
293 * TODO if it may happen in this path, we should just treat decryption
294 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
295 if (action
== SEND_MBOX
|| action
== SEND_DECRYPT
)
296 assert(! is_head(buf
, len
));
298 if ((/*action == SEND_MBOX ||*/ action
== SEND_DECRYPT
) &&
305 flags
= ((int)action
& _TD_EOF
);
307 n
= mime_write(buf
, len
, fp
,
308 action
== SEND_MBOX
? CONV_NONE
: convert
,
310 (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
311 action
== SEND_QUOTE
||
312 action
== SEND_QUOTE_ALL
?
314 action
== SEND_TOSRCH
|| action
== SEND_TOPIPE
?
316 action
== SEND_TOFLTR
?
318 action
== SEND_SHOW
?
324 sz
= (ssize_t
)((size_t)sz
+ n
);
326 if (stats
!= NULL
&& stats
[0] != -1)
327 for (cp
= buf
; cp
< &buf
[sz
]; ++cp
)
330 _addstats(stats
, n
, sz
);
335 static enum pipeflags
336 _pipecmd(char **result
, char const *content_type
)
344 if (content_type
== NULL
)
347 /* First check wether there is a special pipe-MIMETYPE handler */
348 s
= ac_alloc(strlen(content_type
) + 6);
349 memcpy(s
, "pipe-", 5);
353 *cp
++ = lowerconv(*cq
);
354 while (*cq
++ != '\0');
361 /* User specified a command, inspect for special cases */
363 /* Normal command line */
366 } else if (*++cp
== '\0') {
367 /* Treat as plain text */
369 } else if (! msglist_is_single
) {
370 /* Viewing multiple messages in one go, don't block system */
372 *result
= UNCONST(tr(86,
373 "[Directly address message only to display this]\n"));
375 /* Viewing a single message only */
376 #if 0 /* TODO send/MIME layer rewrite: when we have a single-pass parser
377 * TODO then the parsing phase and the send phase will be separated;
378 * TODO that allows us to ask a user *before* we start the send, i.e.,
379 * TODO *before* a pager pipe is setup (which is the problem with
380 * TODO the '#if 0' code here) */
381 size_t l
= strlen(content_type
);
382 char const *x
= tr(999, "Should i display a part `%s' (y/n)? ");
383 s
= ac_alloc(l
+= strlen(x
) + 1);
384 snprintf(s
, l
- 1, x
, content_type
);
386 puts(""); /* .. we've hijacked a pipe 8-] ... */
389 x
= tr(210, "[User skipped diplay]\n");
391 *result
= UNCONST(x
);
395 /* Asynchronous command, normal command line */
396 ret
= PIPE_ASYNC
, *result
= ++cp
;
398 ret
= PIPE_COMM
, *result
= cp
;
405 _pipefile(char const *pipecomm
, FILE **qbuf
, bool_t quote
, bool_t async
)
413 if ((*qbuf
= Ftemp(&tempPipe
, "Rp", "w+", 0600, 1)) == NULL
) {
414 perror(tr(173, "tmpfile"));
421 if ((sh
= value("SHELL")) == NULL
)
423 if ((rbuf
= Popen(pipecomm
, "W", sh
,
424 async
? -1 : fileno(*qbuf
))) == NULL
)
435 * Send message described by the passed pointer to the
436 * passed output buffer. Return -1 on error.
437 * Adjust the status: field if need be.
438 * If doign is given, suppress ignored header fields.
439 * prefix is a string to prepend to each output line.
440 * action = data destination (SEND_MBOX,_TOFILE,_TODISP,_QUOTE,_DECRYPT).
441 * stats[0] is line count, stats[1] is character count. stats may be NULL.
442 * Note that stats[0] is valid for SEND_MBOX only.
445 sendmp(struct message
*mp
, FILE *obuf
, struct ignoretab
*doign
,
446 char const *prefix
, enum sendaction action
, off_t
*stats
)
455 if (mp
== dot
&& action
!= SEND_TOSRCH
&& action
!= SEND_TOFLTR
)
458 stats
[0] = stats
[1] = 0;
459 quoteflt_init(&qf
, prefix
);
462 * First line is the From_ line, so no headers there to worry about.
464 if ((ibuf
= setinput(&mb
, mp
, NEED_BODY
)) == NULL
)
469 if (mp
->m_flag
& MNOFROM
) {
470 if (doign
!= allignore
&& doign
!= fwdignore
&&
471 action
!= SEND_RFC822
)
472 sz
= fprintf(obuf
, "%.*sFrom %s %s\n",
474 (qf
.qf_pfix_len
!= 0 ? qf
.qf_pfix
: ""),
475 fakefrom(mp
), fakedate(mp
->m_time
));
477 if (qf
.qf_pfix_len
> 0 && doign
!= allignore
&&
478 doign
!= fwdignore
&& action
!= SEND_RFC822
) {
479 i
= fwrite(qf
.qf_pfix
, sizeof *qf
.qf_pfix
,
480 qf
.qf_pfix_len
, obuf
);
481 if (i
!= qf
.qf_pfix_len
)
485 while (cnt
&& (c
= getc(ibuf
)) != EOF
) {
486 if (doign
!= allignore
&& doign
!= fwdignore
&&
487 action
!= SEND_RFC822
) {
497 _addstats(stats
, 1, sz
);
500 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
501 pf
|= PARSE_DECRYPT
|PARSE_PARTS
;
502 if ((ip
= parsemsg(mp
, pf
)) == NULL
)
505 rv
= sendpart(mp
, ip
, obuf
, doign
, &qf
, action
, stats
, 0);
507 quoteflt_destroy(&qf
);
512 sendpart(struct message
*zmp
, struct mimepart
*ip
, FILE *obuf
,
513 struct ignoretab
*doign
, struct quoteflt
*qf
,
514 enum sendaction
volatile action
, off_t
*volatile stats
, int level
)
516 int volatile ispipe
, rt
= 0;
518 char *line
= NULL
, *cp
, *cp2
, *start
, *pipecomm
= NULL
;
519 size_t linesize
= 0, linelen
, cnt
;
520 int dostat
, infld
= 0, ignoring
= 1, isenc
, c
;
521 struct mimepart
*volatile np
;
522 FILE *volatile ibuf
= NULL
, *volatile pbuf
= obuf
,
523 *volatile qbuf
= obuf
, *origobuf
= obuf
;
524 enum conversion
volatile convert
;
525 sighandler_type
volatile oldpipe
= SIG_DFL
;
528 if (ip
->m_mimecontent
== MIME_PKCS7
&& ip
->m_multipart
&&
529 action
!= SEND_MBOX
&& action
!= SEND_RFC822
&&
535 if (!is_ign("status", 6, doign
))
537 if (!is_ign("x-status", 8, doign
))
542 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
)
545 if (ip
->m_mimecontent
== MIME_DISCARD
)
548 if ((ip
->m_flag
& MNOFROM
) == 0)
549 while (cnt
&& (c
= getc(ibuf
)) != EOF
) {
555 convert
= action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
556 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
557 action
== SEND_TOSRCH
|| action
== SEND_TOFLTR
?
558 CONV_FROMHDR
: CONV_NONE
;
560 /* Work the headers */
561 quoteflt_reset(qf
, obuf
);
562 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
564 if (line
[0] == '\n') {
566 * If line is blank, we've reached end of
567 * headers, so force out status: field
568 * and note that we are no longer in header
572 statusput(zmp
, obuf
, qf
, stats
);
574 xstatusput(zmp
, obuf
, qf
, stats
);
575 if (doign
!= allignore
)
576 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
,
581 if (infld
&& blankchar(line
[0])) {
583 * If this line is a continuation (via space or tab)
584 * of a previous header field, determine if the start
585 * of the line is a MIME encoded word.
588 for (cp
= line
; blankchar(*cp
); cp
++);
589 if (cp
> line
&& linelen
- (cp
- line
) > 8 &&
590 cp
[0] == '=' && cp
[1] == '?')
595 * Pick up the header field if we have one.
597 for (cp
= line
; (c
= *cp
& 0377) && c
!= ':' &&
601 while (spacechar(*cp
))
603 if (cp
[0] != ':' && level
== 0 && lineno
== 1) {
605 * Not a header line, force out status:
606 * This happens in uucp style mail where
607 * there are no headers at all.
610 statusput(zmp
, obuf
, qf
, stats
);
612 xstatusput(zmp
, obuf
, qf
, stats
);
613 if (doign
!= allignore
)
614 _out("\n", 1, obuf
, CONV_NONE
,SEND_MBOX
,
619 * If it is an ignored field and
620 * we care about such things, skip it.
623 *cp2
= 0; /* temporarily null terminate */
624 if ((doign
&& is_ign(line
, cp2
- line
, doign
)) ||
625 (action
== SEND_MBOX
&&
626 !boption("keep-content-length") &&
627 (asccasecmp(line
, "content-length")==0
628 || asccasecmp(line
, "lines") == 0)))
630 else if (asccasecmp(line
, "status") == 0) {
632 * If the field is "status," go compute
633 * and print the real Status: field
636 statusput(zmp
, obuf
, qf
, stats
);
640 } else if (asccasecmp(line
, "x-status") == 0) {
642 * If the field is "status," go compute
643 * and print the real Status: field
646 xstatusput(zmp
, obuf
, qf
, stats
);
656 * Determine if the end of the line is a MIME encoded word.
659 if (cnt
&& (c
= getc(ibuf
)) != EOF
) {
661 if (linelen
> 0 && line
[linelen
- 1] == '\n')
662 cp
= &line
[linelen
- 2];
664 cp
= &line
[linelen
- 1];
665 while (cp
>= line
&& whitechar(*cp
))
667 if (cp
- line
> 8 && cp
[0] == '=' &&
674 size_t len
= linelen
;
676 if (action
== SEND_TODISP
||
677 action
== SEND_TODISP_ALL
||
678 action
== SEND_QUOTE
||
679 action
== SEND_QUOTE_ALL
||
680 action
== SEND_TOSRCH
||
681 action
== SEND_TOFLTR
) {
683 * Strip blank characters if two MIME-encoded
684 * words follow on continuing lines.
687 while (len
> 0 && blankchar(*start
)) {
692 if (len
> 0 && start
[len
- 1] == '\n')
694 while (len
> 0 && blankchar(start
[len
- 1]))
697 _out(start
, len
, obuf
, convert
, action
, qf
, stats
,
710 switch (ip
->m_mimecontent
) {
717 case SEND_TODISP_ALL
:
720 if (value("rfc822-body-from_")) {
721 if (qf
->qf_pfix_len
> 0) {
722 size_t i
= fwrite(qf
->qf_pfix
,
724 qf
->qf_pfix_len
, obuf
);
725 if (i
== qf
->qf_pfix_len
)
726 _addstats(stats
, 0, i
);
728 put_from_(obuf
, ip
->m_multipart
, stats
);
736 if (value("rfc822-body-from_"))
737 put_from_(obuf
, ip
->m_multipart
, stats
);
746 if (action
== SEND_TOFLTR
)
750 case MIME_TEXT_PLAIN
:
753 case SEND_TODISP_ALL
:
757 switch (_pipecmd(&pipecomm
, ip
->m_ct_type_plain
)) {
759 _out(pipecomm
, strlen(pipecomm
), obuf
,
760 CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
775 if (action
!= SEND_DECRYPT
)
779 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&&
780 action
!= SEND_SHOW
&& ip
->m_multipart
)
786 case SEND_TODISP_ALL
:
790 switch (_pipecmd(&pipecomm
, ip
->m_ct_type_plain
)) {
792 _out(pipecomm
, strlen(pipecomm
), obuf
,
793 CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
803 goto jcopyout
; /* break; break; */
805 if (pipecomm
!= NULL
)
807 if (level
== 0 && cnt
) {
808 char const *x
= tr(210, "[Binary content]\n");
809 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
,
825 case MIME_ALTERNATIVE
:
826 if ((action
== SEND_TODISP
|| action
== SEND_QUOTE
) &&
827 value("print-alternatives") == NULL
) {
829 for (np
= ip
->m_multipart
; np
; np
= np
->m_nextpart
)
830 if (np
->m_mimecontent
== MIME_TEXT_PLAIN
)
833 for (np
= ip
->m_multipart
; np
;
834 np
= np
->m_nextpart
) {
835 if (np
->m_ct_type_plain
!= NULL
&&
836 action
!= SEND_QUOTE
) {
837 _print_part_info(&rest
, np
,
839 _out(rest
.s
, rest
.l
, obuf
,
840 CONV_NONE
, SEND_MBOX
,
843 if (doact
&& np
->m_mimecontent
==
846 rt
= sendpart(zmp
, np
, obuf
,
849 quoteflt_reset(qf
, origobuf
);
862 case SEND_TODISP_ALL
:
871 if ((action
== SEND_TODISP
||
872 action
== SEND_TODISP_ALL
) &&
873 ip
->m_multipart
!= NULL
&&
874 ip
->m_multipart
->m_mimecontent
== MIME_DISCARD
&&
875 ip
->m_multipart
->m_nextpart
== NULL
) {
876 char const *x
= tr(85,
877 "[Missing multipart boundary - "
878 "use \"show\" to display "
879 "the raw message]\n");
880 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
,
883 for (np
= ip
->m_multipart
; np
; np
= np
->m_nextpart
) {
884 if (np
->m_mimecontent
== MIME_DISCARD
&&
885 action
!= SEND_DECRYPT
)
890 if (np
->m_partstring
&&
891 strcmp(np
->m_partstring
,
895 if ((obuf
= newfile(np
,
896 UNVOLATILE(&ispipe
)))
901 if (sigsetjmp(pipejmp
, 1)) {
905 oldpipe
= safe_signal(SIGPIPE
, onpipe
);
908 case SEND_TODISP_ALL
:
910 if (ip
->m_mimecontent
!= MIME_MULTI
&&
916 _print_part_info(&rest
, np
, doign
,
918 _out(rest
.s
, rest
.l
, obuf
,
919 CONV_NONE
, SEND_MBOX
, qf
,
936 if (sendpart(zmp
, np
, obuf
, doign
, qf
,
937 action
, stats
, level
+1) < 0)
939 quoteflt_reset(qf
, origobuf
);
940 if (action
== SEND_QUOTE
)
942 if (action
== SEND_TOFILE
&& obuf
!= origobuf
) {
946 jpipe_close
: safe_signal(SIGPIPE
, SIG_IGN
);
948 safe_signal(SIGPIPE
, oldpipe
);
961 * Copy out message body
964 if (doign
== allignore
&& level
== 0) /* skip final blank line */
966 switch (ip
->m_mimeenc
) {
976 convert
= CONV_FROMQP
;
979 switch (ip
->m_mimecontent
) {
981 case MIME_TEXT_PLAIN
:
983 convert
= CONV_FROMB64_T
;
986 convert
= CONV_FROMB64
;
992 if (action
== SEND_DECRYPT
|| action
== SEND_MBOX
||
993 action
== SEND_RFC822
|| action
== SEND_SHOW
)
996 if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
997 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
998 action
== SEND_TOSRCH
) &&
999 (ip
->m_mimecontent
== MIME_TEXT_PLAIN
||
1000 ip
->m_mimecontent
== MIME_TEXT_HTML
||
1001 ip
->m_mimecontent
== MIME_TEXT
)) {
1002 char const *tcs
= charset_get_lc();
1004 if (iconvd
!= (iconv_t
)-1)
1005 n_iconv_close(iconvd
);
1006 /* TODO Since Base64 has an odd 4:3 relation in between input
1007 * TODO and output an input line may end with a partial
1008 * TODO multibyte character; this is no problem at all unless
1009 * TODO we send to the display or whatever, i.e., ensure
1010 * TODO makeprint() or something; to avoid this trap, *force*
1011 * TODO iconv(), in which case this layer will handle leftovers
1013 if (convert
== CONV_FROMB64_T
||
1014 (asccasecmp(tcs
, ip
->m_charset
) &&
1015 asccasecmp(charset_get_7bit(),
1017 iconvd
= n_iconv_open(tcs
, ip
->m_charset
);
1018 /* XXX Don't bail out if we cannot iconv(3) here;
1019 * XXX alternatively we could avoid trying to open
1020 * XXX if ip->m_charset is "unknown-8bit", which was
1021 * XXX the one that has bitten me?? */
1023 * TODO errors should DEFINETELY not be scrolled away!
1024 * TODO what about an error buffer (think old shsp(1)),
1025 * TODO re-dump errors since last snapshot when the
1026 * TODO command loop enters again? i.e., at least print
1027 * TODO "There were errors ?" before the next prompt,
1028 * TODO so that the user can look at the error buffer?
1030 if (iconvd
== (iconv_t
)-1 && errno
== EINVAL
) {
1031 fprintf(stderr
, tr(179,
1032 "Cannot convert from %s to %s\n"),
1033 ip
->m_charset
, tcs
);
1039 if (pipecomm
!= NULL
&&
1040 (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
1041 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
)) {
1043 pbuf
= _pipefile(pipecomm
, UNVOLATILE(&qbuf
),
1044 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
,
1046 action
= SEND_TOPIPE
;
1048 oldpipe
= safe_signal(SIGPIPE
, onpipe
);
1049 if (sigsetjmp(pipejmp
, 1))
1057 size_t save_qf_pfix_len
= qf
->qf_pfix_len
;
1058 off_t
*save_stats
= stats
;
1060 if (pbuf
!= origobuf
) {
1061 qf
->qf_pfix_len
= 0; /* XXX legacy (remove filter instead) */
1068 quoteflt_reset(qf
, pbuf
);
1069 while (!eof
&& fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
1071 if (_out(line
, linelen
, pbuf
, convert
, action
, qf
, stats
,
1072 &rest
) < 0 || ferror(pbuf
)) {
1073 rt
= -1; /* XXX Should bail away?! */
1077 if (!eof
&& rest
.l
!= 0) {
1087 if (pbuf
!= origobuf
) {
1088 qf
->qf_pfix_len
= save_qf_pfix_len
;
1095 safe_signal(SIGPIPE
, SIG_IGN
);
1096 Pclose(pbuf
, ispipe
);
1097 safe_signal(SIGPIPE
, oldpipe
);
1099 pipecpy(qbuf
, obuf
, origobuf
, qf
, stats
);
1102 if (iconvd
!= (iconv_t
)-1)
1103 n_iconv_close(iconvd
);
1108 static struct mimepart
*
1109 parsemsg(struct message
*mp
, enum parseflags pf
)
1111 struct mimepart
*ip
;
1113 ip
= csalloc(1, sizeof *ip
);
1114 ip
->m_flag
= mp
->m_flag
;
1115 ip
->m_have
= mp
->m_have
;
1116 ip
->m_block
= mp
->m_block
;
1117 ip
->m_offset
= mp
->m_offset
;
1118 ip
->m_size
= mp
->m_size
;
1119 ip
->m_xsize
= mp
->m_xsize
;
1120 ip
->m_lines
= mp
->m_lines
;
1121 ip
->m_xlines
= mp
->m_lines
;
1122 if (parsepart(mp
, ip
, pf
, 0) != OKAY
)
1128 parsepart(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
1133 ip
->m_ct_type
= hfield1("content-type", (struct message
*)ip
);
1134 if (ip
->m_ct_type
!= NULL
) {
1135 ip
->m_ct_type_plain
= savestr(ip
->m_ct_type
);
1136 if ((cp
= strchr(ip
->m_ct_type_plain
, ';')) != NULL
)
1138 } else if (ip
->m_parent
&& ip
->m_parent
->m_mimecontent
== MIME_DIGEST
)
1139 ip
->m_ct_type_plain
= UNCONST("message/rfc822");
1141 ip
->m_ct_type_plain
= UNCONST("text/plain");
1144 ip
->m_charset
= mime_getparam("charset", ip
->m_ct_type
);
1145 if (ip
->m_charset
== NULL
)
1146 ip
->m_charset
= charset_get_7bit();
1147 ip
->m_ct_transfer_enc
= hfield1("content-transfer-encoding",
1148 (struct message
*)ip
);
1149 ip
->m_mimeenc
= ip
->m_ct_transfer_enc
?
1150 mime_getenc(ip
->m_ct_transfer_enc
) : MIME_7B
;
1151 if ((cp
= hfield1("content-disposition", (struct message
*)ip
)) == 0 ||
1152 (ip
->m_filename
= mime_getparam("filename", cp
)) == 0)
1153 if (ip
->m_ct_type
!= NULL
)
1154 ip
->m_filename
= mime_getparam("name", ip
->m_ct_type
);
1155 ip
->m_mimecontent
= mime_classify_content_of_part(ip
);
1157 if (pf
& PARSE_PARTS
) {
1159 fprintf(stderr
, tr(36,
1160 "MIME content too deeply nested\n"));
1163 switch (ip
->m_mimecontent
) {
1165 if (pf
& PARSE_DECRYPT
) {
1167 parsepkcs7(zmp
, ip
, pf
, level
);
1170 fprintf(stderr
, tr(225,
1171 "No SSL support compiled in.\n"));
1179 case MIME_ALTERNATIVE
:
1181 _parsemultipart(zmp
, ip
, pf
, level
);
1184 parse822(zmp
, ip
, pf
, level
);
1192 newpart(struct mimepart
*ip
, struct mimepart
**np
, off_t offs
, int *part
)
1194 struct mimepart
*pp
;
1197 *np
= csalloc(1, sizeof **np
);
1198 (*np
)->m_flag
= MNOFROM
;
1199 (*np
)->m_have
= HAVE_HEADER
|HAVE_BODY
;
1200 (*np
)->m_block
= mailx_blockof(offs
);
1201 (*np
)->m_offset
= mailx_offsetof(offs
);
1204 sz
= ip
->m_partstring
? strlen(ip
->m_partstring
) : 0;
1206 (*np
)->m_partstring
= salloc(sz
);
1207 if (ip
->m_partstring
)
1208 snprintf((*np
)->m_partstring
, sz
, "%s.%u",
1209 ip
->m_partstring
, *part
);
1211 snprintf((*np
)->m_partstring
, sz
, "%u", *part
);
1213 (*np
)->m_mimecontent
= MIME_DISCARD
;
1214 (*np
)->m_parent
= ip
;
1215 if (ip
->m_multipart
) {
1216 for (pp
= ip
->m_multipart
; pp
->m_nextpart
; pp
= pp
->m_nextpart
);
1217 pp
->m_nextpart
= *np
;
1219 ip
->m_multipart
= *np
;
1223 endpart(struct mimepart
**np
, off_t xoffs
, long lines
)
1227 offs
= mailx_positionof((*np
)->m_block
, (*np
)->m_offset
);
1228 (*np
)->m_size
= (*np
)->m_xsize
= xoffs
- offs
;
1229 (*np
)->m_lines
= (*np
)->m_xlines
= lines
;
1234 parse822(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
1237 int c
, lastc
= '\n';
1241 struct mimepart
*np
;
1244 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
)
1247 lines
= ip
->m_lines
;
1248 while (cnt
&& ((c
= getc(ibuf
)) != EOF
)) {
1258 np
= csalloc(1, sizeof *np
);
1259 np
->m_flag
= MNOFROM
;
1260 np
->m_have
= HAVE_HEADER
|HAVE_BODY
;
1261 np
->m_block
= mailx_blockof(offs
);
1262 np
->m_offset
= mailx_offsetof(offs
);
1263 np
->m_size
= np
->m_xsize
= cnt
;
1264 np
->m_lines
= np
->m_xlines
= lines
;
1265 np
->m_partstring
= ip
->m_partstring
;
1267 ip
->m_multipart
= np
;
1268 if (value("rfc822-body-from_")) {
1269 substdate((struct message
*)np
);
1270 np
->m_from
= fakefrom((struct message
*)np
);
1272 parsepart(zmp
, np
, pf
, level
+1);
1277 parsepkcs7(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
1280 struct message m
, *xmp
;
1281 struct mimepart
*np
;
1284 memcpy(&m
, ip
, sizeof m
);
1285 to
= hfield1("to", zmp
);
1286 cc
= hfield1("cc", zmp
);
1287 if ((xmp
= smime_decrypt(&m
, to
, cc
, 0)) != NULL
) {
1288 np
= csalloc(1, sizeof *np
);
1289 np
->m_flag
= xmp
->m_flag
;
1290 np
->m_have
= xmp
->m_have
;
1291 np
->m_block
= xmp
->m_block
;
1292 np
->m_offset
= xmp
->m_offset
;
1293 np
->m_size
= xmp
->m_size
;
1294 np
->m_xsize
= xmp
->m_xsize
;
1295 np
->m_lines
= xmp
->m_lines
;
1296 np
->m_xlines
= xmp
->m_xlines
;
1297 np
->m_partstring
= ip
->m_partstring
;
1298 if (parsepart(zmp
, np
, pf
, level
+1) == OKAY
) {
1300 ip
->m_multipart
= np
;
1307 * Get a file for an attachment.
1310 newfile(struct mimepart
*ip
, int *ispipe
)
1312 char *f
= ip
->m_filename
;
1317 if (f
!= NULL
&& f
!= (char *)-1) {
1320 mime_fromhdr(&in
, &out
, TD_ISPR
);
1321 memcpy(f
, out
.s
, out
.l
);
1322 *(f
+ out
.l
) = '\0';
1326 if (options
& OPT_INTERACTIVE
) {
1328 jgetname
: (void)printf(tr(278, "Enter filename for part %s (%s)"),
1329 ip
->m_partstring
? ip
->m_partstring
: "?",
1330 ip
->m_ct_type_plain
);
1331 f2
= readstr_input(": ", f
!= (char *)-1 ? f
: NULL
);
1332 if (f2
== NULL
|| *f2
== '\0') {
1333 fprintf(stderr
, tr(279, "... skipping this\n"));
1335 } else if (*f2
== '|')
1336 /* Pipes are expanded by the shell */
1338 else if ((f3
= file_expand(f2
)) == NULL
)
1339 /* (Error message written by file_expand()) */
1344 if (f
== NULL
|| f
== (char *)-1)
1349 cp
= value("SHELL");
1352 fp
= Popen(f
+ 1, "w", cp
, 1);
1353 if (! (*ispipe
= (fp
!= NULL
)))
1356 if ((fp
= Fopen(f
, "w")) == NULL
)
1357 fprintf(stderr
, tr(176, "Cannot open %s\n"), f
);
1363 pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
, struct quoteflt
*qf
,
1367 size_t linesize
= 0, linelen
, cnt
;
1372 cnt
= fsize(pipebuf
);
1375 quoteflt_reset(qf
, outbuf
);
1376 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, pipebuf
, 0)
1378 if ((sz
= quoteflt_push(qf
, line
, linelen
)) < 0)
1382 if ((sz
= quoteflt_flush(qf
)) > 0)
1387 if (all_sz
> 0 && outbuf
== origobuf
)
1388 _addstats(stats
, 1, all_sz
);
1393 * Output a reasonable looking status field.
1396 statusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1402 if (mp
->m_flag
& MREAD
)
1404 if ((mp
->m_flag
& MNEW
) == 0)
1408 int i
= fprintf(obuf
, "%.*sStatus: %s\n",
1409 (int)qf
->qf_pfix_len
,
1410 (qf
->qf_pfix_len
> 0) ? qf
->qf_pfix
: 0,
1413 _addstats(stats
, 1, i
);
1418 xstatusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1422 char *xp
= xstatout
;
1424 if (mp
->m_flag
& MFLAGGED
)
1426 if (mp
->m_flag
& MANSWERED
)
1428 if (mp
->m_flag
& MDRAFTED
)
1432 int i
= fprintf(obuf
, "%.*sX-Status: %s\n",
1433 (int)qf
->qf_pfix_len
,
1434 (qf
->qf_pfix_len
> 0) ? qf
->qf_pfix
: 0,
1437 _addstats(stats
, 1, i
);
1442 put_from_(FILE *fp
, struct mimepart
*ip
, off_t
*stats
)
1444 char const *froma
, *date
, *nl
;
1447 if (ip
&& ip
->m_from
) {
1449 date
= fakedate(ip
->m_time
);
1453 date
= time_current
.tc_ctime
;
1457 i
= fprintf(fp
, "From %s %s%s", froma
, date
, nl
);
1459 _addstats(stats
, (*nl
!= '\0'), i
);