1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ MIME support functions.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
9 * Gunnar Ritter. 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 Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his 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 GUNNAR RITTER 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 GUNNAR RITTER 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 struct mtnode
*mt_next
;
46 size_t mt_mtlen
; /* Length of MIME type string */
47 char mt_line
[VFIELD_SIZE(8)];
50 static char const * const _mt_sources
[] = {
51 /* XXX Order fixed due to *mimetypes-load-control* handling! */
52 MIME_TYPES_USR
, MIME_TYPES_SYS
, NULL
54 * const _mt_bltin
[] = {
55 #include "mime_types.h"
59 static struct mtnode
*_mt_list
;
60 static char *_cs_iter_base
, *_cs_iter
;
63 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
65 # define _CS_ITER_GET() ((_cs_iter != NULL) ? _cs_iter : charset_get_lc())
67 #define _CS_ITER_STEP() _cs_iter = n_strsep(&_cs_iter_base, ',', TRU1)
69 /* Initialize MIME type list */
70 static void _mt_init(void);
71 static void __mt_add_line(char const *line
, struct mtnode
**tail
);
73 /* Is 7-bit enough? */
75 static bool_t
_has_highbit(char const *s
);
76 static bool_t
_name_highbit(struct name
*np
);
79 /* Get the conversion that matches *encoding* */
80 static enum conversion
_conversion_by_encoding(void);
82 /* fwrite(3) while checking for displayability */
83 static ssize_t
_fwrite_td(struct str
const *input
, enum tdflags flags
,
84 struct str
*rest
, struct quoteflt
*qf
);
86 static size_t delctrl(char *cp
, size_t sz
);
88 static int is_this_enc(char const *line
, char const *encoding
);
90 /* Convert header fields to RFC 1522 format and write to the file fo */
91 static ssize_t
mime_write_tohdr(struct str
*in
, FILE *fo
);
93 /* Write len characters of the passed string to the passed file, doing charset
94 * and header conversion */
95 static ssize_t
convhdra(char const *str
, size_t len
, FILE *fp
);
97 /* Write an address to a header field */
98 static ssize_t
mime_write_tohdr_a(struct str
*in
, FILE *f
);
100 /* Append to buf, handling resizing */
101 static void addstr(char **buf
, size_t *sz
, size_t *pos
,
102 char const *str
, size_t len
);
104 static void addconv(char **buf
, size_t *sz
, size_t *pos
,
105 char const *str
, size_t len
);
110 struct mtnode
*tail
= NULL
;
111 char *line
= NULL
; /* TODO line pool */
114 char const *ccp
, * const *srcs
;
118 if ((ccp
= ok_vlook(mimetypes_load_control
)) == NULL
)
120 else for (idx_ok
= 0; *ccp
!= '\0'; ++ccp
)
131 /* XXX bad *mimetypes-load-control*; log error? */
135 for (idx
= 1, srcs
= _mt_sources
; *srcs
!= NULL
; idx
<<= 1, ++srcs
) {
136 if (!(idx
& idx_ok
) || (ccp
= file_expand(*srcs
)) == NULL
)
138 if ((fp
= Fopen(ccp
, "r")) == NULL
) {
139 /*fprintf(stderr, _("Cannot open `%s'\n"), fn);*/
142 while (fgetline(&line
, &linesize
, NULL
, NULL
, fp
, 0))
143 __mt_add_line(line
, &tail
);
149 for (srcs
= _mt_bltin
; *srcs
!= NULL
; ++srcs
)
150 __mt_add_line(*srcs
, &tail
);
155 __mt_add_line(char const *line
, struct mtnode
**tail
) /* XXX diag? dups!*/
162 if (!alphachar(*line
))
166 while (!blankchar(*line
) && *line
!= '\0')
170 tlen
= PTR2SIZE(line
- typ
);
172 while (blankchar(*line
) && *line
!= '\0')
178 if (line
[elen
- 1] == '\n') {
179 if (--elen
> 0 && line
[elen
- 1] == '\r')
185 mtn
= smalloc(sizeof(struct mtnode
) -
186 VFIELD_SIZEOF(struct mtnode
, mt_line
) + tlen
+ 1 + elen
+1);
188 (*tail
)->mt_next
= mtn
;
193 mtn
->mt_mtlen
= tlen
;
194 memcpy(mtn
->mt_line
, typ
, tlen
);
195 mtn
->mt_line
[tlen
] = '\0';
197 memcpy(mtn
->mt_line
+ tlen
, line
, elen
);
199 mtn
->mt_line
[tlen
] = '\0';
206 _has_highbit(char const *s
)
213 if ((ui8_t
)*s
& 0x80)
215 while (*s
++ != '\0');
224 _name_highbit(struct name
*np
)
230 if (_has_highbit(np
->n_name
) || _has_highbit(np
->n_fullname
))
239 #endif /* HAVE_ICONV */
241 static enum conversion
242 _conversion_by_encoding(void)
248 if ((cp
= ok_vlook(encoding
)) == NULL
)
249 ret
= MIME_DEFAULT_ENCODING
;
250 else if (!strcmp(cp
, "quoted-printable"))
252 else if (!strcmp(cp
, "8bit"))
254 else if (!strcmp(cp
, "base64"))
257 fprintf(stderr
, _("Warning: invalid encoding %s, using base64\n"),
266 _fwrite_td(struct str
const *input
, enum tdflags flags
, struct str
*rest
,
269 /* TODO note: after send/MIME layer rewrite we will have a string pool
270 * TODO so that memory allocation count drops down massively; for now,
271 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
272 /* TODO well if we get a broken pipe here, and it happens to
273 * TODO happen pretty easy when sleeping in a full pipe buffer,
274 * TODO then the current codebase performs longjump away;
275 * TODO this leaves memory leaks behind ('think up to 3 per,
276 * TODO dep. upon alloca availability). For this to be fixed
277 * TODO we either need to get rid of the longjmp()s (tm) or
278 * TODO the storage must come from the outside or be tracked
279 * TODO in a carrier struct. Best both. But storage reuse
280 * TODO would be a bigbig win besides */
281 /* *input* _may_ point to non-modifyable buffer; but even then it only
282 * needs to be dup'ed away if we have to transform the content */
293 if ((flags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1) {
296 if (rest
!= NULL
&& rest
->l
> 0) {
297 in
.l
= rest
->l
+ input
->l
;
298 in
.s
= buf
= smalloc(in
.l
+1);
299 memcpy(in
.s
, rest
->s
, rest
->l
);
300 memcpy(in
.s
+ rest
->l
, input
->s
, input
->l
);
304 if (n_iconv_str(iconvd
, &out
, &in
, &in
, TRU1
) != 0 && rest
!= NULL
&&
306 /* Incomplete multibyte at EOF is special */
307 if (flags
& _TD_EOF
) {
308 out
.s
= srealloc(out
.s
, out
.l
+ 4);
309 /* TODO 0xFFFD out.s[out.l++] = '[';*/
310 out
.s
[out
.l
++] = '?'; /* TODO 0xFFFD !!! */
311 /* TODO 0xFFFD out.s[out.l++] = ']';*/
313 n_str_add(rest
, &in
);
317 flags
&= ~_TD_BUFCOPY
;
325 makeprint(&in
, &out
);
326 else if (flags
& _TD_BUFCOPY
)
327 n_str_dup(&out
, &in
);
330 if (flags
& TD_DELCTRL
)
331 out
.l
= delctrl(out
.s
, out
.l
);
333 rv
= quoteflt_push(qf
, out
.s
, out
.l
);
337 if (in
.s
!= input
->s
)
344 delctrl(char *cp
, size_t sz
)
350 if (!cntrlchar(cp
[x
]))
359 is_this_enc(char const *line
, char const *encoding
)
361 int rv
, c
, quoted
= 0;
367 while (*line
!= '\0' && *encoding
)
368 if ((c
= *line
++, lowerconv(c
) != *encoding
++))
371 if (quoted
&& *line
== '"')
373 if (*line
== '\0' || whitechar(*line
))
382 mime_write_tohdr(struct str
*in
, FILE *fo
)
384 /* TODO mime_write_tohdr(): we don't know the name of our header->maxcol..
385 * TODO MIME/send layer rewrite: more available state!!
386 * TODO Because of this we cannot make a difference in between structured
387 * TODO and unstructured headers (RFC 2047, 5. (2))
388 * TODO NOT MULTIBYTE SAFE IF AN ENCODED WORD HAS TO BE SPLITTED!
389 * TODO To be better we had to mbtowc_l() (non-std! and no locale!!) and
390 * TODO work char-wise! -> S-CText..
391 * TODO The real problem for STD compatibility is however that "in" is
392 * TODO already iconv(3) encoded to the target character set! We could
393 * TODO also solve it (very expensively!) if we would narrow down to an
394 * TODO encoded word and then iconv(3)+CTencode in one go, in which case
395 * TODO multibyte errors could be catched!
396 * TODO All this doesn't take any care about RFC 2231, but simply and
397 * TODO falsely applies RFC 2047 and normal RFC 822/5322 folding to values
398 * TODO of parameters; part of the problem is that we just don't make a
399 * TODO difference in structured and unstructed headers, as long in TODO!
400 * TODO See also RFC 2047, 5., .." These are the ONLY locations"..
401 * TODO So, for now we require mutt(1)s "rfc2047_parameters=yes" support!!
402 * TODO BTW.: the purpose of QP is to allow non MIME-aware ASCII guys to
403 * TODO read the field nonetheless... */
405 /* Maximum line length *//* XXX we are too inflexible and could use
406 * XXX MIME_LINELEN unless an RFC 2047 encoding was actually used */
407 _MAXCOL
= MIME_LINELEN_RFC2047
410 _FIRST
= 1<<0, /* Nothing written yet, start of string */
411 _NO_QP
= 1<<1, /* No quoted-printable allowed */
412 _NO_B64
= 1<<2, /* Ditto, base64 */
413 _ENC_LAST
= 1<<3, /* Last round generated encoded word */
414 _SHOULD_BEE
= 1<<4, /* Avoid lines longer than SHOULD via encoding */
416 _RND_MASK
= (1<<_RND_SHIFT
) - 1,
417 _SPACE
= 1<<(_RND_SHIFT
+1), /* Leading whitespace */
418 _8BIT
= 1<<(_RND_SHIFT
+2), /* High bit set */
419 _ENCODE
= 1<<(_RND_SHIFT
+3), /* Need encoding */
420 _ENC_B64
= 1<<(_RND_SHIFT
+4), /* - let it be base64 */
421 _OVERLONG
= 1<<(_RND_SHIFT
+5) /* Temporarily rised limit */
424 struct str cout
, cin
;
425 char const *cset7
, *cset8
, *wbot
, *upper
, *wend
, *wcur
;
426 ui32_t cset7_len
, cset8_len
;
431 cout
.s
= NULL
, cout
.l
= 0;
432 cset7
= charset_get_7bit();
433 cset7_len
= (ui32_t
)strlen(cset7
);
434 cset8
= _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
435 cset8_len
= (ui32_t
)strlen(cset8
);
437 /* RFC 1468, "MIME Considerations":
438 * ISO-2022-JP may also be used in MIME Part 2 headers. The "B"
439 * encoding should be used with ISO-2022-JP text. */
440 /* TODO of course, our current implementation won't deal properly with
441 * TODO any stateful encoding at all... (the standard says each encoded
442 * TODO word must include all necessary reset sequences..., i.e., each
443 * TODO encoded word must be a self-contained iconv(3) life cycle) */
444 if (!asccasecmp(cset8
, "iso-2022-jp"))
448 upper
= wbot
+ in
->l
;
449 col
= sizeof("Content-Transfer-Encoding: ") -1; /* dreadful thing */
451 for (sz
= 0; wbot
< upper
; flags
&= ~_FIRST
, wbot
= wend
) {
454 while (wcur
< upper
&& whitechar(*wcur
)) {
459 /* Any occurrence of whitespace resets prevention of lines >SHOULD via
460 * enforced encoding (xxx SHOULD, but.. encoding is expensive!!) */
462 flags
&= ~_SHOULD_BEE
;
464 /* Data ends with WS - dump it and done.
465 * Also, if we have seen multiple successive whitespace characters, then
466 * if there was no encoded word last, i.e., if we can simply take them
467 * over to the output as-is, keep one WS for possible later separation
468 * purposes and simply print the others as-is, directly! */
473 if ((flags
& (_ENC_LAST
| _SPACE
)) == _SPACE
&& wcur
- wbot
> 1) {
478 /* Skip over a word to next non-whitespace, keep track along the way
479 * wether our 7-bit charset suffices to represent the data */
480 for (wend
= wcur
; wend
< upper
; ++wend
) {
481 if (whitechar(*wend
))
483 if ((uc_i
)*wend
& 0x80)
487 /* Decide wether the range has to become encoded or not */
488 i
= PTR2SIZE(wend
- wcur
);
489 j
= mime_cte_mustquote(wcur
, i
, MIMECTE_ISHEAD
);
490 /* If it just cannot fit on a SHOULD line length, force encode */
492 flags
|= _SHOULD_BEE
; /* (Sigh: SHOULD only, not MUST..) */
495 if ((flags
& _SHOULD_BEE
) || j
> 0) {
498 /* Use base64 if requested or more than 50% -37.5-% of the bytes of
499 * the string need to be encoded */
500 if ((flags
& _NO_QP
) || j
>= i
>> 1)/*(i >> 2) + (i >> 3))*/
503 DBG( if (flags
& _8BIT
) assert(flags
& _ENCODE
); )
505 if (!(flags
& _ENCODE
)) {
506 /* Encoded word produced, but no linear whitespace for necessary RFC
507 * 2047 separation? Generate artificial data (bad standard!) */
508 if ((flags
& (_ENC_LAST
| _SPACE
)) == _ENC_LAST
) {
509 if (col
>= _MAXCOL
) {
522 /* todo No effort here: (1) v15.0 has to bring complete rewrite,
523 * todo (2) the standard is braindead and (3) usually this is one
524 * todo word only, and why be smarter than the standard? */
526 i
= PTR2SIZE(wend
- wbot
);
527 if (i
+ col
<= (flags
& _OVERLONG
? MIME_LINELEN_MAX
: _MAXCOL
)) {
528 i
= fwrite(wbot
, sizeof *wbot
, i
, fo
);
534 /* Doesn't fit, try to break the line first; */
537 if (whitechar(*wbot
)) {
538 putc((uc_i
)*wbot
, fo
);
541 putc(' ', fo
); /* Bad standard: artificial data! */
548 /* It is so long that it needs to be broken, effectively causing
549 * artificial spaces to be inserted (bad standard), yuck */
550 /* todo This is not multibyte safe, as above; and completely stupid
551 * todo P.S.: our _SHOULD_BEE prevents these cases in the meanwhile */
552 wcur
= wbot
+ MIME_LINELEN_MAX
- 8;
557 /* Encoding to encoded word(s); deal with leading whitespace, place
558 * a separator first as necessary: encoded words must always be
559 * separated from text and other encoded words with linear WS.
560 * And if an encoded word was last, intermediate whitespace must
561 * also be encoded, otherwise it would get stripped away! */
563 if ((flags
& (_ENC_LAST
| _SPACE
)) != _SPACE
) {
564 /* Reinclude whitespace */
566 /* We don't need to place a separator at the very beginning */
567 if (!(flags
& _FIRST
))
575 * An 'encoded-word' may not be more than 75 characters long,
576 * including 'charset', 'encoding', 'encoded-text', and
577 * delimiters. If it is desirable to encode more text than will
578 * fit in an 'encoded-word' of 75 characters, multiple
579 * 'encoded-word's (separated by CRLF SPACE) may be used.
581 * While there is no limit to the length of a multiple-line
582 * header field, each line of a header field that contains one
583 * or more 'encoded-word's is limited to 76 characters */
585 cin
.s
= UNCONST(wbot
);
586 cin
.l
= PTR2SIZE(wend
- wbot
);
588 if (flags
& _ENC_B64
)
589 j
= b64_encode(&cout
, &cin
, B64_ISHEAD
| B64_ISENCWORD
)->l
;
591 j
= qp_encode(&cout
, &cin
, QP_ISHEAD
| QP_ISENCWORD
)->l
;
592 /* (Avoid trigraphs in the RFC 2047 placeholder..) */
593 i
= j
+ (flags
& _8BIT
? cset8_len
: cset7_len
) + sizeof("=!!B!!=") -1;
598 /* Unfortunately RFC 2047 explicitly disallows encoded words to be
599 * longer (just like RFC 5322's "a line SHOULD fit in 78 but MAY be
600 * 998 characters long"), so we cannot use the _OVERLONG mechanism,
601 * even though all tested mailers seem to support it */
602 if (i
+ col
<= (/*flags & _OVERLONG ? MIME_LINELEN_MAX :*/ _MAXCOL
)) {
603 fprintf(fo
, "%.1s=?%s?%c?%.*s?=",
604 wcur
, (flags
& _8BIT
? cset8
: cset7
),
605 (flags
& _ENC_B64
? 'B' : 'Q'),
606 (int)cout
.l
, cout
.s
);
612 /* Doesn't fit, try to break the line first */
613 /* TODO I've commented out the _FIRST test since we (1) cannot do
614 * TODO _OVERLONG since (MUAs support but) the standard disallows,
615 * TODO and because of our iconv problem i prefer an empty first line
616 * TODO in favour of a possibly messed up multibytes character. :-( */
617 if (col
> 1 /* TODO && !(flags & _FIRST)*/) {
621 if (!(flags
& _SPACE
)) {
624 /*flags |= _OVERLONG;*/
625 goto jenc_retry_same
;
627 putc((uc_i
)*wcur
, fo
);
628 if (whitechar(*(wcur
= wbot
)))
634 /*flags &= ~_OVERLONG;*/
639 /* It is so long that it needs to be broken, effectively causing
640 * artificial data to be inserted (bad standard), yuck */
641 /* todo This is not multibyte safe, as above */
642 /*if (!(flags & _OVERLONG)) {
646 i
= PTR2SIZE(wend
- wbot
) + !!(flags
& _SPACE
);
647 j
= 3 + !(flags
& _ENC_B64
);
651 /* (Note the problem most likely is the transfer-encoding blow,
652 * which is why we test this *after* the decrements.. */
667 convhdra(char const *str
, size_t len
, FILE *fp
)
676 cin
.s
= UNCONST(str
);
680 if (iconvd
!= (iconv_t
)-1) {
682 if (n_iconv_str(iconvd
, &ciconv
, &cin
, NULL
, FAL0
) != 0)
687 ret
= mime_write_tohdr(&cin
, fp
);
690 if (ciconv
.s
!= NULL
)
698 mime_write_tohdr_a(struct str
*in
, FILE *f
) /* TODO error handling */
700 char const *cp
, *lastcp
;
706 if ((cp
= routeaddr(in
->s
)) != NULL
&& cp
> lastcp
) {
707 if ((sz
= convhdra(lastcp
, PTR2SIZE(cp
- lastcp
), f
)) < 0)
715 for ( ; *cp
!= '\0'; ++cp
) {
718 sz
+= fwrite(lastcp
, 1, PTR2SIZE(cp
- lastcp
+ 1), f
);
720 cp
= skip_comment(cp
);
722 if ((x
= convhdra(lastcp
, PTR2SIZE(cp
- lastcp
), f
)) < 0) {
734 if (*cp
== '\\' && cp
[1] != '\0')
741 sz
+= fwrite(lastcp
, 1, PTR2SIZE(cp
- lastcp
), f
);
748 addstr(char **buf
, size_t *sz
, size_t *pos
, char const *str
, size_t len
)
751 *buf
= srealloc(*buf
, *sz
+= len
);
752 memcpy(&(*buf
)[*pos
], str
, len
);
758 addconv(char **buf
, size_t *sz
, size_t *pos
, char const *str
, size_t len
)
765 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
766 addstr(buf
, sz
, pos
, out
.s
, out
.l
);
772 charset_get_7bit(void)
777 if ((t
= ok_vlook(charset_7bit
)) == NULL
)
785 charset_get_8bit(void)
790 if ((t
= ok_vlook(CHARSET_8BIT_OKEY
)) == NULL
)
803 if ((t
= ok_vlook(ttycharset
)) == NULL
)
810 charset_iter_reset(char const *a_charset_to_try_first
)
813 size_t sarrl
[3], len
;
816 UNUSED(a_charset_to_try_first
);
819 sarr
[0] = a_charset_to_try_first
;
820 if ((sarr
[1] = ok_vlook(sendcharsets
)) == NULL
&&
821 ok_blook(sendcharsets_else_ttycharset
))
822 sarr
[1] = charset_get_lc();
823 sarr
[2] = charset_get_8bit();
825 sarr
[2] = charset_get_lc();
828 sarrl
[2] = len
= strlen(sarr
[2]);
830 if ((cp
= UNCONST(sarr
[1])) != NULL
)
831 len
+= (sarrl
[1] = strlen(cp
));
834 if ((cp
= UNCONST(sarr
[0])) != NULL
)
835 len
+= (sarrl
[0] = strlen(cp
));
840 _cs_iter_base
= cp
= salloc(len
+ 1 + 1 +1);
843 if ((len
= sarrl
[0]) != 0) {
844 memcpy(cp
, sarr
[0], len
);
848 if ((len
= sarrl
[1]) != 0) {
849 memcpy(cp
, sarr
[1], len
);
855 memcpy(cp
, sarr
[2], len
);
860 return (_cs_iter
!= NULL
);
864 charset_iter_next(void)
870 rv
= (_cs_iter
!= NULL
);
876 charset_iter_is_valid(void)
881 rv
= (_cs_iter
!= NULL
);
898 charset_iter_recurse(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
901 outer_storage
[0] = _cs_iter_base
;
902 outer_storage
[1] = _cs_iter
;
907 charset_iter_restore(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
910 _cs_iter_base
= outer_storage
[0];
911 _cs_iter
= outer_storage
[1];
917 need_hdrconv(struct header
*hp
, enum gfield w
) /* TODO once only, then iter */
919 char const *ret
= NULL
;
923 if (hp
->h_from
!= NULL
) {
924 if (_name_highbit(hp
->h_from
))
926 } else if (_has_highbit(myaddrs(NULL
)))
928 if (hp
->h_organization
) {
929 if (_has_highbit(hp
->h_organization
))
931 } else if (_has_highbit(ok_vlook(ORGANIZATION
)))
934 if (_name_highbit(hp
->h_replyto
))
936 } else if (_has_highbit(ok_vlook(replyto
)))
939 if (_name_highbit(hp
->h_sender
))
941 } else if (_has_highbit(ok_vlook(sender
)))
944 if ((w
& GTO
) && _name_highbit(hp
->h_to
))
946 if ((w
& GCC
) && _name_highbit(hp
->h_cc
))
948 if ((w
& GBCC
) && _name_highbit(hp
->h_bcc
))
950 if ((w
& GSUBJECT
) && _has_highbit(hp
->h_subject
))
952 ret
= _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
956 #endif /* HAVE_ICONV */
964 if (is_this_enc(p
, "7bit"))
966 else if (is_this_enc(p
, "8bit"))
968 else if (is_this_enc(p
, "base64"))
970 else if (is_this_enc(p
, "binary"))
972 else if (is_this_enc(p
, "quoted-printable"))
981 mime_getparam(char const *param
, char *h
)
983 char *p
= h
, *q
, *rv
= NULL
;
989 if (!whitechar(*p
)) {
991 while (*p
&& (*p
!= ';' || c
== '\\')) {
992 c
= (c
== '\\') ? '\0' : *p
;
1000 while (whitechar(*p
))
1002 if (!ascncasecmp(p
, param
, sz
)) {
1004 while (whitechar(*p
))
1010 while (*p
!= '\0' && (*p
!= ';' || c
== '\\')) {
1011 if (*p
== '"' && c
!= '\\') {
1013 while (*p
!= '\0' && (*p
!= '"' || c
== '\\')) {
1014 c
= (c
== '\\') ? '\0' : *p
;
1019 c
= (c
== '\\') ? '\0' : *p
;
1026 while (whitechar(*p
))
1032 if ((q
= strchr(p
, '"')) == NULL
)
1035 while (*q
!= '\0' && !whitechar(*q
) && *q
!= ';')
1038 sz
= PTR2SIZE(q
- p
);
1039 rv
= salloc(q
- p
+1);
1048 mime_get_boundary(char *h
, size_t *len
)
1054 if ((p
= mime_getparam("boundary", h
)) != NULL
) {
1058 q
= salloc(sz
+ 2 +1);
1060 memcpy(q
+ 2, p
, sz
);
1061 *(q
+ sz
+ 2) = '\0';
1068 mime_create_boundary(void)
1074 snprintf(bp
, 48, "=_%011" PRIu64
"=-%s=_",
1075 (ui64_t
)time_current
.tc_time
, getrandstring(47 - (11 + 6)));
1081 mime_classify_file(FILE *fp
, char const **contenttype
, char const **charset
,
1084 /* TODO classify once only PLEASE PLEASE PLEASE */
1085 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
1086 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
1087 * TODO and report that state to the outer world */
1089 #define F_SIZEOF (sizeof(F_) -1)
1091 char f_buf
[F_SIZEOF
], *f_p
= f_buf
;
1093 _CLEAN
= 0, /* Plain RFC 2822 message */
1094 _NCTT
= 1<<0, /* *contenttype == NULL */
1095 _ISTXT
= 1<<1, /* *contenttype =~ text/ */
1096 _ISTXTCOK
= 1<<2, /* _ISTXT + *mime-allow-text-controls* */
1097 _HIGHBIT
= 1<<3, /* Not 7bit clean */
1098 _LONGLINES
= 1<<4, /* MIME_LINELEN_LIMIT exceed. */
1099 _CTRLCHAR
= 1<<5, /* Control characters seen */
1100 _HASNUL
= 1<<6, /* Contains \0 characters */
1101 _NOTERMNL
= 1<<7, /* Lacks a final newline */
1102 _TRAILWS
= 1<<8, /* Blanks before NL */
1103 _FROM_
= 1<<9 /* ^From_ seen */
1105 enum conversion convert
;
1110 assert(ftell(fp
) == 0x0l
);
1114 if (*contenttype
== NULL
)
1116 else if (!ascncasecmp(*contenttype
, "text/", 5))
1117 ctt
= ok_blook(mime_allow_text_controls
) ? _ISTXT
| _ISTXTCOK
: _ISTXT
;
1118 convert
= _conversion_by_encoding();
1123 /* We have to inspect the file content */
1124 for (curlen
= 0, c
= EOF
;; ++curlen
) {
1130 if (!(ctt
& _ISTXTCOK
))
1134 if (c
== '\n' || c
== EOF
) {
1135 if (curlen
>= MIME_LINELEN_LIMIT
)
1139 if (blankchar(lastc
))
1145 /* A bit hairy is handling of \r=\x0D=CR.
1147 * Control characters other than TAB, or CR and LF as parts of CRLF
1148 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
1149 * we cannot peek the next character. Thus right here, inspect the last
1150 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
1151 /*else*/ if (lastc
== '\r')
1154 /* Control character? XXX this is all ASCII here */
1155 if (c
< 0x20 || c
== 0x7F) {
1156 /* RFC 2045, 6.7, as above ... */
1157 if (c
!= '\t' && c
!= '\r')
1159 /* If there is a escape sequence in backslash notation defined for
1160 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
1161 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
1162 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
1164 if ((c
>= '\x07' && c
<= '\x0D') || c
== '\x1B')
1166 ctt
|= _HASNUL
; /* Force base64 */
1167 if (!(ctt
& _ISTXTCOK
))
1169 } else if ((ui8_t
)c
& 0x80) {
1171 /* TODO count chars with HIGHBIT? libmagic?
1172 * TODO try encode part - base64 if bails? */
1173 if (!(ctt
& (_NCTT
| _ISTXT
))) { /* TODO _NCTT?? */
1174 ctt
|= _HASNUL
; /* Force base64 */
1177 } else if (!(ctt
& _FROM_
) && UICMP(z
, curlen
, <, F_SIZEOF
)) {
1179 if (UICMP(z
, curlen
, ==, F_SIZEOF
- 1) &&
1180 PTR2SIZE(f_p
- f_buf
) == F_SIZEOF
&&
1181 !memcmp(f_buf
, F_
, F_SIZEOF
))
1189 if (ctt
& _HASNUL
) {
1190 convert
= CONV_TOB64
;
1191 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1192 * on request; else enforce what file(1)/libmagic(3) would suggest */
1193 if (ctt
& _ISTXTCOK
)
1195 if (ctt
& (_NCTT
| _ISTXT
))
1196 *contenttype
= "application/octet-stream";
1197 if (*charset
== NULL
)
1198 *charset
= "binary";
1202 if (ctt
& (_LONGLINES
| _CTRLCHAR
| _NOTERMNL
| _TRAILWS
| _FROM_
)) {
1203 if (convert
!= CONV_TOB64
)
1204 convert
= CONV_TOQP
;
1207 if (ctt
& _HIGHBIT
) {
1209 if (ctt
& (_NCTT
| _ISTXT
))
1210 *do_iconv
= ((ctt
& _HIGHBIT
) != 0);
1213 convert
= CONV_7BIT
;
1215 *contenttype
= "text/plain";
1217 /* Not an attachment with specified charset? */
1219 if (*charset
== NULL
) /* TODO MIME/send: iter active? iter! else */
1220 *charset
= (ctt
& _HIGHBIT
) ? _CS_ITER_GET() : charset_get_7bit();
1229 mime_classify_content_of_part(struct mimepart
*mpp
)
1231 enum mimecontent mc
;
1233 union {char const *cp
; long l
;} mce
;
1237 ct
= mpp
->m_ct_type_plain
;
1239 if (!asccasecmp(ct
, "application/octet-stream") && mpp
->m_filename
!= NULL
&&
1240 (mce
.cp
= ok_vlook(mime_counter_evidence
)) != NULL
) {
1241 ct
= mime_classify_content_type_by_fileext(mpp
->m_filename
);
1243 /* TODO add bit 1 to possible *mime-counter-evidence* value
1244 * TODO and let it mean to save the attachment in
1245 * TODO a temporary file that mime_classify_file() can
1246 * TODO examine, and using MIME_TEXT if that gives us
1247 * TODO something that seems to be human readable?! */
1250 mce
.l
= strtol(mce
.cp
, NULL
, 0);
1251 if (mce
.l
& MIMECE_USR_OVWR
)
1252 mpp
->m_ct_type_usr_ovwr
= UNCONST(ct
);
1255 if (strchr(ct
, '/') == NULL
) /* For compatibility with non-MIME */
1257 else if (!asccasecmp(ct
, "text/plain"))
1258 mc
= MIME_TEXT_PLAIN
;
1259 else if (!asccasecmp(ct
, "text/html"))
1260 mc
= MIME_TEXT_HTML
;
1261 else if (!ascncasecmp(ct
, "text/", 5))
1263 else if (!asccasecmp(ct
, "message/rfc822"))
1265 else if (!ascncasecmp(ct
, "message/", 8))
1267 else if (!asccasecmp(ct
, "multipart/alternative"))
1268 mc
= MIME_ALTERNATIVE
;
1269 else if (!asccasecmp(ct
, "multipart/digest"))
1271 else if (!ascncasecmp(ct
, "multipart/", 10))
1273 else if (!asccasecmp(ct
, "application/x-pkcs7-mime") ||
1274 !asccasecmp(ct
, "application/pkcs7-mime"))
1282 mime_classify_content_type_by_fileext(char const *name
)
1284 char *content
= NULL
;
1289 /* TODO mime_classify(): mime.types(5) has *-gz but we search dot!
1290 * TODO i.e., we cannot handle files like dubidu.tar-gz; need globs! */
1291 /* TODO even better: regex, with fast lists for (README|INSTALL|NEWS) etc,
1292 * TODO that, also add some mechanism for filenames without extension */
1293 if ((name
= strrchr(name
, '.')) == NULL
|| *++name
== '\0')
1296 if (_mt_list
== NULL
)
1298 if (NELEM(_mt_bltin
) == 0 && _mt_list
== (struct mtnode
*)-1)
1301 nlen
= strlen(name
);
1302 for (mtn
= _mt_list
; mtn
!= NULL
; mtn
= mtn
->mt_next
) {
1303 char const *ext
= mtn
->mt_line
+ mtn
->mt_mtlen
+ 1,
1306 while (!whitechar(*cp
) && *cp
!= '\0')
1308 /* Better to do case-insensitive comparison on extension, since the
1309 * RFC doesn't specify case of attribute values? */
1310 if (nlen
== PTR2SIZE(cp
- ext
) && !ascncasecmp(name
, ext
, nlen
)) {
1311 content
= savestrbuf(mtn
->mt_line
, mtn
->mt_mtlen
);
1314 while (whitechar(*cp
) && *cp
!= '\0')
1317 } while (*ext
!= '\0');
1325 mimepart_get_handler(struct mimepart
const *mpp
)
1328 #define __L (sizeof(__S) -1)
1329 char const *es
, *cs
;
1334 /* TODO some mechanism for filenames without extension */
1335 el
= ((es
= mpp
->m_filename
) != NULL
&& (es
= strrchr(es
, '.')) != NULL
&&
1336 *++es
!= '\0') ? strlen(es
) : 0;
1337 cl
= ((cs
= mpp
->m_ct_type_usr_ovwr
) != NULL
||
1338 (cs
= mpp
->m_ct_type_plain
) != NULL
) ? strlen(cs
) : 0;
1339 if ((l
= MAX(el
, cl
)) == 0) {
1344 buf
= ac_alloc(__L
+ l
+1);
1345 memcpy(buf
, __S
, __L
);
1347 /* File-extension handlers take precedence.
1348 * Yes, we really "fail" here for file extensions which clash MIME types */
1350 memcpy(buf
+ __L
, es
, el
+1);
1351 for (rv
= buf
+ __L
; *rv
!= '\0'; ++rv
)
1352 *rv
= lowerconv(*rv
);
1354 if ((rv
= vok_vlook(buf
)) != NULL
)
1358 /* Then MIME Content-Type: */
1360 memcpy(buf
+ __L
, cs
, cl
+1);
1361 for (rv
= buf
+ __L
; *rv
!= '\0'; ++rv
)
1362 *rv
= lowerconv(*rv
);
1364 if ((rv
= vok_vlook(buf
)) != NULL
)
1379 c_mimetypes(void *v
)
1387 if (argv
[1] != NULL
)
1389 if (!asccasecmp(*argv
, "show"))
1391 if (!asccasecmp(*argv
, "clear"))
1394 fprintf(stderr
, "Synopsis: mimetypes: %s\n",
1395 _("Either <show> (default) or <clear> the mime.types cache"));
1399 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
1405 if (_mt_list
== NULL
)
1407 if (NELEM(_mt_bltin
) == 0 && _mt_list
== (struct mtnode
*)-1) {
1408 fprintf(stderr
, _("Interpolate what file?\n"));
1413 if ((fp
= Ftmp(NULL
, "mimelist", OF_RDWR
| OF_UNLINK
| OF_REGISTER
, 0600)) ==
1420 for (l
= 0, mtn
= _mt_list
; mtn
!= NULL
; ++l
, mtn
= mtn
->mt_next
)
1421 fprintf(fp
, "%s\t%s\n", mtn
->mt_line
, mtn
->mt_line
+ mtn
->mt_mtlen
+ 1);
1423 page_or_print(fp
, l
);
1429 if (NELEM(_mt_bltin
) == 0 && _mt_list
== (struct mtnode
*)-1)
1431 while ((mtn
= _mt_list
) != NULL
) {
1432 _mt_list
= mtn
->mt_next
;
1439 mime_fromhdr(struct str
const *in
, struct str
*out
, enum tdflags flags
)
1441 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1442 * TODO this is the usual newline problem all around the codebase;
1443 * TODO i.e., if we strip it, then the display misses it ;>
1444 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1445 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1446 * TODO why our display reflects what is contained in the message: the 1:1
1447 * TODO relationship of message content and display!
1448 * TODO instead a header line should be decoded to what it is (a single
1449 * TODO line that is) and it should be objective to the backend wether
1450 * TODO it'll be folded to fit onto the display or not, e.g., for search
1451 * TODO purposes etc. then the only condition we have to honour in here
1452 * TODO is that whitespace in between multiple adjacent MIME encoded words
1453 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1454 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1455 struct str cin
, cout
;
1456 char *p
, *op
, *upper
, *cs
, *cbeg
;
1457 ui32_t convert
, lastenc
, lastoutl
;
1460 iconv_t fhicd
= (iconv_t
)-1;
1466 *(out
->s
= smalloc(1)) = '\0';
1472 tcs
= charset_get_lc();
1476 lastenc
= lastoutl
= 0;
1480 if (*p
== '=' && *(p
+ 1) == '?') {
1483 while (p
< upper
&& *p
!= '?')
1484 ++p
; /* strip charset */
1487 cs
= salloc(PTR2SIZE(++p
- cbeg
));
1488 memcpy(cs
, cbeg
, PTR2SIZE(p
- cbeg
- 1));
1489 cs
[p
- cbeg
- 1] = '\0';
1491 if (fhicd
!= (iconv_t
)-1)
1492 n_iconv_close(fhicd
);
1493 fhicd
= asccasecmp(cs
, tcs
) ? n_iconv_open(tcs
, cs
) : (iconv_t
)-1;
1497 convert
= CONV_FROMB64
;
1500 convert
= CONV_FROMQP
;
1502 default: /* invalid, ignore */
1510 if (PTRCMP(p
+ 1, >=, upper
))
1512 if (*p
++ == '?' && *p
== '=')
1521 if (convert
== CONV_FROMB64
) {
1522 /* XXX Take care for, and strip LF from
1523 * XXX [Invalid Base64 encoding ignored] */
1524 if (b64_decode(&cout
, &cin
, NULL
) == STOP
&&
1525 cout
.s
[cout
.l
- 1] == '\n')
1528 qp_decode(&cout
, &cin
, NULL
);
1532 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
1533 cin
.s
= NULL
, cin
.l
= 0; /* XXX string pool ! */
1534 convert
= n_iconv_str(fhicd
, &cin
, &cout
, NULL
, TRU1
);
1535 out
= n_str_add(out
, &cin
);
1536 if (convert
) /* EINVAL at EOS */
1537 out
= n_str_add_buf(out
, "?", 1);
1541 out
= n_str_add(out
, &cout
);
1542 lastenc
= lastoutl
= out
->l
;
1549 onlyws
= (lastenc
> 0);
1553 if (op
[0] == '=' && (PTRCMP(op
+ 1, ==, upper
) || op
[1] == '?'))
1555 if (onlyws
&& !blankchar(*op
))
1559 out
= n_str_add_buf(out
, p
, PTR2SIZE(op
- p
));
1561 if (!onlyws
|| lastoutl
!= lastenc
)
1566 out
->s
[out
->l
] = '\0';
1568 if (flags
& TD_ISPR
) {
1569 makeprint(out
, &cout
);
1573 if (flags
& TD_DELCTRL
)
1574 out
->l
= delctrl(out
->s
, out
->l
);
1576 if (fhicd
!= (iconv_t
)-1)
1577 n_iconv_close(fhicd
);
1585 mime_fromaddr(char const *name
)
1587 char const *cp
, *lastcp
;
1589 size_t ressz
= 1, rescur
= 0;
1594 if (*name
== '\0') {
1595 res
= savestr(name
);
1599 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1600 addconv(&res
, &ressz
, &rescur
, name
, PTR2SIZE(cp
- name
));
1605 for ( ; *cp
; ++cp
) {
1608 addstr(&res
, &ressz
, &rescur
, lastcp
, PTR2SIZE(cp
- lastcp
+ 1));
1610 cp
= skip_comment(cp
);
1612 addconv(&res
, &ressz
, &rescur
, lastcp
, PTR2SIZE(cp
- lastcp
));
1619 if (*cp
== '\\' && cp
[1] != '\0')
1626 addstr(&res
, &ressz
, &rescur
, lastcp
, PTR2SIZE(cp
- lastcp
));
1627 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1642 xmime_write(char const *ptr
, size_t size
, FILE *f
, enum conversion convert
,
1643 enum tdflags dflags
, struct str
*rest
)
1646 struct quoteflt
*qf
;
1649 quoteflt_reset(qf
= quoteflt_dummy(), f
);
1650 rv
= mime_write(ptr
, size
, f
, convert
, dflags
, qf
, rest
);
1651 assert(quoteflt_flush(qf
) == 0);
1657 mime_write(char const *ptr
, size_t size
, FILE *f
,
1658 enum conversion convert
, enum tdflags dflags
,
1659 struct quoteflt
*qf
, struct str
*rest
)
1661 /* TODO note: after send/MIME layer rewrite we will have a string pool
1662 * TODO so that memory allocation count drops down massively; for now,
1663 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1669 in
.s
= UNCONST(ptr
);
1674 dflags
|= _TD_BUFCOPY
;
1675 if ((sz
= size
) == 0) {
1676 if (rest
!= NULL
&& rest
->l
!= 0)
1682 if ((dflags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1 &&
1683 (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1684 convert
== CONV_TOB64
|| convert
== CONV_TOHDR
)) {
1685 if (n_iconv_str(iconvd
, &out
, &in
, NULL
, FAL0
) != 0) {
1686 /* XXX report conversion error? */;
1692 dflags
&= ~_TD_BUFCOPY
;
1699 state
= qp_decode(&out
, &in
, rest
);
1702 qp_encode(&out
, &in
, QP_NONE
);
1705 sz
= quoteflt_push(qf
, in
.s
, in
.l
);
1710 case CONV_FROMB64_T
:
1711 state
= b64_decode(&out
, &in
, rest
);
1713 if ((sz
= out
.l
) != 0) {
1714 ui32_t opl
= qf
->qf_pfix_len
;
1716 qf
->qf_pfix_len
= 0;
1717 sz
= _fwrite_td(&out
, (dflags
& ~_TD_BUFCOPY
), rest
, qf
);
1718 qf
->qf_pfix_len
= opl
;
1724 b64_encode(&out
, &in
, B64_LF
| B64_MULTILINE
);
1726 sz
= fwrite(out
.s
, sizeof *out
.s
, out
.l
, f
);
1727 if (sz
!= (ssize_t
)out
.l
)
1731 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
| (dflags
& TD_DELCTRL
));
1732 sz
= quoteflt_push(qf
, out
.s
, out
.l
);
1735 sz
= mime_write_tohdr(&in
, f
);
1738 sz
= mime_write_tohdr_a(&in
, f
);
1741 sz
= _fwrite_td(&in
, dflags
, NULL
, qf
);