1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message content preparation (sendmp()).
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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
42 static sigjmp_buf _send_pipejmp
;
44 /* Going for user display, print Part: info string */
45 static void _print_part_info(FILE *obuf
, struct mimepart
const *mpp
,
46 struct n_ignore
const *doitp
, int level
,
47 struct quoteflt
*qf
, ui64_t
*stats
);
49 /* Create a pipe; if mpp is not NULL, place some NAILENV_* environment
50 * variables accordingly */
51 static FILE * _pipefile(struct mime_handler
*mhp
,
52 struct mimepart
const *mpp
, FILE **qbuf
,
53 char const *tmpname
, int term_infd
);
55 /* Call mime_write() as approbiate and adjust statistics */
56 SINLINE ssize_t
_out(char const *buf
, size_t len
, FILE *fp
,
57 enum conversion convert
, enum sendaction action
,
58 struct quoteflt
*qf
, ui64_t
*stats
, struct str
*outrest
,
62 static void _send_onpipe(int signo
);
65 static int sendpart(struct message
*zmp
, struct mimepart
*ip
,
66 FILE *obuf
, struct n_ignore
const *doitp
,
67 struct quoteflt
*qf
, enum sendaction action
,
68 ui64_t
*stats
, int level
);
70 /* Get a file for an attachment */
71 static FILE * newfile(struct mimepart
*ip
, bool_t
volatile *ispipe
);
73 static void pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
,
74 struct quoteflt
*qf
, ui64_t
*stats
);
76 /* Output a reasonable looking status field */
77 static void statusput(const struct message
*mp
, FILE *obuf
,
78 struct quoteflt
*qf
, ui64_t
*stats
);
79 static void xstatusput(const struct message
*mp
, FILE *obuf
,
80 struct quoteflt
*qf
, ui64_t
*stats
);
82 static void put_from_(FILE *fp
, struct mimepart
*ip
, ui64_t
*stats
);
85 _print_part_info(FILE *obuf
, struct mimepart
const *mpp
, /* TODO strtofmt.. */
86 struct n_ignore
const *doitp
, int level
, struct quoteflt
*qf
, ui64_t
*stats
)
89 struct str ti
= {NULL
, 0}, to
;
90 struct str
const *cpre
, *csuf
;
96 struct n_colour_pen
*cpen
= n_colour_pen_create(n_COLOUR_ID_VIEW_PARTINFO
,
98 if ((cpre
= n_colour_pen_to_str(cpen
)) != NULL
)
99 csuf
= n_colour_reset_to_str();
107 /* Take care of "99.99", i.e., 5 */
108 if ((cp
= mpp
->m_partstring
) == NULL
|| cp
[0] == '\0')
110 if (level
|| (cp
[0] != '1' && cp
[1] == '\0') || (cp
[0] == '1' && /* TODO */
111 cp
[1] == '.' && cp
[2] != '1')) /* TODO code should not look like so */
112 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
114 /* Part id, content-type, encoding, charset */
116 _out(cpre
->s
, cpre
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
117 _out("[-- #", 5, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
118 _out(cp
, strlen(cp
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,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
,NULL
);
124 if ((cp
= mpp
->m_ct_type_usr_ovwr
) != NULL
)
125 _out("+", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,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
,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
,NULL
);
142 if (to
.l
> 25 && !asccasecmp(cp
, "quoted-printable"))
144 _out(cp
, strlen(cp
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
147 if (mpp
->m_multipart
== NULL
/* TODO */ && (cp
= mpp
->m_charset
) != NULL
) {
148 _out(", ", 2, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
149 _out(cp
, strlen(cp
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
152 _out(" --]", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
154 _out(csuf
->s
, csuf
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
155 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
158 if (mpp
->m_content_info
& CI_MIME_ERRORS
) {
160 _out(cpre
->s
, cpre
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
162 _out("[-- ", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
164 ti
.l
= strlen(ti
.s
= n_UNCONST(_("Defective MIME structure")));
166 ti
.s
= NULL
; /* Not allocated! */
167 to
.l
= delctrl(to
.s
, to
.l
);
168 _out(to
.s
, to
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
171 _out(" --]", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
173 _out(csuf
->s
, csuf
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
175 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
179 if (n_ignore_is_ign(doitp
, "content-disposition", 19) &&
180 mpp
->m_filename
!= NULL
&& *mpp
->m_filename
!= '\0') {
182 _out(cpre
->s
, cpre
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
184 _out("[-- ", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
187 makeprint(n_str_add_cp(&ti
, mpp
->m_filename
), &to
);
188 to
.l
= delctrl(to
.s
, to
.l
);
189 _out(to
.s
, to
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
192 _out(" --]", 4, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
194 _out(csuf
->s
, csuf
->l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
196 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
, NULL
);
205 _pipefile(struct mime_handler
*mhp
, struct mimepart
const *mpp
, FILE **qbuf
,
206 char const *tmpname
, int term_infd
)
209 char const *env_addon
[8], *cp
, *sh
;
215 if (mhp
->mh_flags
& MIME_HDL_ISQUOTE
) {
216 if ((*qbuf
= Ftmp(NULL
, "sendp", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==
218 n_perr(_("tmpfile"), 0);
223 if ((mhp
->mh_flags
& MIME_HDL_TYPE_MASK
) == MIME_HDL_PTF
) {
224 union {int (*ptf
)(void); char const *sh
;} u
;
227 if (*qbuf
!= stdout
) /* xxx never? v15: it'll be a filter anyway */
231 if((rbuf
= Popen((char*)-1, "W", u
.sh
, NULL
, fileno(*qbuf
))) == NULL
)
237 if (mpp
== NULL
|| (cp
= mpp
->m_filename
) == NULL
)
239 env_addon
[0] = str_concat_csvl(&s
, NAILENV_FILENAME
, "=", cp
, NULL
)->s
;
241 /* NAIL_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
242 * TODO a file wherever he wants! *Do* create a zero-size temporary file
243 * TODO and give *that* path as NAIL_FILENAME_TEMPORARY, clean it up once
244 * TODO the pipe returns? Like this we *can* verify path/name issues! */
245 env_addon
[1] = str_concat_csvl(&s
, NAILENV_FILENAME_GENERATED
, "=",
246 getrandstring(n_MIN(NAME_MAX
/ 4, 16)), NULL
)->s
;
248 /* NAIL_CONTENT{,_EVIDENCE} */
249 if (mpp
== NULL
|| (cp
= mpp
->m_ct_type_plain
) == NULL
)
251 env_addon
[2] = str_concat_csvl(&s
, NAILENV_CONTENT
, "=", cp
, NULL
)->s
;
253 if (mpp
!= NULL
&& mpp
->m_ct_type_usr_ovwr
!= NULL
)
254 cp
= mpp
->m_ct_type_usr_ovwr
;
255 env_addon
[3] = str_concat_csvl(&s
, NAILENV_CONTENT_EVIDENCE
, "=", cp
,
258 env_addon
[4] = str_concat_csvl(&s
, NAILENV_TMPDIR
, /* TODO v15*/
259 "=", ok_vlook(TMPDIR
), NULL
)->s
;
263 /* NAIL_FILENAME_TEMPORARY? */
264 if (tmpname
!= NULL
) {
265 env_addon
[5] = str_concat_csvl(&s
, NAILENV_FILENAME_TEMPORARY
, "=",
270 sh
= ok_vlook(SHELL
);
272 if (mhp
->mh_flags
& MIME_HDL_NEEDSTERM
) {
277 pid
= run_command(sh
, &nset
, term_infd
, COMMAND_FD_PASS
, "-c",
278 mhp
->mh_shell_cmd
, NULL
, env_addon
);
279 rbuf
= (pid
< 0) ? NULL
: (FILE*)-1;
281 rbuf
= Popen(mhp
->mh_shell_cmd
, "W", sh
, env_addon
,
282 (mhp
->mh_flags
& MIME_HDL_ASYNC
? -1 : fileno(*qbuf
)));
285 n_err(_("Cannot run MIME type handler: %s: %s\n"),
286 mhp
->mh_msg
, strerror(errno
));
299 _out(char const *buf
, size_t len
, FILE *fp
, enum conversion convert
, enum
300 sendaction action
, struct quoteflt
*qf
, ui64_t
*stats
, struct str
*outrest
,
308 Well
... it turns out to
not work like that since of course a valid
309 RFC
4155 compliant parser
, like S
-nail
, takes care
for From_ lines only
310 after an empty line has been seen
, which cannot be detected that easily
312 ifdef HAVE_DEBUG
/* TODO assert legacy */
313 /* TODO if at all, this CAN only happen for SEND_DECRYPT, since all
314 * TODO other input situations handle RFC 4155 OR, if newly generated,
315 * TODO enforce quoted-printable if there is From_, as "required" by
316 * TODO RFC 5751. The SEND_DECRYPT case is not yet overhauled;
317 * TODO if it may happen in this path, we should just treat decryption
318 * TODO as we do for the other input paths; i.e., handle it in SSL!! */
319 if (action
== SEND_MBOX
|| action
== SEND_DECRYPT
)
320 assert(!is_head(buf
, len
, FAL0
));
322 if ((/*action == SEND_MBOX ||*/ action
== SEND_DECRYPT
) &&
323 is_head(buf
, len
, FAL0
)) {
329 flags
= ((int)action
& _TD_EOF
);
331 n
= mime_write(buf
, len
, fp
,
332 action
== SEND_MBOX
? CONV_NONE
: convert
,
333 flags
| ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
334 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
)
336 : (action
== SEND_TOSRCH
|| action
== SEND_TOPIPE
)
337 ? TD_ICONV
: (action
== SEND_SHOW
? TD_ISPR
: TD_NONE
)),
338 qf
, outrest
, inrest
);
351 _send_onpipe(int signo
)
353 NYD_X
; /* Signal handler */
355 siglongjmp(_send_pipejmp
, 1);
358 static sigjmp_buf __sendp_actjmp
; /* TODO someday.. */
359 static int __sendp_sig
; /* TODO someday.. */
360 static sighandler_type __sendp_opipe
;
362 __sendp_onsig(int sig
) /* TODO someday, we won't need it no more */
364 NYD_X
; /* Signal handler */
366 siglongjmp(__sendp_actjmp
, 1);
370 sendpart(struct message
*zmp
, struct mimepart
*ip
, FILE * volatile obuf
,
371 struct n_ignore
const *doitp
, struct quoteflt
*qf
,
372 enum sendaction
volatile action
, ui64_t
* volatile stats
, int level
)
375 struct mime_handler mh
;
376 struct str outrest
, inrest
;
377 char *line
= NULL
, *cp
, *cp2
, *start
;
378 char const * volatile tmpname
= NULL
;
379 size_t linesize
= 0, linelen
, cnt
;
380 int volatile term_infd
;
382 struct mimepart
*volatile np
;
383 FILE * volatile ibuf
= NULL
, * volatile pbuf
= obuf
,
384 * volatile qbuf
= obuf
, *origobuf
= obuf
;
385 enum conversion
volatile convert
;
386 sighandler_type
volatile oldpipe
= SIG_DFL
;
389 n_UNINIT(term_infd
, 0);
392 quoteflt_reset(qf
, obuf
);
394 if (ip
->m_mimecontent
== MIME_PKCS7
) {
395 if (ip
->m_multipart
&&
396 action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
403 if (!n_ignore_is_ign(doitp
, "status", 6))
405 if (!n_ignore_is_ign(doitp
, "x-status", 8))
410 if ((ibuf
= setinput(&mb
, (struct message
*)ip
, NEED_BODY
)) == NULL
) {
416 if (ip
->m_mimecontent
== MIME_DISCARD
)
419 if (!(ip
->m_flag
& MNOFROM
))
420 while (cnt
&& (c
= getc(ibuf
)) != EOF
) {
425 convert
= (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
426 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
427 action
== SEND_TOSRCH
)
428 ? CONV_FROMHDR
: CONV_NONE
;
430 /* Work the headers */
441 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
443 if (line
[0] == '\n') {
444 /* If line is blank, we've reached end of headers, so force out
445 * status: field and note that we are no longer in header fields */
447 statusput(zmp
, obuf
, qf
, stats
);
449 xstatusput(zmp
, obuf
, qf
, stats
);
450 if (doitp
!= n_IGNORE_ALL
)
451 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
, NULL
,NULL
);
456 if ((hps
& HPS_IN_FIELD
) && blankchar(line
[0])) {
457 /* If this line is a continuation (SP / HT) of a previous header
458 * field, determine if the start of the line is a MIME encoded word */
459 if (hps
& HPS_ISENC_2
) {
460 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
))
475 n_err(_("Malformed message: headers and body not separated "
476 "(with empty line)\n"));
477 /* Not a header line, force out status: This happens in uucp style
478 * mail where there are no headers at all */
479 if (level
== 0 /*&& lineno == 1*/) {
481 statusput(zmp
, obuf
, qf
, stats
);
483 xstatusput(zmp
, obuf
, qf
, stats
);
485 if (doitp
!= n_IGNORE_ALL
)
486 _out("\n", 1, obuf
, CONV_NONE
,SEND_MBOX
, qf
, stats
, NULL
,NULL
);
490 /* If it is an ignored field and we care about such things, skip it.
491 * Misuse dostat also for another bit xxx use a bitenum + for more */
492 if (ok_blook(keep_content_length
))
495 *cp2
= 0; /* temporarily null terminate */
496 if ((doitp
!= NULL
&&
497 n_ignore_is_ign(doitp
, line
, PTR2SIZE(cp2
- line
))) ||
498 (action
== SEND_MBOX
&& !(dostat
& (1 << 2)) &&
499 (!asccasecmp(line
, "content-length") ||
500 !asccasecmp(line
, "lines"))))
502 else if (!asccasecmp(line
, "status")) {
503 /* If field is "status," go compute and print real Status: field */
505 statusput(zmp
, obuf
, qf
, stats
);
509 } else if (!asccasecmp(line
, "x-status")) {
510 /* If field is "status," go compute and print real Status: field */
512 xstatusput(zmp
, obuf
, qf
, stats
);
518 /* For colourization we need the complete line, so save it */
519 /* XXX This is all temporary (colour belongs into backend), so
520 * XXX use tmpname as a temporary storage in the meanwhile */
522 if (pstate
& PS_COLOUR_ACTIVE
)
523 tmpname
= savestrbuf(line
, PTR2SIZE(cp2
- line
));
531 /* Determine if the end of the line is a MIME encoded word */
532 /* TODO geeeh! all this lengthy stuff that follows is about is dealing
533 * TODO with header follow lines, and it should be up to the backend
534 * TODO what happens and what not, i.e., it doesn't matter whether it's
535 * TODO a MIME-encoded word or not, as long as a single separating space
536 * TODO remains in between lines (the MIME stuff will correctly remove
537 * TODO whitespace in between multiple adjacent encoded words) */
539 if (cnt
&& (c
= getc(ibuf
)) != EOF
) {
541 cp
= line
+ linelen
- 1;
542 if (linelen
> 0 && *cp
== '\n')
544 while (cp
>= line
&& whitechar(*cp
))
546 if (PTR2SIZE(cp
- line
> 8) && cp
[0] == '=' && cp
[-1] == '?')
552 if (!(hps
& HPS_IGNORE
)) {
553 size_t len
= linelen
;
555 if (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
556 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
557 action
== SEND_TOSRCH
) {
558 /* Strip blank characters if two MIME-encoded words follow on
559 * continuing lines */
560 if (hps
& HPS_ISENC_1
)
561 while (len
> 0 && blankchar(*start
)) {
565 if (hps
& HPS_ISENC_2
)
566 if (len
> 0 && start
[len
- 1] == '\n')
568 while (len
> 0 && blankchar(start
[len
- 1]))
573 bool_t colour_stripped
= FAL0
;
574 if (tmpname
!= NULL
) {
575 n_colour_put(obuf
, n_COLOUR_ID_VIEW_HEADER
, tmpname
);
576 if (len
> 0 && start
[len
- 1] == '\n') {
577 colour_stripped
= TRU1
;
582 _out(start
, len
, obuf
, convert
, action
, qf
, stats
, NULL
,NULL
);
584 if (tmpname
!= NULL
) {
585 n_colour_reset(obuf
);
605 memset(&mh
, 0, sizeof mh
);
607 switch (ip
->m_mimecontent
) {
611 case SEND_TODISP_ALL
:
614 if (ok_blook(rfc822_body_from_
)) {
615 if (qf
->qf_pfix_len
> 0) {
616 size_t i
= fwrite(qf
->qf_pfix
, sizeof *qf
->qf_pfix
,
617 qf
->qf_pfix_len
, obuf
);
618 if (i
== qf
->qf_pfix_len
&& stats
!= NULL
)
621 put_from_(obuf
, ip
->m_multipart
, stats
);
629 if (ok_blook(rfc822_body_from_
))
630 put_from_(obuf
, ip
->m_multipart
, stats
);
640 case MIME_TEXT_PLAIN
:
643 case SEND_TODISP_ALL
:
646 switch (mime_type_handler(&mh
, ip
, action
)) {
648 _out(mh
.mh_msg
.s
, mh
.mh_msg
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
,
650 /* We would print this as plain text, so better force going home */
661 if (action
!= SEND_DECRYPT
)
665 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&&
666 action
!= SEND_SHOW
&& ip
->m_multipart
!= NULL
)
672 case SEND_TODISP_ALL
:
675 switch (mime_type_handler(&mh
, ip
, action
)) {
677 _out(mh
.mh_msg
.s
, mh
.mh_msg
.l
, obuf
, CONV_NONE
, SEND_MBOX
, qf
,
679 /* We would print this as plain text, so better force going home */
682 /* FIXME WE NEED TO DO THAT IF WE ARE THE ONLY MAIL
683 * FIXME CONTENT !! */
688 if (level
== 0 && cnt
) {
689 char const *x
= _("[-- Binary content --]\n");
690 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
706 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
710 * TODO (despite the fact that v15 will do this completely differently
711 * TODO by having an action-specific "manager" that will traverse the
712 * TODO parsed MIME tree and decide for each part whether it'll be
713 * TODO displayed or not *before* we walk the tree for doing action */
715 struct mpstack
*outer
;
717 } outermost
, * volatile curr
, * volatile mpsp
;
718 bool_t
volatile neednl
, hadpart
;
719 struct n_sigman smalter
;
721 (curr
= &outermost
)->outer
= NULL
;
723 neednl
= hadpart
= FAL0
;
725 n_SIGMAN_ENTER_SWITCH(&smalter
, n_SIGMAN_ALL
) {
733 for (np
= ip
->m_multipart
;;) {
735 for (; np
!= NULL
; np
= np
->m_nextpart
) {
736 if (action
!= SEND_QUOTE
&& np
->m_ct_type_plain
!= NULL
) {
738 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
740 _print_part_info(obuf
, np
, doitp
, level
, qf
, stats
);
744 switch (np
->m_mimecontent
) {
745 case MIME_ALTERNATIVE
:
751 mpsp
= salloc(sizeof *mpsp
);
753 mpsp
->mp
= np
->m_multipart
;
762 switch (mime_type_handler(&mh
, np
, action
)) {
764 mh
.mh_flags
= MIME_HDL_NULL
;
765 continue; /* break; break; */
767 if (!ok_blook(mime_alternative_favour_rich
)) {/* TODO */
768 struct mimepart
*x
= np
;
770 while ((x
= x
->m_nextpart
) != NULL
) {
771 struct mime_handler mhx
;
773 if (x
->m_mimecontent
== MIME_TEXT_PLAIN
||
774 mime_type_handler(&mhx
, x
, action
) ==
779 continue; /* break; break; */
787 case MIME_TEXT_PLAIN
:
790 if (ok_blook(mime_alternative_favour_rich
)) { /* TODO */
791 struct mimepart
*x
= np
;
793 /* TODO twice TODO, we should dive into /related and
794 * TODO check whether that has rich parts! */
795 while ((x
= x
->m_nextpart
) != NULL
) {
796 struct mime_handler mhx
;
798 switch (mime_type_handler(&mhx
, x
, action
)) {
807 continue; /* break; break; */
811 if (action
== SEND_QUOTE
&& hadpart
) {
812 struct quoteflt
*dummy
= quoteflt_dummy();
813 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, dummy
, stats
,
815 quoteflt_flush(dummy
);
819 rv
= sendpart(zmp
, np
, obuf
, doitp
, qf
, action
, stats
,
821 quoteflt_reset(qf
, origobuf
);
824 curr
= &outermost
; /* Cause overall loop termination */
833 np
= curr
->mp
->m_nextpart
;
836 n_sigman_leave(&smalter
, n_SIGMAN_VIPSIGS_NTTYOUT
);
847 case SEND_TODISP_ALL
:
855 if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
) &&
856 ip
->m_multipart
!= NULL
&&
857 ip
->m_multipart
->m_mimecontent
== MIME_DISCARD
&&
858 ip
->m_multipart
->m_nextpart
== NULL
) {
859 char const *x
= _("[Missing multipart boundary - use show "
860 "to display the raw message]\n");
861 _out(x
, strlen(x
), obuf
, CONV_NONE
, SEND_MBOX
, qf
, stats
,
865 for (np
= ip
->m_multipart
; np
!= NULL
; np
= np
->m_nextpart
) {
866 bool_t
volatile ispipe
;
868 if (np
->m_mimecontent
== MIME_DISCARD
&& action
!= SEND_DECRYPT
)
874 if (np
->m_partstring
&& !strcmp(np
->m_partstring
, "1"))
877 /* TODO Always open multipart on /dev/null, it's a hack to be
878 * TODO able to dive into that structure, and still better
879 * TODO than asking the user for something stupid.
880 * TODO oh, wait, we did ask for a filename for this MIME mail,
881 * TODO and that outer container is useless anyway ;-P */
882 if (np
->m_multipart
!= NULL
) {
883 if ((obuf
= Fopen("/dev/null", "w")) == NULL
)
885 } else if ((obuf
= newfile(np
, &ispipe
)) == NULL
)
889 if (sigsetjmp(_send_pipejmp
, 1)) {
893 oldpipe
= safe_signal(SIGPIPE
, &_send_onpipe
);
896 case SEND_TODISP_ALL
:
897 if (ip
->m_mimecontent
!= MIME_ALTERNATIVE
&&
898 ip
->m_mimecontent
!= MIME_RELATED
&&
899 ip
->m_mimecontent
!= MIME_DIGEST
&&
900 ip
->m_mimecontent
!= MIME_SIGNED
&&
901 ip
->m_mimecontent
!= MIME_ENCRYPTED
&&
902 ip
->m_mimecontent
!= MIME_MULTI
)
904 _print_part_info(obuf
, np
, doitp
, level
, qf
, stats
);
918 if ((action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
) &&
919 np
->m_multipart
== NULL
&& ip
->m_parent
!= NULL
) {
920 struct quoteflt
*dummy
= quoteflt_dummy();
921 _out("\n", 1, obuf
, CONV_NONE
, SEND_MBOX
, dummy
, stats
,
923 quoteflt_flush(dummy
);
925 if (sendpart(zmp
, np
, obuf
, doitp
, qf
, action
, stats
, level
+1) < 0)
927 quoteflt_reset(qf
, origobuf
);
929 if (action
== SEND_QUOTE
) {
930 if (ip
->m_mimecontent
!= MIME_RELATED
)
933 if (action
== SEND_TOFILE
&& obuf
!= origobuf
) {
938 safe_signal(SIGPIPE
, SIG_IGN
);
940 safe_signal(SIGPIPE
, oldpipe
);
953 /* Copy out message body */
954 if (doitp
== n_IGNORE_ALL
&& level
== 0) /* skip final blank line */
956 switch (ip
->m_mime_enc
) {
963 convert
= CONV_FROMQP
;
966 switch (ip
->m_mimecontent
) {
968 case MIME_TEXT_PLAIN
:
970 convert
= CONV_FROMB64_T
;
973 switch (mh
.mh_flags
& MIME_HDL_TYPE_MASK
) {
976 convert
= CONV_FROMB64_T
;
979 convert
= CONV_FROMB64
;
989 if (action
== SEND_DECRYPT
|| action
== SEND_MBOX
||
990 action
== SEND_RFC822
|| action
== SEND_SHOW
)
993 else if ((action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
||
994 action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
||
995 action
== SEND_TOSRCH
) &&
996 (ip
->m_mimecontent
== MIME_TEXT_PLAIN
||
997 ip
->m_mimecontent
== MIME_TEXT_HTML
||
998 ip
->m_mimecontent
== MIME_TEXT
||
999 (mh
.mh_flags
& MIME_HDL_TYPE_MASK
) == MIME_HDL_TEXT
||
1000 (mh
.mh_flags
& MIME_HDL_TYPE_MASK
) == MIME_HDL_PTF
)) {
1001 char const *tcs
= charset_get_lc();
1003 if (iconvd
!= (iconv_t
)-1)
1004 n_iconv_close(iconvd
);
1005 /* TODO Since Base64 has an odd 4:3 relation in between input
1006 * TODO and output an input line may end with a partial
1007 * TODO multibyte character; this is no problem at all unless
1008 * TODO we send to the display or whatever, i.e., ensure
1009 * TODO makeprint() or something; to avoid this trap, *force*
1010 * TODO iconv(), in which case this layer will handle leftovers
1011 * TODO correctly. It's a pre-v15 we-have-filters hack */
1012 if (convert
== CONV_FROMB64_T
|| (asccasecmp(tcs
, ip
->m_charset
) &&
1013 asccasecmp(charset_get_7bit(), ip
->m_charset
))) {
1014 iconvd
= n_iconv_open(tcs
, ip
->m_charset
);
1015 if (iconvd
== (iconv_t
)-1 && errno
== EINVAL
) {
1016 n_err(_("Cannot convert from %s to %s\n"), ip
->m_charset
, tcs
);
1017 /*rv = 1; goto jleave;*/
1023 switch (mh
.mh_flags
& MIME_HDL_TYPE_MASK
) {
1029 term_infd
= COMMAND_FD_PASS
;
1030 if (mh
.mh_flags
& (MIME_HDL_TMPF
| MIME_HDL_NEEDSTERM
)) {
1033 of
= OF_RDWR
| OF_REGISTER
;
1034 if (!(mh
.mh_flags
& MIME_HDL_TMPF
)) {
1036 mh
.mh_flags
|= MIME_HDL_TMPF_FILL
;
1038 } else if (mh
.mh_flags
& MIME_HDL_TMPF_UNLINK
)
1039 of
|= OF_REGISTER_UNLINK
;
1041 if ((pbuf
= Ftmp((mh
.mh_flags
& MIME_HDL_TMPF
? &cp
: NULL
),
1042 (mh
.mh_flags
& MIME_HDL_TMPF_FILL
? "mimehdlfill" : "mimehdl"),
1046 if (mh
.mh_flags
& MIME_HDL_TMPF
) {
1047 tmpname
= savestr(cp
);
1051 if (mh
.mh_flags
& MIME_HDL_TMPF_FILL
) {
1053 term_infd
= fileno(pbuf
);
1059 pbuf
= _pipefile(&mh
, ip
, n_UNVOLATILE(&qbuf
), tmpname
, term_infd
);
1065 } else if ((mh
.mh_flags
& MIME_HDL_NEEDSTERM
) && pbuf
== (FILE*)-1) {
1070 action
= SEND_TOPIPE
;
1072 oldpipe
= safe_signal(SIGPIPE
, &_send_onpipe
);
1073 if (sigsetjmp(_send_pipejmp
, 1))
1079 mh
.mh_flags
= MIME_HDL_NULL
;
1086 bool_t
volatile eof
;
1087 ui32_t save_qf_pfix_len
= qf
->qf_pfix_len
;
1088 ui64_t
*save_stats
= stats
;
1090 if (pbuf
!= origobuf
) {
1091 qf
->qf_pfix_len
= 0; /* XXX legacy (remove filter instead) */
1095 outrest
.s
= inrest
.s
= NULL
;
1096 outrest
.l
= inrest
.l
= 0;
1100 __sendp_opipe
= safe_signal(SIGPIPE
, &__sendp_onsig
);
1101 if (sigsetjmp(__sendp_actjmp
, 1)) {
1102 if (outrest
.s
!= NULL
)
1104 if (inrest
.s
!= NULL
)
1108 if (iconvd
!= (iconv_t
)-1)
1109 n_iconv_close(iconvd
);
1111 safe_signal(SIGPIPE
, __sendp_opipe
);
1112 n_raise(__sendp_sig
);
1116 quoteflt_reset(qf
, pbuf
);
1117 while (!eof
&& fgetline(&line
, &linesize
, &cnt
, &linelen
, ibuf
, 0)) {
1119 if (_out(line
, linelen
, pbuf
, convert
, action
, qf
, stats
, &outrest
,
1120 (action
& _TD_EOF
? NULL
: &inrest
)) < 0 || ferror(pbuf
)) {
1121 rv
= -1; /* XXX Should bail away?! */
1125 if(eof
<= FAL0
&& rv
>= 0 && (outrest
.l
!= 0 || inrest
.l
!= 0)){
1127 if(eof
|| inrest
.l
== 0)
1129 eof
= eof
? TRU1
: TRUM1
;
1133 /* TODO HACK: when sending to the display we yet get fooled if a message
1134 * TODO doesn't end in a newline, because of our input/output 1:1.
1135 * TODO This should be handled automatically by a display filter, then */
1136 if(rv
>= 0 && !qf
->qf_nl_last
&&
1137 (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
))
1138 rv
= quoteflt_push(qf
, "\n", 1);
1142 if (rv
>= 0 && (mh
.mh_flags
& MIME_HDL_TMPF_FILL
)) {
1143 mh
.mh_flags
&= ~MIME_HDL_TMPF_FILL
;
1145 really_rewind(pbuf
);
1146 /* Don't Fclose() the Ftmp() thing due to OF_REGISTER_UNLINK++ */
1147 goto jpipe_for_real
;
1151 safe_signal(SIGPIPE
, __sendp_opipe
);
1153 if (outrest
.s
!= NULL
)
1155 if (inrest
.s
!= NULL
)
1158 if (pbuf
!= origobuf
) {
1159 qf
->qf_pfix_len
= save_qf_pfix_len
;
1168 safe_signal(SIGPIPE
, SIG_IGN
);
1169 Pclose(pbuf
, !(mh
.mh_flags
& MIME_HDL_ASYNC
));
1170 safe_signal(SIGPIPE
, oldpipe
);
1171 if (rv
>= 0 && qbuf
!= NULL
&& qbuf
!= obuf
)
1172 pipecpy(qbuf
, obuf
, origobuf
, qf
, stats
);
1175 if (iconvd
!= (iconv_t
)-1)
1176 n_iconv_close(iconvd
);
1184 newfile(struct mimepart
*ip
, bool_t
volatile *ispipe
)
1194 if (f
!= NULL
&& f
!= (char*)-1) {
1197 makeprint(&in
, &out
);
1198 out
.l
= delctrl(out
.s
, out
.l
);
1199 f
= savestrbuf(out
.s
, out
.l
);
1203 /* In interactive mode, let user perform all kind of expansions as desired,
1204 * and offer |SHELL-SPEC pipe targets, too */
1205 if (options
& OPT_INTERACTIVE
) {
1207 struct n_string shou
, *shoup
;
1210 shoup
= n_string_creat_auto(&shou
);
1212 /* TODO Generic function which asks for filename.
1213 * TODO If the current part is the first textpart the target
1214 * TODO is implicit from outer `write' etc! */
1215 /* I18N: Filename input prompt with file type indication */
1216 str_concat_csvl(&prompt
, _("Enter filename for part "),
1217 (ip
->m_partstring
!= NULL
) ? ip
->m_partstring
: _("?"),
1218 _(" ("), ip
->m_ct_type_plain
, _("): "), NULL
);
1220 f2
= n_lex_input_cp(n_LEXINPUT_CTX_DEFAULT
| n_LEXINPUT_HIST_ADD
,
1221 prompt
.s
, ((f
!= (char*)-1 && f
!= NULL
)
1222 ? n_shexp_quote_cp(f
, FAL0
) : NULL
));
1224 in
.s
= n_UNCONST(f2
);
1226 if((n_shexp_parse_token(shoup
, &in
, n_SHEXP_PARSE_TRUNC
|
1227 n_SHEXP_PARSE_TRIMSPACE
| n_SHEXP_PARSE_LOG
|
1228 n_SHEXP_PARSE_IGNORE_EMPTY
) &
1229 (n_SHEXP_STATE_STOP
| n_SHEXP_STATE_OUTPUT
|
1230 n_SHEXP_STATE_ERR_MASK
)
1231 ) != (n_SHEXP_STATE_STOP
| n_SHEXP_STATE_OUTPUT
))
1233 f2
= n_string_cp(shoup
);
1235 if (f2
== NULL
|| *f2
== '\0') {
1236 if (options
& OPT_D_V
)
1237 n_err(_("... skipping this\n"));
1238 n_string_gut(shoup
);
1244 /* Pipes are expanded by the shell */
1246 else if ((f3
= fexpand(f2
, FEXP_LOCAL
| FEXP_NVAR
)) == NULL
)
1247 /* (Error message written by fexpand()) */
1252 n_string_gut(shoup
);
1255 if (f
== NULL
|| f
== (char*)-1 || *f
== '\0')
1257 else if (options
& OPT_INTERACTIVE
) {
1259 fp
= Popen(&f
[1], "w", ok_vlook(SHELL
), NULL
, 1);
1260 if (!(*ispipe
= (fp
!= NULL
)))
1262 } else if ((fp
= Fopen(f
, "w")) == NULL
)
1263 n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f
, FAL0
));
1265 /* Be very picky in non-interactive mode: actively disallow pipes,
1266 * prevent directory separators, and any filename member that would
1267 * become expanded by the shell if the name would be echo(1)ed */
1268 if(anyof(f
, "/" n_SHEXP_MAGIC_PATH_CHARS
)){
1271 for(out
.s
= salloc((strlen(f
) * 3) +1), out
.l
= 0; (c
= *f
++) != '\0';)
1272 if(strchr("/" n_SHEXP_MAGIC_PATH_CHARS
, c
)){
1273 out
.s
[out
.l
++] = '%';
1274 n_c_to_hex_base16(&out
.s
[out
.l
], c
);
1278 out
.s
[out
.l
] = '\0';
1282 /* Avoid overwriting of existing files */
1283 while((fp
= Fopen(f
, "wx")) == NULL
){
1286 if((e
= errno
) != EEXIST
){
1287 n_err(_("Cannot open %s: %s\n"),
1288 n_shexp_quote_cp(f
, FAL0
), strerror(e
));
1292 if(ip
->m_partstring
!= NULL
)
1293 f
= savecatsep(f
, '#', ip
->m_partstring
);
1295 f
= savecat(f
, "#.");
1304 pipecpy(FILE *pipebuf
, FILE *outbuf
, FILE *origobuf
, struct quoteflt
*qf
,
1307 char *line
= NULL
; /* TODO line pool */
1308 size_t linesize
= 0, linelen
, cnt
;
1314 cnt
= (size_t)fsize(pipebuf
);
1317 quoteflt_reset(qf
, outbuf
);
1318 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, pipebuf
, 0) != NULL
) {
1319 if ((sz
= quoteflt_push(qf
, line
, linelen
)) < 0)
1323 if ((sz
= quoteflt_flush(qf
)) > 0)
1328 if (all_sz
> 0 && outbuf
== origobuf
&& stats
!= NULL
)
1335 statusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1338 char statout
[3], *cp
= statout
;
1341 if (mp
->m_flag
& MREAD
)
1343 if (!(mp
->m_flag
& MNEW
))
1347 int i
= fprintf(obuf
, "%.*sStatus: %s\n", (int)qf
->qf_pfix_len
,
1348 (qf
->qf_pfix_len
> 0 ? qf
->qf_pfix
: 0), statout
);
1349 if (i
> 0 && stats
!= NULL
)
1356 xstatusput(const struct message
*mp
, FILE *obuf
, struct quoteflt
*qf
,
1360 char *xp
= xstatout
;
1363 if (mp
->m_flag
& MFLAGGED
)
1365 if (mp
->m_flag
& MANSWERED
)
1367 if (mp
->m_flag
& MDRAFTED
)
1371 int i
= fprintf(obuf
, "%.*sX-Status: %s\n", (int)qf
->qf_pfix_len
,
1372 (qf
->qf_pfix_len
> 0 ? qf
->qf_pfix
: 0), xstatout
);
1373 if (i
> 0 && stats
!= NULL
)
1380 put_from_(FILE *fp
, struct mimepart
*ip
, ui64_t
*stats
)
1382 char const *froma
, *date
, *nl
;
1386 if (ip
!= NULL
&& ip
->m_from
!= NULL
) {
1388 date
= fakedate(ip
->m_time
);
1392 date
= time_current
.tc_ctime
;
1396 n_COLOUR( n_colour_put(fp
, n_COLOUR_ID_VIEW_FROM_
, NULL
); )
1397 i
= fprintf(fp
, "From %s %s%s", froma
, date
, nl
);
1398 n_COLOUR( n_colour_reset(fp
); )
1399 if (i
> 0 && stats
!= NULL
)
1405 sendmp(struct message
*mp
, FILE *obuf
, struct n_ignore
const *doitp
,
1406 char const *prefix
, enum sendaction action
, ui64_t
*stats
)
1411 enum mime_parse_flags mpf
;
1412 struct mimepart
*ip
;
1416 time_current_update(&time_current
, TRU1
);
1418 if (mp
== dot
&& action
!= SEND_TOSRCH
)
1419 pstate
|= PS_DID_PRINT_DOT
;
1422 quoteflt_init(&qf
, prefix
);
1424 /* First line is the From_ line, so no headers there to worry about */
1425 if ((ibuf
= setinput(&mb
, mp
, NEED_BODY
)) == NULL
)
1432 char const *cpre
= n_empty
, *csuf
= n_empty
;
1434 struct n_colour_pen
*cpen
= n_colour_pen_create(n_COLOUR_ID_VIEW_FROM_
,NULL
);
1435 struct str
const *sp
= n_colour_pen_to_str(cpen
);
1439 sp
= n_colour_reset_to_str();
1445 nozap
= (doitp
!= n_IGNORE_ALL
&& doitp
!= n_IGNORE_FWD
&&
1446 action
!= SEND_RFC822
&&
1447 !n_ignore_is_ign(doitp
, "from_", sizeof("from_") -1));
1448 if (mp
->m_flag
& MNOFROM
) {
1450 sz
= fprintf(obuf
, "%s%.*sFrom %s %s%s\n",
1451 cpre
, (int)qf
.qf_pfix_len
,
1452 (qf
.qf_pfix_len
!= 0 ? qf
.qf_pfix
: n_empty
), fakefrom(mp
),
1453 fakedate(mp
->m_time
), csuf
);
1455 if (qf
.qf_pfix_len
> 0) {
1456 i
= fwrite(qf
.qf_pfix
, sizeof *qf
.qf_pfix
, qf
.qf_pfix_len
, obuf
);
1457 if (i
!= qf
.qf_pfix_len
)
1464 cpre
= (char const*)0x1;
1468 while (cnt
> 0 && (c
= getc(ibuf
)) != EOF
) {
1470 if (c
== '\n' && csuf
!= NULL
) {
1471 cpre
= (char const*)0x1;
1483 if (csuf
!= NULL
&& cpre
!= (char const*)0x1)
1487 while (cnt
> 0 && (c
= getc(ibuf
)) != EOF
) {
1494 if (sz
> 0 && stats
!= NULL
)
1497 mpf
= MIME_PARSE_NONE
;
1498 if (action
!= SEND_MBOX
&& action
!= SEND_RFC822
&& action
!= SEND_SHOW
)
1499 mpf
|= MIME_PARSE_PARTS
| MIME_PARSE_DECRYPT
;
1500 if ((ip
= mime_parse_msg(mp
, mpf
)) == NULL
)
1503 rv
= sendpart(mp
, ip
, obuf
, doitp
, &qf
, action
, stats
, 0);
1505 quoteflt_destroy(&qf
);