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 - 2015 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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 #ifndef HAVE_AMALGAMATION
43 PIPE_NULL
, /* No pipe- mimetype handler */
44 PIPE_COMM
, /* Normal command */
45 PIPE_ASYNC
, /* Normal command, run asynchronous */
46 PIPE_TEXT
, /* @ special command to force treatment as text */
47 PIPE_MSG
/* Display message (returned as command string) */
50 static sigjmp_buf _send_pipejmp
;
52 /* Going for user display, print Part: info string */
53 static void _print_part_info(FILE *obuf
, struct mimepart
const *mpp
,
54 struct ignoretab
*doign
, int level
,
55 struct quoteflt
*qf
, ui64_t
*stats
);
57 /* Query possible pipe command for MIME part */
58 static enum pipeflags
_pipecmd(char const **result
, struct mimepart
const *mpp
);
60 /* Create a pipe; if mpp is not NULL, place some NAILENV_* environment
61 * variables accordingly */
62 static FILE * _pipefile(char const *pipecomm
, struct mimepart
const *mpp
,
63 FILE **qbuf
, bool_t quote
, bool_t async
);
65 /* Call mime_write() as approbiate and adjust statistics */
66 SINLINE ssize_t
_out(char const *buf
, size_t len
, FILE *fp
,
67 enum conversion convert
, enum sendaction action
,
68 struct quoteflt
*qf
, ui64_t
*stats
, struct str
*rest
);
71 static void _send_onpipe(int signo
);
74 static int sendpart(struct message
*zmp
, struct mimepart
*ip
,
75 FILE *obuf
, struct ignoretab
*doign
,
76 struct quoteflt
*qf
, enum sendaction action
,
77 ui64_t
*stats
, int level
);
79 /* Get a file for an attachment */
80 static FILE * newfile(struct mimepart
*ip
, int *ispipe
);
82 static void pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
,
83 struct quoteflt
*qf
, ui64_t
*stats
);
85 /* Output a reasonable looking status field */
86 static void statusput(const struct message
*mp
, FILE *obuf
,
87 struct quoteflt
*qf
, ui64_t
*stats
);
88 static void xstatusput(const struct message
*mp
, FILE *obuf
,
89 struct quoteflt
*qf
, ui64_t
*stats
);
91 static void put_from_(FILE *fp
, struct mimepart
*ip
, ui64_t
*stats
);
94 _print_part_info(FILE *obuf
, struct mimepart
const *mpp
, /* TODO strtofmt.. */
95 struct ignoretab
*doign
, int level
, struct quoteflt
*qf
, ui64_t
*stats
)
98 struct str ti
= {NULL
, 0}, to
;
99 struct str
const *cpre
, *csuf
;
104 cpre
= colour_get(COLOURSPEC_PARTINFO
);
105 csuf
= colour_get(COLOURSPEC_RESET
);
110 /* Take care of "99.99", i.e., 5 */
111 if ((cp
= mpp
->m_partstring
) == NULL
|| cp
[0] == '\0')
113 if (level
|| (cp
[0] != '1' && cp
[1] == '\0'))
114 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
116 _out(cpre
->s
, cpre
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
117 _out("[-- #", 5, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
118 _out(cp
, strlen(cp
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
120 to
.l
= snprintf(buf
, sizeof buf
, " %" PRIuZ
"/%" PRIuZ
" ",
121 (uiz_t
)mpp
->m_lines
, (uiz_t
)mpp
->m_size
);
122 _out(buf
, to
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
124 if ((cp
= mpp
->m_ct_type_usr_ovwr
) != NULL
)
125 _out("+", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
127 cp
= mpp
->m_ct_type_plain
;
128 if ((to
.l
= strlen(cp
)) > 30 && is_asccaseprefix(cp
, "application/")) {
129 size_t const al
= sizeof("appl../") -1, fl
= sizeof("application/") -1;
130 size_t i
= to
.l
- fl
;
131 char *x
= salloc(al
+ i
+1);
133 memcpy(x
, "appl../", al
);
134 memcpy(x
+ al
, cp
+ fl
, i
+1);
138 _out(cp
, to
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
140 if (mpp
->m_multipart
== NULL
/* TODO */ && (cp
= mpp
->m_ct_enc
) != NULL
) {
141 _out(", ", 2, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
142 if (to
.l
> 25 && !asccasecmp(cp
, "quoted-printable"))
144 _out(cp
, strlen(cp
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
147 if (mpp
->m_multipart
== NULL
/* TODO */ && (cp
= mpp
->m_charset
) != NULL
) {
148 _out(", ", 2, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
149 _out(cp
, strlen(cp
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
152 _out(" --]", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
154 _out(csuf
->s
, csuf
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
155 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
157 if (is_ign("content-disposition", 19, doign
) && mpp
->m_filename
!= NULL
&&
158 *mpp
->m_filename
!= '\0') {
159 makeprint(n_str_add_cp(&ti
, mpp
->m_filename
), &to
);
161 to
.l
= delctrl(to
.s
, to
.l
);
164 _out(cpre
->s
, cpre
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
165 _out("[-- ", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
166 _out(to
.s
, to
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
167 _out(" --]", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
169 _out(csuf
->s
, csuf
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
170 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
177 static enum pipeflags
178 _pipecmd(char const **result
, struct mimepart
const *mpp
)
186 /* Do we have any handler for this part? */
187 if ((cp
= mime_type_mimepart_handler(mpp
)) == NULL
)
189 else if (cp
== MIME_TYPE_HANDLER_TEXT
)
192 #ifdef HAVE_FILTER_HTML_TAGSOUP
193 cp
== MIME_TYPE_HANDLER_HTML
||
198 } else if (*++cp
== '\0') {
199 /* Treat as plain text */
201 } else if (!(pstate
& PS_MSGLIST_DIRECT
)) {
202 /* Viewing multiple messages in one go, don't block system */
203 *result
= _("[Directly address message only to display this]\n");
206 /* Viewing a single message only */
207 /* TODO send/MIME layer rewrite: when we have a single-pass parser
208 * TODO then the parsing phase and the send phase will be separated;
209 * TODO that allows us to ask a user *before* we start the send, i.e.,
210 * TODO *before* a pager pipe is setup */
212 /* Asynchronous command, normal command line */
213 *result
= ++cp
, rv
= PIPE_ASYNC
;
215 *result
= cp
, rv
= PIPE_COMM
;
222 _pipefile(char const *pipecomm
, struct mimepart
const *mpp
, FILE **qbuf
,
223 bool_t quote
, bool_t async
)
226 char const *env_addon
[8], *cp
, *sh
;
233 if ((*qbuf
= Ftmp(NULL
, "sendp", OF_RDWR
| OF_UNLINK
| OF_REGISTER
,
235 n_perr(_("tmpfile"), 0);
241 #ifdef HAVE_FILTER_HTML_TAGSOUP
242 if (pipecomm
== MIME_TYPE_HANDLER_HTML
) {
243 union {int (*ptf
)(void); char const *sh
;} u
;
246 if (*qbuf
!= stdout
) /* xxx never? v15: it'll be a filter anyway */
249 pipecomm
= "Builtin HTML tagsoup filter";
250 u
.ptf
= &htmlflt_process_main
;
251 if((rbuf
= Popen((char*)-1, "W", u
.sh
, NULL
, fileno(*qbuf
))) == NULL
)
258 if (mpp
== NULL
|| (cp
= mpp
->m_filename
) == NULL
)
260 env_addon
[0] = str_concat_csvl(&s
, NAILENV_FILENAME
, "=", cp
, NULL
)->s
;
262 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
263 * TODO a file wherever he wants! *Do* create a zero-size temporary file
264 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
265 * TODO the pipe returns? Like this we *can* verify path/name issues! */
266 env_addon
[1] = str_concat_csvl(&s
, NAILENV_FILENAME_GENERATED
, "=",
267 getrandstring(MIN(NAME_MAX
/ 4, 16)), NULL
)->s
;
269 /* NAIL_CONTENT{,_EVIDENCE} */
270 if (mpp
== NULL
|| (cp
= mpp
->m_ct_type_plain
) == NULL
)
272 env_addon
[2] = str_concat_csvl(&s
, NAILENV_CONTENT
, "=", cp
, NULL
)->s
;
274 if (mpp
!= NULL
&& mpp
->m_ct_type_usr_ovwr
!= NULL
)
275 cp
= mpp
->m_ct_type_usr_ovwr
;
276 env_addon
[3] = str_concat_csvl(&s
, NAILENV_CONTENT_EVIDENCE
, "=", cp
,
279 env_addon
[4] = str_concat_csvl(&s
, NAILENV_TMPDIR
, "=", tempdir
, NULL
)->s
;
280 env_addon
[5] = str_concat_csvl(&s
, "TMPDIR", "=", tempdir
, NULL
)->s
;
284 if ((sh
= ok_vlook(SHELL
)) == NULL
)
287 rbuf
= Popen(pipecomm
, "W", sh
, env_addon
, (async
? -1 : fileno(*qbuf
)));
289 #ifdef HAVE_FILTER_HTML_TAGSOUP
292 n_err(_("Cannot run MIME type handler \"%s\": %s\n"),
293 pipecomm
, strerror(errno
));
299 #ifdef HAVE_FILTER_HTML_TAGSOUP
307 _out(char const *buf
, size_t len
, FILE *fp
, enum conversion convert
, enum
308 sendaction action
, struct quoteflt
*qf
, ui64_t
*stats
, struct str
*rest
)
315 Well
... it turns out to
not work like that since of course a valid
316 RFC
4155 compliant parser
, like S
-nail
, takes care
for From_ lines only
317 after an empty line has been seen
, which cannot be detected that easily
319 ifdef HAVE_DEBUG
/* TODO assert legacy */
320 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
321 * TODO other input situations handle RFC 4155 OR, if newly generated,
322 * TODO enforce quoted-printable if there is From_, as "required" by
323 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
324 * TODO if it may happen in this path, we should just treat decryption
325 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
326 if (action
== SEND_MBOX
|| action
== SEND_DECRYPT
)
327 assert(!is_head(buf
, len
, FAL0
));
329 if ((/*action == SEND_MBOX ||*/ action
== SEND_DECRYPT
) &&
330 is_head(buf
, len
, FAL0
)) {
336 flags
= ((int)action
& _TD_EOF
);
338 n
= mime_write(buf
, len
, fp
,
339 action
== SEND_MBOX
? CONV_NONE
: convert
,
340 flags
| ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
341 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
)
343 : (action
== SEND_TOSRCH
|| action
== SEND_TOPIPE
)
344 ? TD_ICONV
: (action
== SEND_SHOW
? TD_ISPR
: TD_NONE
)),
358 _send_onpipe(int signo
)
360 NYD_X
; /* Signal handler */
362 siglongjmp(_send_pipejmp
, 1);
365 static sigjmp_buf __sendp_actjmp
; /* TODO someday.. */
366 static int __sendp_sig
; /* TODO someday.. */
367 static sighandler_type __sendp_opipe
;
369 __sendp_onsig(int sig
) /* TODO someday, we won't need it no more */
371 NYD_X
; /* Signal handler */
373 siglongjmp(__sendp_actjmp
, 1);
376 static sigjmp_buf __sndalter_actjmp
; /* TODO someday.. */
377 static int __sndalter_sig
; /* TODO someday.. */
379 __sndalter_onsig(int sig
) /* TODO someday, we won't need it no more */
381 NYD_X
; /* Signal handler */
382 __sndalter_sig
= sig
;
383 siglongjmp(__sndalter_actjmp
, 1);
387 sendpart(struct message
*zmp
, struct mimepart
*ip
, FILE * volatile obuf
,
388 struct ignoretab
*doign
, struct quoteflt
*qf
,
389 enum sendaction
volatile action
, ui64_t
* volatile stats
, int level
)
391 int volatile ispipe
, rv
= 0;
393 char *line
= NULL
, *cp
, *cp2
, *start
;
394 char const *pipecomm
= NULL
;
395 size_t linesize
= 0, linelen
, cnt
;
396 int dostat
, infld
= 0, ignoring
= 1, isenc
, c
;
397 struct mimepart
*volatile np
;
398 FILE * volatile ibuf
= NULL
, * volatile pbuf
= obuf
, * volatile qbuf
= obuf
,
400 enum conversion
volatile convert
;
401 sighandler_type
volatile oldpipe
= SIG_DFL
;
405 if (ip
->m_mimecontent
== MIME_PKCS7
&& ip
->m_multipart
&&
406 action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
412 if (!is_ign("status", 6, doign
))
414 if (!is_ign("x-status", 8, doign
))
419 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
) {
425 if (ip
->m_mimecontent
== MIME_DISCARD
)
428 if (!(ip
->m_flag
& MNOFROM
))
429 while (cnt
&& (c
= getc(ibuf
)) != EOF
) {
435 convert
= (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
436 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
437 action
== SEND_TOSRCH
)
438 ? CONV_FROMHDR
: CONV_NONE
;
440 /* Work the headers */
441 quoteflt_reset(qf
, obuf
);
442 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
444 if (line
[0] == '\n') {
445 /* If line is blank, we've reached end of headers, so force out
446 * status: field and note that we are no longer in header fields */
448 statusput(zmp
, obuf
, qf
, stats
);
450 xstatusput(zmp
, obuf
, qf
, stats
);
451 if (doign
!= allignore
)
452 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
457 if (infld
&& blankchar(line
[0])) {
458 /* If this line is a continuation (SP / HT) of a previous header
459 * field, determine if the start of the line is a MIME encoded word */
461 for (cp
= line
; blankchar(*cp
); ++cp
);
462 if (cp
> line
&& linelen
- PTR2SIZE(cp
- line
) > 8 &&
463 cp
[0] == '=' && cp
[1] == '?')
467 /* Pick up the header field if we have one */
468 for (cp
= line
; (c
= *cp
& 0377) && c
!= ':' && !spacechar(c
); ++cp
)
471 while (spacechar(*cp
))
473 if (cp
[0] != ':' && level
== 0 && lineno
== 1) {
474 /* Not a header line, force out status: This happens in uucp style
475 * mail where there are no headers at all */
477 statusput(zmp
, obuf
, qf
, stats
);
479 xstatusput(zmp
, obuf
, qf
, stats
);
480 if (doign
!= allignore
)
481 _out("\n", 1, obuf
, CONV_NONE
,SEND_MBOX
, qf
, stats
, NULL
);
485 /* If it is an ignored field and we care about such things, skip it.
486 * Misuse dostat also for another bit xxx use a bitenum + for more */
487 if (ok_blook(keep_content_length
))
490 *cp2
= 0; /* temporarily null terminate */
491 if ((doign
&& is_ign(line
, PTR2SIZE(cp2
- line
), doign
)) ||
492 (action
== SEND_MBOX
&& !(dostat
& (1 << 2)) &&
493 (!asccasecmp(line
, "content-length") ||
494 !asccasecmp(line
, "lines"))))
496 else if (!asccasecmp(line
, "status")) {
497 /* If field is "status," go compute and print real Status: field */
499 statusput(zmp
, obuf
, qf
, stats
);
503 } else if (!asccasecmp(line
, "x-status")) {
504 /* If field is "status," go compute and print real Status: field */
506 xstatusput(zmp
, obuf
, qf
, stats
);
512 /* For colourization we need the complete line, so save it */
513 /* XXX This is all temporary (colour belongs into backend), so
514 * XXX use pipecomm as a temporary storage in the meanwhile */
516 if (colour_table
!= NULL
)
517 pipecomm
= savestrbuf(line
, PTR2SIZE(cp2
- line
));
525 /* Determine if the end of the line is a MIME encoded word */
526 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
527 * TODO with header follow lines, and it should be up to the backend
528 * TODO what happens and what not, i.e., it doesn't matter wether it's
529 * TODO a MIME-encoded word or not, as long as a single separating space
530 * TODO remains in between lines (the MIME stuff will correctly remove
531 * TODO whitespace in between multiple adjacent encoded words) */
533 if (cnt
&& (c
= getc(ibuf
)) != EOF
) {
535 cp
= line
+ linelen
- 1;
536 if (linelen
> 0 && *cp
== '\n')
538 while (cp
>= line
&& whitechar(*cp
))
540 if (PTR2SIZE(cp
- line
> 8) && cp
[0] == '=' && cp
[-1] == '?')
547 size_t len
= linelen
;
549 if (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
550 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
551 action
== SEND_TOSRCH
) {
552 /* Strip blank characters if two MIME-encoded words follow on
553 * continuing lines */
555 while (len
> 0 && blankchar(*start
)) {
560 if (len
> 0 && start
[len
- 1] == '\n')
562 while (len
> 0 && blankchar(start
[len
- 1]))
567 bool_t colour_stripped
= FAL0
;
568 if (pipecomm
!= NULL
) {
569 colour_put_header(obuf
, pipecomm
);
570 if (len
> 0 && start
[len
- 1] == '\n') {
571 colour_stripped
= TRU1
;
576 _out(start
, len
, obuf
, convert
, action
, qf
, stats
, NULL
);
578 if (pipecomm
!= NULL
) {
579 colour_reset(obuf
); /* XXX reset after \n!! */
598 switch (ip
->m_mimecontent
) {
602 case SEND_TODISP_ALL
:
605 if (ok_blook(rfc822_body_from_
)) {
606 if (qf
->qf_pfix_len
> 0) {
607 size_t i
= fwrite(qf
->qf_pfix
, sizeof *qf
->qf_pfix
,
608 qf
->qf_pfix_len
, obuf
);
609 if (i
== qf
->qf_pfix_len
&& stats
!= NULL
)
612 put_from_(obuf
, ip
->m_multipart
, stats
);
620 if (ok_blook(rfc822_body_from_
))
621 put_from_(obuf
, ip
->m_multipart
, stats
);
631 case MIME_TEXT_PLAIN
:
634 case SEND_TODISP_ALL
:
638 switch (_pipecmd(&pipecomm
, ip
)) {
640 _out(pipecomm
, strlen(pipecomm
), obuf
, CONV_NONE
, SEND_MBOX
, qf
,
642 /* We would print this as plain text, so better force going home */
658 if (action
!= SEND_DECRYPT
)
662 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&&
663 action
!= SEND_SHOW
&& ip
->m_multipart
!= NULL
)
669 case SEND_TODISP_ALL
:
673 switch (_pipecmd(&pipecomm
, ip
)) {
675 _out(pipecomm
, strlen(pipecomm
), obuf
, CONV_NONE
, SEND_MBOX
, qf
,
686 goto jcopyout
; /* break; break; */
688 if (pipecomm
!= NULL
)
690 if (level
== 0 && cnt
) {
691 char const *x
= _("[Binary content]\n");
692 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
705 case MIME_ALTERNATIVE
:
707 if ((action
== SEND_TODISP
|| action
== SEND_QUOTE
) &&
708 !ok_blook(print_alternatives
)) {
709 /* XXX This (a) should not remain (b) should be own fun */
711 struct mpstack
*outer
;
713 } outermost
, * volatile curr
= &outermost
, * volatile mpsp
;
714 sighandler_type
volatile opsh
, oish
, ohsh
;
715 size_t volatile partcnt
= 0/* silence CC */;
716 bool_t
volatile neednl
= FAL0
;
722 opsh
= safe_signal(SIGPIPE
, &__sndalter_onsig
);
723 oish
= safe_signal(SIGINT
, &__sndalter_onsig
);
724 ohsh
= safe_signal(SIGHUP
, &__sndalter_onsig
);
725 if (sigsetjmp(__sndalter_actjmp
, 1)) {
730 for (np
= ip
->m_multipart
;;) {
733 for (; np
!= NULL
; np
= np
->m_nextpart
) {
734 if (action
!= SEND_QUOTE
&& np
->m_ct_type_plain
!= NULL
) {
736 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
737 _print_part_info(obuf
, np
, doign
, level
, qf
, stats
);
741 switch (np
->m_mimecontent
) {
742 case MIME_ALTERNATIVE
:
746 mpsp
= ac_alloc(sizeof *mpsp
);
748 mpsp
->mp
= np
->m_multipart
;
755 switch (_pipecmd(&pipecomm
, np
)) {
762 case MIME_TEXT_PLAIN
:
764 if (action
== SEND_QUOTE
&& partcnt
> 1 &&
765 ip
->m_mimecontent
== MIME_ALTERNATIVE
)
768 if (action
== SEND_QUOTE
&& partcnt
> 1) {
769 struct quoteflt
*dummy
= quoteflt_dummy();
770 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, dummy
, stats
,
772 quoteflt_flush(dummy
);
775 rv
= sendpart(zmp
, np
, obuf
, doign
, qf
, action
, stats
,
777 quoteflt_reset(qf
, origobuf
);
781 for (;; curr
= mpsp
) {
782 if ((mpsp
= curr
->outer
) == NULL
)
796 np
= curr
->mp
->m_nextpart
;
798 safe_signal(SIGHUP
, ohsh
);
799 safe_signal(SIGINT
, oish
);
800 safe_signal(SIGPIPE
, opsh
);
801 if (__sndalter_sig
!= 0)
802 n_raise(__sndalter_sig
);
810 case SEND_TODISP_ALL
:
818 if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
) &&
819 ip
->m_multipart
!= NULL
&&
820 ip
->m_multipart
->m_mimecontent
== MIME_DISCARD
&&
821 ip
->m_multipart
->m_nextpart
== NULL
) {
822 char const *x
= _("[Missing multipart boundary - use \"show\" "
823 "to display the raw message]\n");
824 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
);
827 for (np
= ip
->m_multipart
; np
!= NULL
; np
= np
->m_nextpart
) {
828 if (np
->m_mimecontent
== MIME_DISCARD
&& action
!= SEND_DECRYPT
)
833 if (np
->m_partstring
&& !strcmp(np
->m_partstring
, "1"))
836 /* TODO Always open multipart on /dev/null, it's a hack to be
837 * TODO able to dive into that structure, and still better
838 * TODO than asking the user for something stupid.
839 * TODO oh, wait, we did ask for a filename for this MIME mail,
840 * TODO and that outer container is useless anyway ;-P */
841 if (np
->m_multipart
!= NULL
) {
842 if ((obuf
= Fopen("/dev/null", "w")) == NULL
)
844 } else if ((obuf
= newfile(np
, UNVOLATILE(&ispipe
))) == NULL
)
848 if (sigsetjmp(_send_pipejmp
, 1)) {
852 oldpipe
= safe_signal(SIGPIPE
, &_send_onpipe
);
855 case SEND_TODISP_ALL
:
857 if (ip
->m_mimecontent
!= MIME_MULTI
&&
858 ip
->m_mimecontent
!= MIME_ALTERNATIVE
&&
859 ip
->m_mimecontent
!= MIME_RELATED
&&
860 ip
->m_mimecontent
!= MIME_DIGEST
)
862 _print_part_info(obuf
, np
, doign
, level
, qf
, stats
);
875 if (sendpart(zmp
, np
, obuf
, doign
, qf
, action
, stats
, level
+1) < 0)
877 quoteflt_reset(qf
, origobuf
);
879 if (action
== SEND_QUOTE
)
881 if (action
== SEND_TOFILE
&& obuf
!= origobuf
) {
886 safe_signal(SIGPIPE
, SIG_IGN
);
888 safe_signal(SIGPIPE
, oldpipe
);
900 /* Copy out message body */
902 if (doign
== allignore
&& level
== 0) /* skip final blank line */
904 switch (ip
->m_mime_enc
) {
911 convert
= CONV_FROMQP
;
914 switch (ip
->m_mimecontent
) {
916 case MIME_TEXT_PLAIN
:
918 convert
= CONV_FROMB64_T
;
921 convert
= CONV_FROMB64
;
928 if (action
== SEND_DECRYPT
|| action
== SEND_MBOX
||
929 action
== SEND_RFC822
|| action
== SEND_SHOW
)
932 if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
933 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
934 action
== SEND_TOSRCH
) &&
935 (ip
->m_mimecontent
== MIME_TEXT_PLAIN
||
936 ip
->m_mimecontent
== MIME_TEXT_HTML
||
937 ip
->m_mimecontent
== MIME_TEXT
)) {
938 char const *tcs
= charset_get_lc();
940 if (iconvd
!= (iconv_t
)-1)
941 n_iconv_close(iconvd
);
942 /* TODO Since Base64 has an odd 4:3 relation in between input
943 * TODO and output an input line may end with a partial
944 * TODO multibyte character; this is no problem at all unless
945 * TODO we send to the display or whatever, i.e., ensure
946 * TODO makeprint() or something; to avoid this trap, *force*
947 * TODO iconv(), in which case this layer will handle leftovers
949 if (convert
== CONV_FROMB64_T
|| (asccasecmp(tcs
, ip
->m_charset
) &&
950 asccasecmp(charset_get_7bit(), ip
->m_charset
))) {
951 iconvd
= n_iconv_open(tcs
, ip
->m_charset
);
953 * TODO errors should DEFINETELY not be scrolled away!
954 * TODO what about an error buffer (think old shsp(1)),
955 * TODO re-dump errors since last snapshot when the
956 * TODO command loop enters again? i.e., at least print
957 * TODO "There were errors ?" before the next prompt,
958 * TODO so that the user can look at the error buffer?
960 if (iconvd
== (iconv_t
)-1 && errno
== EINVAL
) {
961 n_err(_("Cannot convert from %s to %s\n"), ip
->m_charset
, tcs
);
962 /*rv = 1; goto jleave;*/
968 if (pipecomm
!= NULL
&& (action
== SEND_TODISP
||
969 action
== SEND_TODISP_ALL
|| action
== SEND_QUOTE
||
970 action
== SEND_QUOTE_ALL
)) {
972 pbuf
= _pipefile(pipecomm
, ip
, UNVOLATILE(&qbuf
),
973 (action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
), !ispipe
);
976 if (iconvd
!= (iconv_t
)-1)
977 n_iconv_close(iconvd
);
982 action
= SEND_TOPIPE
;
984 oldpipe
= safe_signal(SIGPIPE
, &_send_onpipe
);
985 if (sigsetjmp(_send_pipejmp
, 1))
993 ui32_t save_qf_pfix_len
= qf
->qf_pfix_len
;
994 ui64_t
*save_stats
= stats
;
996 if (pbuf
!= origobuf
) {
997 qf
->qf_pfix_len
= 0; /* XXX legacy (remove filter instead) */
1006 __sendp_opipe
= safe_signal(SIGPIPE
, &__sendp_onsig
);
1007 if (sigsetjmp(__sendp_actjmp
, 1)) {
1012 if (iconvd
!= (iconv_t
)-1)
1013 n_iconv_close(iconvd
);
1015 safe_signal(SIGPIPE
, __sendp_opipe
);
1016 n_raise(__sendp_sig
);
1020 quoteflt_reset(qf
, pbuf
);
1021 while (!eof
&& fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
1023 if (_out(line
, linelen
, pbuf
, convert
, action
, qf
, stats
, &rest
) < 0 ||
1025 rv
= -1; /* XXX Should bail away?! */
1029 if (!eof
&& rest
.l
!= 0) {
1036 safe_signal(SIGPIPE
, __sendp_opipe
);
1042 if (pbuf
!= origobuf
) {
1043 qf
->qf_pfix_len
= save_qf_pfix_len
;
1052 safe_signal(SIGPIPE
, SIG_IGN
);
1053 Pclose(pbuf
, ispipe
);
1054 safe_signal(SIGPIPE
, oldpipe
);
1055 if (qbuf
!= NULL
&& qbuf
!= obuf
)
1056 pipecpy(qbuf
, obuf
, origobuf
, qf
, stats
);
1059 if (iconvd
!= (iconv_t
)-1)
1060 n_iconv_close(iconvd
);
1068 newfile(struct mimepart
*ip
, int *ispipe
)
1078 if (f
!= NULL
&& f
!= (char*)-1) {
1081 makeprint(&in
, &out
);
1082 out
.l
= delctrl(out
.s
, out
.l
);
1083 f
= savestrbuf(out
.s
, out
.l
);
1087 /* In interactive mode, let user perform all kind of expansions as desired,
1088 * and offer |SHELL-SPEC pipe targets, too */
1089 if (options
& OPT_INTERACTIVE
) {
1093 /* TODO Generic function which asks for filename.
1094 * TODO If the current part is the first textpart the target
1095 * TODO is implicit from outer `write' etc! */
1096 /* I18N: Filename input prompt with file type indication */
1097 str_concat_csvl(&prompt
, _("Enter filename for part "),
1098 (ip
->m_partstring
!= NULL
) ? ip
->m_partstring
: _("?"),
1099 _(" ("), ip
->m_ct_type_plain
, _("): "), NULL
);
1101 f2
= n_input_cp_addhist(prompt
.s
, ((f
!= (char*)-1 && f
!= NULL
)
1102 ? fexpand_nshell_quote(f
) : NULL
), TRU1
);
1103 if (f2
== NULL
|| *f2
== '\0') {
1104 if (options
& OPT_D_V
)
1105 n_err(_("... skipping this\n"));
1108 } else if (*f2
== '|')
1109 /* Pipes are expanded by the shell */
1111 else if ((f3
= fexpand(f2
, FEXP_LOCAL
| FEXP_NSHELL
)) == NULL
)
1112 /* (Error message written by fexpand()) */
1118 if (f
== NULL
|| f
== (char*)-1 || *f
== '\0')
1120 else if (options
& OPT_INTERACTIVE
) {
1123 cp
= ok_vlook(SHELL
);
1126 fp
= Popen(f
+ 1, "w", cp
, NULL
, 1);
1127 if (!(*ispipe
= (fp
!= NULL
)))
1129 } else if ((fp
= Fopen(f
, "w")) == NULL
)
1130 n_err(_("Cannot open \"%s\"\n"), f
);
1132 /* Be very picky in non-interactive mode: actively disallow pipes,
1133 * prevent directory separators, and any filename member that would
1134 * become expanded by the shell if the name would be echo(1)ed */
1135 if(anyof(f
, "/" n_SHEXP_MAGIC_PATH_CHARS
)){
1138 for(out
.s
= salloc((strlen(f
) * 3) +1), out
.l
= 0; (c
= *f
++) != '\0';)
1139 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS
, c
)){
1140 out
.s
[out
.l
++] = '%';
1141 mime_char_to_hexseq(&out
.s
[out
.l
], c
);
1145 out
.s
[out
.l
] = '\0';
1149 /* Avoid overwriting of existing files */
1150 while((fp
= Fopen(f
, "wx")) == NULL
){
1153 if((e
= errno
) != EEXIST
){
1154 n_err(_("Cannot open \"%s\": %s\n"), f
, strerror(e
));
1158 if(ip
->m_partstring
!= NULL
)
1159 f
= savecatsep(f
, '#', ip
->m_partstring
);
1161 f
= savecat(f
, "#.");
1170 pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
, struct quoteflt
*qf
,
1173 char *line
= NULL
; /* TODO line pool */
1174 size_t linesize
= 0, linelen
, cnt
;
1180 cnt
= fsize(pipebuf
);
1183 quoteflt_reset(qf
, outbuf
);
1184 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, pipebuf
, 0) != NULL
) {
1185 if ((sz
= quoteflt_push(qf
, line
, linelen
)) < 0)
1189 if ((sz
= quoteflt_flush(qf
)) > 0)
1194 if (all_sz
> 0 && outbuf
== origobuf
&& stats
!= NULL
)
1201 statusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1204 char statout
[3], *cp
= statout
;
1207 if (mp
->m_flag
& MREAD
)
1209 if (!(mp
->m_flag
& MNEW
))
1213 int i
= fprintf(obuf
, "%.*sStatus: %s\n", (int)qf
->qf_pfix_len
,
1214 (qf
->qf_pfix_len
> 0 ? qf
->qf_pfix
: 0), statout
);
1215 if (i
> 0 && stats
!= NULL
)
1222 xstatusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1226 char *xp
= xstatout
;
1229 if (mp
->m_flag
& MFLAGGED
)
1231 if (mp
->m_flag
& MANSWERED
)
1233 if (mp
->m_flag
& MDRAFTED
)
1237 int i
= fprintf(obuf
, "%.*sX-Status: %s\n", (int)qf
->qf_pfix_len
,
1238 (qf
->qf_pfix_len
> 0 ? qf
->qf_pfix
: 0), xstatout
);
1239 if (i
> 0 && stats
!= NULL
)
1246 put_from_(FILE *fp
, struct mimepart
*ip
, ui64_t
*stats
)
1248 char const *froma
, *date
, *nl
;
1252 if (ip
!= NULL
&& ip
->m_from
!= NULL
) {
1254 date
= fakedate(ip
->m_time
);
1258 date
= time_current
.tc_ctime
;
1262 colour_put(fp
, COLOURSPEC_FROM_
);
1263 i
= fprintf(fp
, "From %s %s%s", froma
, date
, nl
);
1265 if (i
> 0 && stats
!= NULL
)
1271 sendmp(struct message
*mp
, FILE *obuf
, struct ignoretab
*doign
,
1272 char const *prefix
, enum sendaction action
, ui64_t
*stats
)
1277 enum mime_parse_flags mpf
;
1278 struct mimepart
*ip
;
1282 time_current_update(&time_current
, TRU1
);
1284 if (mp
== dot
&& action
!= SEND_TOSRCH
)
1285 pstate
|= PS_DID_PRINT_DOT
;
1288 quoteflt_init(&qf
, prefix
);
1290 /* First line is the From_ line, so no headers there to worry about */
1291 if ((ibuf
= setinput(&mb
, mp
, NEED_BODY
)) == NULL
)
1297 struct str
const *cpre
, *csuf
;
1299 cpre
= colour_get(COLOURSPEC_FROM_
);
1300 csuf
= colour_get(COLOURSPEC_RESET
);
1304 if (mp
->m_flag
& MNOFROM
) {
1305 if (doign
!= allignore
&& doign
!= fwdignore
&& action
!= SEND_RFC822
)
1306 sz
= fprintf(obuf
, "%s%.*sFrom %s %s%s\n",
1307 (cpre
!= NULL
? cpre
->s
: ""),
1308 (int)qf
.qf_pfix_len
, (qf
.qf_pfix_len
!= 0 ? qf
.qf_pfix
: ""),
1309 fakefrom(mp
), fakedate(mp
->m_time
),
1310 (csuf
!= NULL
? csuf
->s
: ""));
1311 } else if (doign
!= allignore
&& doign
!= fwdignore
&&
1312 action
!= SEND_RFC822
) {
1313 if (qf
.qf_pfix_len
> 0) {
1314 i
= fwrite(qf
.qf_pfix
, sizeof *qf
.qf_pfix
, qf
.qf_pfix_len
, obuf
);
1315 if (i
!= qf
.qf_pfix_len
)
1321 fputs(cpre
->s
, obuf
);
1322 cpre
= (struct str
const*)0x1;
1326 while (cnt
> 0 && (c
= getc(ibuf
)) != EOF
) {
1328 if (c
== '\n' && csuf
!= NULL
) {
1329 cpre
= (struct str
const*)0x1;
1330 fputs(csuf
->s
, obuf
);
1341 if (csuf
!= NULL
&& cpre
!= (struct str
const*)0x1)
1342 fputs(csuf
->s
, obuf
);
1345 while (cnt
> 0 && (c
= getc(ibuf
)) != EOF
) {
1352 if (sz
> 0 && stats
!= NULL
)
1355 mpf
= MIME_PARSE_NONE
;
1356 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
1357 mpf
|= MIME_PARSE_DECRYPT
| MIME_PARSE_PARTS
;
1358 if ((ip
= mime_parse_msg(mp
, mpf
)) == NULL
)
1361 rv
= sendpart(mp
, ip
, obuf
, doign
, &qf
, action
, stats
, 0);
1363 quoteflt_destroy(&qf
);