1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Mail to mail folders and displays.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 sigjmp_buf _send_pipejmp
;
61 static struct mimepart
*parsemsg(struct message
*mp
, enum parseflags pf
);
62 static enum okay
parsepart(struct message
*zmp
, struct mimepart
*ip
,
63 enum parseflags pf
, int level
);
64 static void parse822(struct message
*zmp
, struct mimepart
*ip
,
65 enum parseflags pf
, int level
);
67 static void parsepkcs7(struct message
*zmp
, struct mimepart
*ip
,
68 enum parseflags pf
, int level
);
70 static void _parsemultipart(struct message
*zmp
,
71 struct mimepart
*ip
, enum parseflags pf
, int level
);
72 static void __newpart(struct mimepart
*ip
, struct mimepart
**np
,
73 off_t offs
, int *part
);
74 static void __endpart(struct mimepart
**np
, off_t xoffs
, long lines
);
76 /* Going for user display, print Part: info string */
77 static void _print_part_info(struct str
*out
, struct mimepart
*mip
,
78 struct ignoretab
*doign
, int level
);
80 /* Query possible pipe command for MIME part */
81 static enum pipeflags
_pipecmd(char **result
, struct mimepart
const *mpp
);
83 /* Create a pipe; if mpp is not NULL, place some PIPEHOOK_* environment
84 * variables accordingly */
85 static FILE * _pipefile(char const *pipecomm
, struct mimepart
const *mpp
,
86 FILE **qbuf
, bool_t quote
, bool_t async
);
88 /* Adjust output statistics */
89 SINLINE
void _addstats(off_t
*stats
, off_t lines
, off_t bytes
);
91 /* Call mime_write() as approbiate and adjust statistics */
92 SINLINE ssize_t
_out(char const *buf
, size_t len
, FILE *fp
,
93 enum conversion convert
, enum sendaction action
,
94 struct quoteflt
*qf
, off_t
*stats
,
98 static void _send_onpipe(int signo
);
101 static int sendpart(struct message
*zmp
, struct mimepart
*ip
,
102 FILE *obuf
, struct ignoretab
*doign
,
103 struct quoteflt
*qf
, enum sendaction action
,
104 off_t
*stats
, int level
);
106 /* Get a file for an attachment */
107 static FILE * newfile(struct mimepart
*ip
, int *ispipe
);
109 static void pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
,
110 struct quoteflt
*qf
, off_t
*stats
);
112 /* Output a reasonable looking status field */
113 static void statusput(const struct message
*mp
, FILE *obuf
,
114 struct quoteflt
*qf
, off_t
*stats
);
115 static void xstatusput(const struct message
*mp
, FILE *obuf
,
116 struct quoteflt
*qf
, off_t
*stats
);
118 static void put_from_(FILE *fp
, struct mimepart
*ip
, off_t
*stats
);
120 static struct mimepart
*
121 parsemsg(struct message
*mp
, enum parseflags pf
)
126 ip
= csalloc(1, sizeof *ip
);
127 ip
->m_flag
= mp
->m_flag
;
128 ip
->m_have
= mp
->m_have
;
129 ip
->m_block
= mp
->m_block
;
130 ip
->m_offset
= mp
->m_offset
;
131 ip
->m_size
= mp
->m_size
;
132 ip
->m_xsize
= mp
->m_xsize
;
133 ip
->m_lines
= mp
->m_lines
;
134 ip
->m_xlines
= mp
->m_lines
;
135 if (parsepart(mp
, ip
, pf
, 0) != OKAY
)
142 parsepart(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
149 ip
->m_ct_type
= hfield1("content-type", (struct message
*)ip
);
150 if (ip
->m_ct_type
!= NULL
) {
151 cp_b
= ip
->m_ct_type_plain
= savestr(ip
->m_ct_type
);
152 if ((cp
= strchr(cp_b
, ';')) != NULL
)
154 cp
= cp_b
+ strlen(cp_b
);
155 while (cp
> cp_b
&& blankchar(cp
[-1]))
158 } else if (ip
->m_parent
!= NULL
&&
159 ip
->m_parent
->m_mimecontent
== MIME_DIGEST
)
160 ip
->m_ct_type_plain
= UNCONST("message/rfc822");
162 ip
->m_ct_type_plain
= UNCONST("text/plain");
163 ip
->m_ct_type_usr_ovwr
= NULL
;
165 if (ip
->m_ct_type
!= NULL
)
166 ip
->m_charset
= mime_getparam("charset", ip
->m_ct_type
);
167 if (ip
->m_charset
== NULL
)
168 ip
->m_charset
= charset_get_7bit();
170 ip
->m_ct_transfer_enc
= hfield1("content-transfer-encoding",
171 (struct message
*)ip
);
172 ip
->m_mimeenc
= (ip
->m_ct_transfer_enc
!= NULL
)
173 ? mime_getenc(ip
->m_ct_transfer_enc
) : MIME_7B
;
175 if (((cp
= hfield1("content-disposition", (struct message
*)ip
)) == NULL
||
176 (ip
->m_filename
= mime_getparam("filename", cp
)) == NULL
) &&
177 ip
->m_ct_type
!= NULL
)
178 ip
->m_filename
= mime_getparam("name", ip
->m_ct_type
);
180 ip
->m_mimecontent
= mime_classify_content_of_part(ip
);
182 if (pf
& PARSE_PARTS
) {
183 if (level
> 9999) { /* TODO MAGIC */
184 fprintf(stderr
, _("MIME content too deeply nested\n"));
187 switch (ip
->m_mimecontent
) {
189 if (pf
& PARSE_DECRYPT
) {
191 parsepkcs7(zmp
, ip
, pf
, level
);
194 fprintf(stderr
, _("No SSL support compiled in.\n"));
202 case MIME_ALTERNATIVE
:
204 _parsemultipart(zmp
, ip
, pf
, level
);
207 parse822(zmp
, ip
, pf
, level
);
218 parse822(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
229 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
)
234 while (cnt
&& ((c
= getc(ibuf
)) != EOF
)) {
245 np
= csalloc(1, sizeof *np
);
246 np
->m_flag
= MNOFROM
;
247 np
->m_have
= HAVE_HEADER
| HAVE_BODY
;
248 np
->m_block
= mailx_blockof(offs
);
249 np
->m_offset
= mailx_offsetof(offs
);
250 np
->m_size
= np
->m_xsize
= cnt
;
251 np
->m_lines
= np
->m_xlines
= lines
;
252 np
->m_partstring
= ip
->m_partstring
;
254 ip
->m_multipart
= np
;
256 if (ok_blook(rfc822_body_from_
)) {
257 substdate((struct message
*)np
);
258 np
->m_from
= fakefrom((struct message
*)np
);/* TODO strip MNOFROM flag? */
261 parsepart(zmp
, np
, pf
, level
+ 1);
268 parsepkcs7(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
271 struct message m
, *xmp
;
276 memcpy(&m
, ip
, sizeof m
);
277 to
= hfield1("to", zmp
);
278 cc
= hfield1("cc", zmp
);
280 if ((xmp
= smime_decrypt(&m
, to
, cc
, 0)) != NULL
) {
281 np
= csalloc(1, sizeof *np
);
282 np
->m_flag
= xmp
->m_flag
;
283 np
->m_have
= xmp
->m_have
;
284 np
->m_block
= xmp
->m_block
;
285 np
->m_offset
= xmp
->m_offset
;
286 np
->m_size
= xmp
->m_size
;
287 np
->m_xsize
= xmp
->m_xsize
;
288 np
->m_lines
= xmp
->m_lines
;
289 np
->m_xlines
= xmp
->m_xlines
;
290 np
->m_partstring
= ip
->m_partstring
;
292 if (parsepart(zmp
, np
, pf
, level
+ 1) == OKAY
) {
294 ip
->m_multipart
= np
;
302 _parsemultipart(struct message
*zmp
, struct mimepart
*ip
, enum parseflags pf
,
305 /* TODO Instead of the recursive multiple run parse we have today,
306 * TODO the send/MIME layer rewrite must create a "tree" of parts with
307 * TODO a single-pass parse, then address each part directly as
308 * TODO necessary; since boundaries start with -- and the content
309 * TODO rather forms a stack this is pretty cheap indeed! */
310 struct mimepart
*np
= NULL
;
311 char *boundary
, *line
= NULL
;
312 size_t linesize
= 0, linelen
, cnt
, boundlen
;
319 if ((boundary
= mime_get_boundary(ip
->m_ct_type
, &linelen
)) == NULL
)
323 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
)
327 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0))
332 __newpart(ip
, &np
, offs
, NULL
);
333 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
334 /* XXX linelen includes LF */
335 if (!((lines
> 0 || part
== 0) && linelen
> boundlen
&&
336 !strncmp(line
, boundary
, boundlen
))) {
341 /* Subpart boundary? */
342 if (line
[boundlen
] == '\n') {
345 __endpart(&np
, offs
- boundlen
- 2, lines
);
346 __newpart(ip
, &np
, offs
- boundlen
- 2, NULL
);
348 __endpart(&np
, offs
, 2);
349 __newpart(ip
, &np
, offs
, &part
);
354 /* Final boundary? Be aware of cases where there is no separating
355 * newline in between boundaries, as has been seen in a message with
356 * "Content-Type: multipart/appledouble;" */
357 if (linelen
< boundlen
+ 2)
359 linelen
-= boundlen
+ 2;
360 if (line
[boundlen
] != '-' || line
[boundlen
+ 1] != '-' ||
361 (linelen
> 0 && line
[boundlen
+ 2] != '\n'))
365 __endpart(&np
, offs
- boundlen
- 4, lines
);
366 __newpart(ip
, &np
, offs
- boundlen
- 4, NULL
);
368 __endpart(&np
, offs
+ cnt
, 2);
373 __endpart(&np
, offs
, lines
);
376 for (np
= ip
->m_multipart
; np
!= NULL
; np
= np
->m_nextpart
)
377 if (np
->m_mimecontent
!= MIME_DISCARD
)
378 parsepart(zmp
, np
, pf
, level
+ 1);
385 __newpart(struct mimepart
*ip
, struct mimepart
**np
, off_t offs
, int *part
)
391 *np
= csalloc(1, sizeof **np
);
392 (*np
)->m_flag
= MNOFROM
;
393 (*np
)->m_have
= HAVE_HEADER
| HAVE_BODY
;
394 (*np
)->m_block
= mailx_blockof(offs
);
395 (*np
)->m_offset
= mailx_offsetof(offs
);
399 sz
= (ip
->m_partstring
!= NULL
) ? strlen(ip
->m_partstring
) : 0;
401 (*np
)->m_partstring
= salloc(sz
);
402 if (ip
->m_partstring
)
403 snprintf((*np
)->m_partstring
, sz
, "%s.%u", ip
->m_partstring
, *part
);
405 snprintf((*np
)->m_partstring
, sz
, "%u", *part
);
407 (*np
)->m_mimecontent
= MIME_DISCARD
;
408 (*np
)->m_parent
= ip
;
410 if (ip
->m_multipart
) {
411 for (pp
= ip
->m_multipart
; pp
->m_nextpart
!= NULL
; pp
= pp
->m_nextpart
)
413 pp
->m_nextpart
= *np
;
415 ip
->m_multipart
= *np
;
420 __endpart(struct mimepart
**np
, off_t xoffs
, long lines
)
425 offs
= mailx_positionof((*np
)->m_block
, (*np
)->m_offset
);
426 (*np
)->m_size
= (*np
)->m_xsize
= xoffs
- offs
;
427 (*np
)->m_lines
= (*np
)->m_xlines
= lines
;
433 _print_part_info(struct str
*out
, struct mimepart
*mip
,
434 struct ignoretab
*doign
, int level
)
436 struct str ct
= {NULL
, 0}, cd
= {NULL
, 0};
438 struct str
const *cpre
, *csuf
;
442 if (is_ign("content-type", 12, doign
)) {
445 if ((out
->s
= mip
->m_ct_type_usr_ovwr
) != NULL
)
449 out
->s
= mip
->m_ct_type_plain
;
451 out
->l
= strlen(out
->s
);
453 ct
.s
= ac_alloc(out
->l
+ 2 + addon
+1);
462 if (is_prefix("application/", out
->s
)) {
463 memcpy(ct
.s
+ ct
.l
, "appl../", 7);
467 out
->l
= MIN(out
->l
, 17 - addon
);
469 out
->l
= MIN(out
->l
, 24 - addon
);
470 memcpy(ct
.s
+ ct
.l
, out
->s
, out
->l
);
476 if (is_ign("content-disposition", 19, doign
) && mip
->m_filename
!= NULL
) {
479 ti
.l
= strlen(ti
.s
= mip
->m_filename
);
480 mime_fromhdr(&ti
, &to
, TD_ISPR
| TD_ICONV
| TD_DELCTRL
);
482 cd
.s
= ac_alloc(2 + 32 +1); /* FIXME was 25.. UNI: USE VISUAL WIDTH!!! */
485 cd
.l
= 2 + field_put_bidi_clip(cd
.s
+ 2, 32 +1, to
.s
, to
.l
);
490 /* Take care of "99.99", i.e., 5 */
491 if ((ps
= mip
->m_partstring
) == NULL
|| ps
[0] == '\0')
495 cpre
= colour_get(COLOURSPEC_PARTINFO
);
496 csuf
= colour_get(COLOURSPEC_RESET
);
501 /* Assume maximum possible sizes for 64 bit integers here to avoid any
502 * buffer overflows just in case we have a bug somewhere and / or the
503 * snprintf() is our internal version that doesn't really provide hard
504 * buffer cuts TODO ensure upper bound on numbers, use 9999999 else */
505 #define __msg "%s%s[-- #%s : %lu/%lu%s%s --]%s\n"
506 out
->l
= sizeof(__msg
) +
508 (cpre
!= NULL
? cpre
->l
+ csuf
->l
: 0) +
510 strlen(ps
) + 2*21 + ct
.l
+ cd
.l
+1;
511 out
->s
= salloc(out
->l
);
512 out
->l
= snprintf(out
->s
, out
->l
, __msg
,
513 (level
|| (ps
[0] != '1' && ps
[1] == '\0') ? "\n" : ""),
514 (cpre
!= NULL
? cpre
->s
: ""),
515 ps
, (ul_it
)mip
->m_lines
, (ul_it
)mip
->m_size
,
516 (ct
.s
!= NULL
? ct
.s
: ""),
517 (cd
.s
!= NULL
? cd
.s
: ""),
518 (csuf
!= NULL
? csuf
->s
: ""));
519 out
->s
[out
->l
] = '\0';
529 static enum pipeflags
530 _pipecmd(char **result
, struct mimepart
const *mpp
)
538 /* Do we have any handler for this part? */
539 if ((cp
= mimepart_get_handler(mpp
)) == NULL
)
541 /* User specified a command, inspect for special cases */
542 else if (cp
[0] != '@') {
543 /* Normal command line */
547 else if (*++cp
== '\0')
548 /* Treat as plain text */
550 else if (!msglist_is_single
) {
551 /* Viewing multiple messages in one go, don't block system */
553 *result
= UNCONST(_("[Directly address message only to display this]\n"));
555 /* Viewing a single message only */
556 /* TODO send/MIME layer rewrite: when we have a single-pass parser
557 * TODO then the parsing phase and the send phase will be separated;
558 * TODO that allows us to ask a user *before* we start the send, i.e.,
559 * TODO *before* a pager pipe is setup */
561 /* Asynchronous command, normal command line */
562 ret
= PIPE_ASYNC
, *result
= ++cp
;
564 ret
= PIPE_COMM
, *result
= cp
;
571 _pipefile(char const *pipecomm
, struct mimepart
const *mpp
, FILE **qbuf
,
572 bool_t quote
, bool_t async
)
575 char const *env_addon
[8], *sh
;
583 if ((*qbuf
= Ftmp(NULL
, "sendp", OF_RDWR
| OF_UNLINK
| OF_REGISTER
,
585 perror(_("tmpfile"));
592 if (mpp
== NULL
|| (cp
= mpp
->m_filename
) == NULL
)
594 env_addon
[0] = str_concat_csvl(&s
, PIPEHOOK_FILENAME
, "=", cp
, NULL
)->s
;
596 /* NAIL_FILENAME_GENERATED */
597 s
.s
= getrandstring(8);
600 else if (*cp
== '\0') {
601 if ( (((cp
= mpp
->m_ct_type_usr_ovwr
) == NULL
|| *cp
== '\0') &&
602 ((cp
= mpp
->m_ct_type_plain
) == NULL
|| *cp
== '\0')) ||
603 ((sh
= strrchr(cp
, '/')) == NULL
|| *++sh
== '\0'))
607 cp
= savecat(cp
, sh
);
610 env_addon
[1] = str_concat_csvl(&s
, PIPEHOOK_FILENAME_GENERATED
, "=", cp
,
613 /* NAIL_CONTENT{,_EVIDENCE} */
614 if (mpp
== NULL
|| (cp
= mpp
->m_ct_type_plain
) == NULL
)
616 env_addon
[2] = str_concat_csvl(&s
, PIPEHOOK_CONTENT
, "=", cp
, NULL
)->s
;
618 if (mpp
!= NULL
&& mpp
->m_ct_type_usr_ovwr
!= NULL
)
619 cp
= mpp
->m_ct_type_usr_ovwr
;
620 env_addon
[3] = str_concat_csvl(&s
, PIPEHOOK_CONTENT_EVIDENCE
, "=", cp
,
625 if ((sh
= ok_vlook(SHELL
)) == NULL
)
627 if ((rbuf
= Popen(pipecomm
, "W", sh
, env_addon
, (async
? -1 : fileno(*qbuf
)))
640 _addstats(off_t
*stats
, off_t lines
, off_t bytes
)
652 _out(char const *buf
, size_t len
, FILE *fp
, enum conversion convert
, enum
653 sendaction action
, struct quoteflt
*qf
, off_t
*stats
, struct str
*rest
)
661 Well
... it turns out to
not work like that since of course a valid
662 RFC
4155 compliant parser
, like S
-nail
, takes care
for From_ lines only
663 after an empty line has been seen
, which cannot be detected that easily
665 ifdef HAVE_DEBUG
/* TODO assert legacy */
666 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
667 * TODO other input situations handle RFC 4155 OR, if newly generated,
668 * TODO enforce quoted-printable if there is From_, as "required" by
669 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
670 * TODO if it may happen in this path, we should just treat decryption
671 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
672 if (action
== SEND_MBOX
|| action
== SEND_DECRYPT
)
673 assert(!is_head(buf
, len
));
675 if ((/*action == SEND_MBOX ||*/ action
== SEND_DECRYPT
) &&
682 flags
= ((int)action
& _TD_EOF
);
684 n
= mime_write(buf
, len
, fp
,
685 action
== SEND_MBOX
? CONV_NONE
: convert
,
686 flags
| ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
687 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
)
689 : (action
== SEND_TOSRCH
|| action
== SEND_TOPIPE
)
690 ? TD_ICONV
: (action
== SEND_SHOW
? TD_ISPR
: TD_NONE
)),
695 sz
= (ssize_t
)((size_t)sz
+ n
);
697 if (stats
!= NULL
&& stats
[0] != -1)
698 for (cp
= buf
; PTRCMP(cp
, <, buf
+ sz
); ++cp
)
701 _addstats(stats
, n
, sz
);
708 _send_onpipe(int signo
)
710 NYD_X
; /* Signal handler */
712 siglongjmp(_send_pipejmp
, 1);
716 sendpart(struct message
*zmp
, struct mimepart
*ip
, FILE * volatile obuf
,
717 struct ignoretab
*doign
, struct quoteflt
*qf
,
718 enum sendaction
volatile action
, off_t
*volatile stats
, int level
)
720 int volatile ispipe
, rv
= 0;
722 char *line
= NULL
, *cp
, *cp2
, *start
, *pipecomm
= NULL
;
723 size_t linesize
= 0, linelen
, cnt
;
724 int dostat
, infld
= 0, ignoring
= 1, isenc
, c
;
725 struct mimepart
*volatile np
;
726 FILE * volatile ibuf
= NULL
, * volatile pbuf
= obuf
, * volatile qbuf
= obuf
,
728 enum conversion
volatile convert
;
729 sighandler_type
volatile oldpipe
= SIG_DFL
;
733 if (ip
->m_mimecontent
== MIME_PKCS7
&& ip
->m_multipart
&&
734 action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
740 if (!is_ign("status", 6, doign
))
742 if (!is_ign("x-status", 8, doign
))
747 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
) {
753 if (ip
->m_mimecontent
== MIME_DISCARD
)
756 if (!(ip
->m_flag
& MNOFROM
))
757 while (cnt
&& (c
= getc(ibuf
)) != EOF
) {
763 convert
= (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
764 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
765 action
== SEND_TOSRCH
)
766 ? CONV_FROMHDR
: CONV_NONE
;
768 /* Work the headers */
769 quoteflt_reset(qf
, obuf
);
770 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
772 if (line
[0] == '\n') {
773 /* If line is blank, we've reached end of headers, so force out
774 * status: field and note that we are no longer in header fields */
776 statusput(zmp
, obuf
, qf
, stats
);
778 xstatusput(zmp
, obuf
, qf
, stats
);
779 if (doign
!= allignore
)
780 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
785 if (infld
&& blankchar(line
[0])) {
786 /* If this line is a continuation (SP / HT) of a previous header
787 * field, determine if the start of the line is a MIME encoded word */
789 for (cp
= line
; blankchar(*cp
); ++cp
);
790 if (cp
> line
&& linelen
- PTR2SIZE(cp
- line
) > 8 &&
791 cp
[0] == '=' && cp
[1] == '?')
795 /* Pick up the header field if we have one */
796 for (cp
= line
; (c
= *cp
& 0377) && c
!= ':' && !spacechar(c
); ++cp
)
799 while (spacechar(*cp
))
801 if (cp
[0] != ':' && level
== 0 && lineno
== 1) {
802 /* Not a header line, force out status: This happens in uucp style
803 * mail where there are no headers at all */
805 statusput(zmp
, obuf
, qf
, stats
);
807 xstatusput(zmp
, obuf
, qf
, stats
);
808 if (doign
!= allignore
)
809 _out("\n", 1, obuf
, CONV_NONE
,SEND_MBOX
, qf
, stats
, NULL
);
813 /* If it is an ignored field and we care about such things, skip it.
814 * Misuse dostat also for another bit xxx use a bitenum + for more */
815 if (ok_blook(keep_content_length
))
818 *cp2
= 0; /* temporarily null terminate */
819 if ((doign
&& is_ign(line
, PTR2SIZE(cp2
- line
), doign
)) ||
820 (action
== SEND_MBOX
&& !(dostat
& (1 << 2)) &&
821 (!asccasecmp(line
, "content-length") ||
822 !asccasecmp(line
, "lines"))))
824 else if (!asccasecmp(line
, "status")) {
825 /* If field is "status," go compute and print real Status: field */
827 statusput(zmp
, obuf
, qf
, stats
);
831 } else if (!asccasecmp(line
, "x-status")) {
832 /* If field is "status," go compute and print real Status: field */
834 xstatusput(zmp
, obuf
, qf
, stats
);
840 /* For colourization we need the complete line, so save it */
841 /* XXX This is all temporary (colour belongs into backend), so
842 * XXX use pipecomm as a temporary storage in the meanwhile */
844 if (colour_table
!= NULL
)
845 pipecomm
= savestrbuf(line
, PTR2SIZE(cp2
- line
));
853 /* Determine if the end of the line is a MIME encoded word */
854 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
855 * TODO with header follow lines, and it should be up to the backend
856 * TODO what happens and what not, i.e., it doesn't matter wether it's
857 * TODO a MIME-encoded word or not, as long as a single separating space
858 * TODO remains in between lines (the MIME stuff will correctly remove
859 * TODO whitespace in between multiple adjacent encoded words) */
861 if (cnt
&& (c
= getc(ibuf
)) != EOF
) {
863 cp
= line
+ linelen
- 1;
864 if (linelen
> 0 && *cp
== '\n')
866 while (cp
>= line
&& whitechar(*cp
))
868 if (PTR2SIZE(cp
- line
> 8) && cp
[0] == '=' && cp
[-1] == '?')
875 size_t len
= linelen
;
877 if (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
878 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
879 action
== SEND_TOSRCH
) {
880 /* Strip blank characters if two MIME-encoded words follow on
881 * continuing lines */
883 while (len
> 0 && blankchar(*start
)) {
888 if (len
> 0 && start
[len
- 1] == '\n')
890 while (len
> 0 && blankchar(start
[len
- 1]))
895 bool_t colour_stripped
= FAL0
;
896 if (pipecomm
!= NULL
) {
897 colour_put_header(obuf
, pipecomm
);
898 if (len
> 0 && start
[len
- 1] == '\n') {
899 colour_stripped
= TRU1
;
904 _out(start
, len
, obuf
, convert
, action
, qf
, stats
, NULL
);
906 if (pipecomm
!= NULL
) {
907 colour_reset(obuf
); /* XXX reset after \n!! */
926 switch (ip
->m_mimecontent
) {
930 case SEND_TODISP_ALL
:
933 if (ok_blook(rfc822_body_from_
)) {
934 if (qf
->qf_pfix_len
> 0) {
935 size_t i
= fwrite(qf
->qf_pfix
, sizeof *qf
->qf_pfix
,
936 qf
->qf_pfix_len
, obuf
);
937 if (i
== qf
->qf_pfix_len
)
938 _addstats(stats
, 0, i
);
940 put_from_(obuf
, ip
->m_multipart
, stats
);
948 if (ok_blook(rfc822_body_from_
))
949 put_from_(obuf
, ip
->m_multipart
, stats
);
959 case MIME_TEXT_PLAIN
:
962 case SEND_TODISP_ALL
:
966 switch (_pipecmd(&pipecomm
, ip
)) {
968 _out(pipecomm
, strlen(pipecomm
), obuf
, CONV_NONE
, SEND_MBOX
, qf
,
970 /* We would print this as plain text, so better force going home */
986 if (action
!= SEND_DECRYPT
)
990 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&&
991 action
!= SEND_SHOW
&& ip
->m_multipart
!= NULL
)
997 case SEND_TODISP_ALL
:
1001 switch (_pipecmd(&pipecomm
, ip
)) {
1003 _out(pipecomm
, strlen(pipecomm
), obuf
, CONV_NONE
, SEND_MBOX
, qf
,
1014 goto jcopyout
; /* break; break; */
1016 if (pipecomm
!= NULL
)
1018 if (level
== 0 && cnt
) {
1019 char const *x
= _("[Binary content]\n");
1020 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
1033 case MIME_ALTERNATIVE
:
1034 if ((action
== SEND_TODISP
|| action
== SEND_QUOTE
) &&
1035 !ok_blook(print_alternatives
)) {
1036 bool_t doact
= FAL0
;
1038 for (np
= ip
->m_multipart
; np
!= NULL
; np
= np
->m_nextpart
)
1039 if (np
->m_mimecontent
== MIME_TEXT_PLAIN
)
1042 for (np
= ip
->m_multipart
; np
!= NULL
; np
= np
->m_nextpart
) {
1043 if (np
->m_ct_type_plain
!= NULL
&& action
!= SEND_QUOTE
) {
1044 _print_part_info(&rest
, np
, doign
, level
);
1045 _out(rest
.s
, rest
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
1048 if (doact
&& np
->m_mimecontent
== MIME_TEXT_PLAIN
) {
1050 rv
= sendpart(zmp
, np
, obuf
, doign
, qf
, action
, stats
,
1052 quoteflt_reset(qf
, origobuf
);
1065 case SEND_TODISP_ALL
:
1067 case SEND_QUOTE_ALL
:
1073 if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
) &&
1074 ip
->m_multipart
!= NULL
&&
1075 ip
->m_multipart
->m_mimecontent
== MIME_DISCARD
&&
1076 ip
->m_multipart
->m_nextpart
== NULL
) {
1077 char const *x
= _("[Missing multipart boundary - use \"show\" "
1078 "to display the raw message]\n");
1079 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
1082 for (np
= ip
->m_multipart
; np
!= NULL
; np
= np
->m_nextpart
) {
1083 if (np
->m_mimecontent
== MIME_DISCARD
&& action
!= SEND_DECRYPT
)
1088 if (np
->m_partstring
&& !strcmp(np
->m_partstring
, "1"))
1091 if ((obuf
= newfile(np
, UNVOLATILE(&ispipe
))) == NULL
)
1095 if (sigsetjmp(_send_pipejmp
, 1)) {
1099 oldpipe
= safe_signal(SIGPIPE
, &_send_onpipe
);
1102 case SEND_TODISP_ALL
:
1103 case SEND_QUOTE_ALL
:
1104 if (ip
->m_mimecontent
!= MIME_MULTI
&&
1105 ip
->m_mimecontent
!= MIME_ALTERNATIVE
&&
1106 ip
->m_mimecontent
!= MIME_DIGEST
)
1108 _print_part_info(&rest
, np
, doign
, level
);
1109 _out(rest
.s
, rest
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
1123 if (sendpart(zmp
, np
, obuf
, doign
, qf
, action
, stats
, level
+1) < 0)
1125 quoteflt_reset(qf
, origobuf
);
1126 if (action
== SEND_QUOTE
)
1128 if (action
== SEND_TOFILE
&& obuf
!= origobuf
) {
1133 safe_signal(SIGPIPE
, SIG_IGN
);
1135 safe_signal(SIGPIPE
, oldpipe
);
1147 /* Copy out message body */
1149 if (doign
== allignore
&& level
== 0) /* skip final blank line */
1151 switch (ip
->m_mimeenc
) {
1158 convert
= CONV_NONE
;
1161 convert
= CONV_FROMQP
;
1164 switch (ip
->m_mimecontent
) {
1166 case MIME_TEXT_PLAIN
:
1167 case MIME_TEXT_HTML
:
1168 convert
= CONV_FROMB64_T
;
1171 convert
= CONV_FROMB64
;
1175 convert
= CONV_NONE
;
1178 if (action
== SEND_DECRYPT
|| action
== SEND_MBOX
||
1179 action
== SEND_RFC822
|| action
== SEND_SHOW
)
1180 convert
= CONV_NONE
;
1182 if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
1183 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
1184 action
== SEND_TOSRCH
) &&
1185 (ip
->m_mimecontent
== MIME_TEXT_PLAIN
||
1186 ip
->m_mimecontent
== MIME_TEXT_HTML
||
1187 ip
->m_mimecontent
== MIME_TEXT
)) {
1188 char const *tcs
= charset_get_lc();
1190 if (iconvd
!= (iconv_t
)-1)
1191 n_iconv_close(iconvd
);
1192 /* TODO Since Base64 has an odd 4:3 relation in between input
1193 * TODO and output an input line may end with a partial
1194 * TODO multibyte character; this is no problem at all unless
1195 * TODO we send to the display or whatever, i.e., ensure
1196 * TODO makeprint() or something; to avoid this trap, *force*
1197 * TODO iconv(), in which case this layer will handle leftovers
1199 if (convert
== CONV_FROMB64_T
|| (asccasecmp(tcs
, ip
->m_charset
) &&
1200 asccasecmp(charset_get_7bit(), ip
->m_charset
))) {
1201 iconvd
= n_iconv_open(tcs
, ip
->m_charset
);
1202 /* XXX Don't bail out if we cannot iconv(3) here;
1203 * XXX alternatively we could avoid trying to open
1204 * XXX if ip->m_charset is "unknown-8bit", which was
1205 * XXX the one that has bitten me?? */
1207 * TODO errors should DEFINETELY not be scrolled away!
1208 * TODO what about an error buffer (think old shsp(1)),
1209 * TODO re-dump errors since last snapshot when the
1210 * TODO command loop enters again? i.e., at least print
1211 * TODO "There were errors ?" before the next prompt,
1212 * TODO so that the user can look at the error buffer?
1214 if (iconvd
== (iconv_t
)-1 && errno
== EINVAL
) {
1215 fprintf(stderr
, _("Cannot convert from %s to %s\n"),
1216 ip
->m_charset
, tcs
);
1217 /*rv = 1; goto jleave;*/
1223 if (pipecomm
!= NULL
&& (action
== SEND_TODISP
||
1224 action
== SEND_TODISP_ALL
|| action
== SEND_QUOTE
||
1225 action
== SEND_QUOTE_ALL
)) {
1227 pbuf
= _pipefile(pipecomm
, ip
, UNVOLATILE(&qbuf
),
1228 (action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
), !ispipe
);
1229 action
= SEND_TOPIPE
;
1231 oldpipe
= safe_signal(SIGPIPE
, &_send_onpipe
);
1232 if (sigsetjmp(_send_pipejmp
, 1))
1240 ui32_t save_qf_pfix_len
= qf
->qf_pfix_len
;
1241 off_t
*save_stats
= stats
;
1243 if (pbuf
!= origobuf
) {
1244 qf
->qf_pfix_len
= 0; /* XXX legacy (remove filter instead) */
1251 quoteflt_reset(qf
, pbuf
);
1252 while (!eof
&& fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
1254 if (_out(line
, linelen
, pbuf
, convert
, action
, qf
, stats
, &rest
) < 0 ||
1256 rv
= -1; /* XXX Should bail away?! */
1260 if (!eof
&& rest
.l
!= 0) {
1270 if (pbuf
!= origobuf
) {
1271 qf
->qf_pfix_len
= save_qf_pfix_len
;
1279 safe_signal(SIGPIPE
, SIG_IGN
);
1280 Pclose(pbuf
, ispipe
);
1281 safe_signal(SIGPIPE
, oldpipe
);
1283 pipecpy(qbuf
, obuf
, origobuf
, qf
, stats
);
1286 if (iconvd
!= (iconv_t
)-1)
1287 n_iconv_close(iconvd
);
1295 newfile(struct mimepart
*ip
, int *ispipe
)
1305 if (f
!= NULL
&& f
!= (char*)-1) {
1308 mime_fromhdr(&in
, &out
, TD_ISPR
);
1309 memcpy(f
, out
.s
, out
.l
);
1310 *(f
+ out
.l
) = '\0';
1314 if (options
& OPT_INTERACTIVE
) {
1317 printf(_("Enter filename for part %s (%s)"),
1318 (ip
->m_partstring
!= NULL
) ? ip
->m_partstring
: "?",
1319 ip
->m_ct_type_plain
);
1320 f2
= readstr_input(": ", (f
!= (char*)-1) ? f
: NULL
);
1321 if (f2
== NULL
|| *f2
== '\0') {
1322 fprintf(stderr
, _("... skipping this\n"));
1325 } else if (*f2
== '|')
1326 /* Pipes are expanded by the shell */
1328 else if ((f3
= file_expand(f2
)) == NULL
)
1329 /* (Error message written by file_expand()) */
1334 if (f
== NULL
|| f
== (char*)-1) {
1341 cp
= ok_vlook(SHELL
);
1344 fp
= Popen(f
+ 1, "w", cp
, NULL
, 1);
1345 if (!(*ispipe
= (fp
!= NULL
)))
1348 if ((fp
= Fopen(f
, "w")) == NULL
)
1349 fprintf(stderr
, _("Cannot open `%s'\n"), f
);
1357 pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
, struct quoteflt
*qf
,
1360 char *line
= NULL
; /* TODO line pool */
1361 size_t linesize
= 0, linelen
, cnt
;
1367 cnt
= fsize(pipebuf
);
1370 quoteflt_reset(qf
, outbuf
);
1371 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, pipebuf
, 0) != NULL
) {
1372 if ((sz
= quoteflt_push(qf
, line
, linelen
)) < 0)
1376 if ((sz
= quoteflt_flush(qf
)) > 0)
1381 if (all_sz
> 0 && outbuf
== origobuf
)
1382 _addstats(stats
, 1, all_sz
);
1388 statusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1391 char statout
[3], *cp
= statout
;
1394 if (mp
->m_flag
& MREAD
)
1396 if (!(mp
->m_flag
& MNEW
))
1400 int i
= fprintf(obuf
, "%.*sStatus: %s\n", (int)qf
->qf_pfix_len
,
1401 (qf
->qf_pfix_len
> 0 ? qf
->qf_pfix
: 0), statout
);
1403 _addstats(stats
, 1, i
);
1409 xstatusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1413 char *xp
= xstatout
;
1416 if (mp
->m_flag
& MFLAGGED
)
1418 if (mp
->m_flag
& MANSWERED
)
1420 if (mp
->m_flag
& MDRAFTED
)
1424 int i
= fprintf(obuf
, "%.*sX-Status: %s\n", (int)qf
->qf_pfix_len
,
1425 (qf
->qf_pfix_len
> 0 ? qf
->qf_pfix
: 0), xstatout
);
1427 _addstats(stats
, 1, i
);
1433 put_from_(FILE *fp
, struct mimepart
*ip
, off_t
*stats
)
1435 char const *froma
, *date
, *nl
;
1439 if (ip
!= NULL
&& ip
->m_from
!= NULL
) {
1441 date
= fakedate(ip
->m_time
);
1445 date
= time_current
.tc_ctime
;
1449 colour_put(fp
, COLOURSPEC_FROM_
);
1450 i
= fprintf(fp
, "From %s %s%s", froma
, date
, nl
);
1453 _addstats(stats
, (*nl
!= '\0'), i
);
1458 sendmp(struct message
*mp
, FILE *obuf
, struct ignoretab
*doign
,
1459 char const *prefix
, enum sendaction action
, off_t
*stats
)
1465 struct mimepart
*ip
;
1469 if (mp
== dot
&& action
!= SEND_TOSRCH
)
1472 stats
[0] = stats
[1] = 0;
1473 quoteflt_init(&qf
, prefix
);
1475 /* First line is the From_ line, so no headers there to worry about */
1476 if ((ibuf
= setinput(&mb
, mp
, NEED_BODY
)) == NULL
)
1482 struct str
const *cpre
, *csuf
;
1484 cpre
= colour_get(COLOURSPEC_FROM_
);
1485 csuf
= colour_get(COLOURSPEC_RESET
);
1489 if (mp
->m_flag
& MNOFROM
) {
1490 if (doign
!= allignore
&& doign
!= fwdignore
&& action
!= SEND_RFC822
)
1491 sz
= fprintf(obuf
, "%s%.*sFrom %s %s%s\n",
1492 (cpre
!= NULL
? cpre
->s
: ""),
1493 (int)qf
.qf_pfix_len
, (qf
.qf_pfix_len
!= 0 ? qf
.qf_pfix
: ""),
1494 fakefrom(mp
), fakedate(mp
->m_time
),
1495 (csuf
!= NULL
? csuf
->s
: ""));
1497 if (doign
!= allignore
&& doign
!= fwdignore
&& action
!= SEND_RFC822
) {
1498 if (qf
.qf_pfix_len
> 0) {
1499 i
= fwrite(qf
.qf_pfix
, sizeof *qf
.qf_pfix
, qf
.qf_pfix_len
, obuf
);
1500 if (i
!= qf
.qf_pfix_len
)
1506 fputs(cpre
->s
, obuf
);
1507 cpre
= (struct str
const*)0x1;
1512 while (cnt
> 0 && (c
= getc(ibuf
)) != EOF
) {
1513 if (doign
!= allignore
&& doign
!= fwdignore
&&
1514 action
!= SEND_RFC822
) {
1516 if (c
== '\n' && csuf
!= NULL
) {
1517 cpre
= (struct str
const*)0x1;
1518 fputs(csuf
->s
, obuf
);
1530 if (csuf
!= NULL
&& cpre
!= (struct str
const*)0x1)
1531 fputs(csuf
->s
, obuf
);
1536 _addstats(stats
, 1, sz
);
1539 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
1540 pf
|= PARSE_DECRYPT
| PARSE_PARTS
;
1541 if ((ip
= parsemsg(mp
, pf
)) == NULL
)
1544 rv
= sendpart(mp
, ip
, obuf
, doign
, &qf
, action
, stats
, 0);
1546 quoteflt_destroy(&qf
);