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
) /* TODO rewrite - FAST! */
384 struct str cin
, cout
;
385 char buf
[B64_LINESIZE
+1]; /* (No CR/LF used) */
386 char const *charset7
, *charset
, *upper
, *wbeg
, *wend
, *lastspc
,
388 size_t col
= 0, quoteany
, wr
, charsetlen
,
389 maxcol
= 65 /* there is the header field's name, too */;
391 bool_t highbit
, mustquote
, broken
;
394 charset7
= charset_get_7bit();
395 charset
= _CS_ITER_GET(); /* TODO MIME/send layer: iter active? iter! else */
396 wr
= strlen(charset7
);
397 charsetlen
= strlen(charset
);
398 charsetlen
= MAX(charsetlen
, wr
);
399 upper
= in
->s
+ in
->l
;
401 /* xxx note this results in too much hits since =/? force quoting even
402 * xxx if they don't form =? etc. */
403 quoteany
= mime_cte_mustquote(in
->s
, in
->l
, TRU1
);
407 for (wbeg
= in
->s
; wbeg
< upper
; ++wbeg
)
408 if ((ui8_t
)*wbeg
& 0x80) {
410 if (charset
== NULL
) {
417 if (quoteany
<< 1 > in
->l
) {
418 /* Print the entire field in base64 */
419 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
421 cin
.s
= UNCONST(wbeg
);
423 cin
.l
= PTR2SIZE(wend
- wbeg
);
424 if (cin
.l
* 4/3 + 7 + charsetlen
< maxcol
- col
) {
427 wr
= fprintf(fo
, "=?%s?B?%s?=", (highbit
? charset
: charset7
),
428 b64_encode(&cout
, &cin
, B64_BUF
)->s
);
432 fwrite("\n ", sizeof(char), 2, fo
);
450 /* Print the field word-wise in quoted-printable */
452 for (wbeg
= in
->s
; wbeg
< upper
; wbeg
= wend
) {
454 while (wbeg
< upper
&& whitechar(*wbeg
)) {
455 lastspc
= lastspc
? lastspc
: wbeg
;
462 while (lastspc
< wbeg
) {
463 putc(*lastspc
&0377, fo
);
473 for (wend
= wbeg
; wend
< upper
&& !whitechar(*wend
); ++wend
)
474 if ((ui8_t
)*wend
& 0x80)
476 mustquote
= (mime_cte_mustquote(wbeg
, PTR2SIZE(wend
- wbeg
), TRU1
)
479 if (mustquote
|| broken
||
480 (PTR2SIZE(wend
- wbeg
) >= 76-5 && quoteany
)) {
481 for (cout
.s
= NULL
;;) {
482 cin
.s
= UNCONST(lastwordend
? lastwordend
: wbeg
);
483 cin
.l
= PTR2SIZE(wend
- cin
.s
);
484 qp_encode(&cout
, &cin
, QP_ISHEAD
);
485 wr
= cout
.l
+ charsetlen
+ 7;
487 if (col
<= maxcol
&& wr
<= maxcol
- col
) {
489 /* TODO because we included the WS in the encoded str,
491 * TODO RFC: "any 'linear-white-space' that separates
492 * TODO a pair of adjacent 'encoded-word's is ignored" */
497 fprintf(fo
, "=?%s?Q?%.*s?=",
498 (highbit
? charset
: charset7
), (int)cout
.l
, cout
.s
);
502 } else if (col
> 1) {
503 /* TODO assuming SP separator, ignore *lastspc* !?? */
505 if (lastspc
!= NULL
) {
530 if (col
&& PTR2SIZE(wend
- wbeg
) > maxcol
- col
) {
535 if (lastspc
== NULL
) {
540 maxcol
-= PTR2SIZE(wbeg
- lastspc
);
543 while (lastspc
< wbeg
) {
544 putc(*lastspc
&0377, fo
);
548 wr
= fwrite(wbeg
, sizeof *wbeg
, PTR2SIZE(wend
- wbeg
), fo
);
561 convhdra(char const *str
, size_t len
, FILE *fp
)
570 cin
.s
= UNCONST(str
);
574 if (iconvd
!= (iconv_t
)-1) {
576 if (n_iconv_str(iconvd
, &ciconv
, &cin
, NULL
, FAL0
) != 0)
581 ret
= mime_write_tohdr(&cin
, fp
);
584 if (ciconv
.s
!= NULL
)
592 mime_write_tohdr_a(struct str
*in
, FILE *f
) /* TODO error handling */
594 char const *cp
, *lastcp
;
600 if ((cp
= routeaddr(in
->s
)) != NULL
&& cp
> lastcp
) {
601 if ((sz
= convhdra(lastcp
, PTR2SIZE(cp
- lastcp
), f
)) < 0)
609 for ( ; *cp
!= '\0'; ++cp
) {
612 sz
+= fwrite(lastcp
, 1, PTR2SIZE(cp
- lastcp
+ 1), f
);
614 cp
= skip_comment(cp
);
616 if ((x
= convhdra(lastcp
, PTR2SIZE(cp
- lastcp
), f
)) < 0) {
628 if (*cp
== '\\' && cp
[1] != '\0')
635 sz
+= fwrite(lastcp
, 1, PTR2SIZE(cp
- lastcp
), f
);
642 addstr(char **buf
, size_t *sz
, size_t *pos
, char const *str
, size_t len
)
645 *buf
= srealloc(*buf
, *sz
+= len
);
646 memcpy(&(*buf
)[*pos
], str
, len
);
652 addconv(char **buf
, size_t *sz
, size_t *pos
, char const *str
, size_t len
)
659 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
660 addstr(buf
, sz
, pos
, out
.s
, out
.l
);
666 charset_get_7bit(void)
671 if ((t
= ok_vlook(charset_7bit
)) == NULL
)
679 charset_get_8bit(void)
684 if ((t
= ok_vlook(CHARSET_8BIT_OKEY
)) == NULL
)
697 if ((t
= ok_vlook(ttycharset
)) == NULL
)
704 charset_iter_reset(char const *a_charset_to_try_first
)
707 size_t sarrl
[3], len
;
710 UNUSED(a_charset_to_try_first
);
713 sarr
[0] = a_charset_to_try_first
;
714 if ((sarr
[1] = ok_vlook(sendcharsets
)) == NULL
&&
715 ok_blook(sendcharsets_else_ttycharset
))
716 sarr
[1] = charset_get_lc();
717 sarr
[2] = charset_get_8bit();
719 sarr
[2] = charset_get_lc();
722 sarrl
[2] = len
= strlen(sarr
[2]);
724 if ((cp
= UNCONST(sarr
[1])) != NULL
)
725 len
+= (sarrl
[1] = strlen(cp
));
728 if ((cp
= UNCONST(sarr
[0])) != NULL
)
729 len
+= (sarrl
[0] = strlen(cp
));
734 _cs_iter_base
= cp
= salloc(len
+ 1 + 1 +1);
737 if ((len
= sarrl
[0]) != 0) {
738 memcpy(cp
, sarr
[0], len
);
742 if ((len
= sarrl
[1]) != 0) {
743 memcpy(cp
, sarr
[1], len
);
749 memcpy(cp
, sarr
[2], len
);
754 return (_cs_iter
!= NULL
);
758 charset_iter_next(void)
764 rv
= (_cs_iter
!= NULL
);
770 charset_iter_is_valid(void)
775 rv
= (_cs_iter
!= NULL
);
792 charset_iter_recurse(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
795 outer_storage
[0] = _cs_iter_base
;
796 outer_storage
[1] = _cs_iter
;
801 charset_iter_restore(char *outer_storage
[2]) /* TODO LEGACY FUN, REMOVE */
804 _cs_iter_base
= outer_storage
[0];
805 _cs_iter
= outer_storage
[1];
811 need_hdrconv(struct header
*hp
, enum gfield w
) /* TODO once only, then iter */
813 char const *ret
= NULL
;
817 if (hp
->h_from
!= NULL
) {
818 if (_name_highbit(hp
->h_from
))
820 } else if (_has_highbit(myaddrs(NULL
)))
822 if (hp
->h_organization
) {
823 if (_has_highbit(hp
->h_organization
))
825 } else if (_has_highbit(ok_vlook(ORGANIZATION
)))
828 if (_name_highbit(hp
->h_replyto
))
830 } else if (_has_highbit(ok_vlook(replyto
)))
833 if (_name_highbit(hp
->h_sender
))
835 } else if (_has_highbit(ok_vlook(sender
)))
838 if ((w
& GTO
) && _name_highbit(hp
->h_to
))
840 if ((w
& GCC
) && _name_highbit(hp
->h_cc
))
842 if ((w
& GBCC
) && _name_highbit(hp
->h_bcc
))
844 if ((w
& GSUBJECT
) && _has_highbit(hp
->h_subject
))
846 ret
= _CS_ITER_GET(); /* TODO MIME/send: iter active? iter! else */
850 #endif /* HAVE_ICONV */
858 if (is_this_enc(p
, "7bit"))
860 else if (is_this_enc(p
, "8bit"))
862 else if (is_this_enc(p
, "base64"))
864 else if (is_this_enc(p
, "binary"))
866 else if (is_this_enc(p
, "quoted-printable"))
875 mime_getparam(char const *param
, char *h
)
877 char *p
= h
, *q
, *rv
= NULL
;
883 if (!whitechar(*p
)) {
885 while (*p
&& (*p
!= ';' || c
== '\\')) {
886 c
= (c
== '\\') ? '\0' : *p
;
894 while (whitechar(*p
))
896 if (!ascncasecmp(p
, param
, sz
)) {
898 while (whitechar(*p
))
904 while (*p
!= '\0' && (*p
!= ';' || c
== '\\')) {
905 if (*p
== '"' && c
!= '\\') {
907 while (*p
!= '\0' && (*p
!= '"' || c
== '\\')) {
908 c
= (c
== '\\') ? '\0' : *p
;
913 c
= (c
== '\\') ? '\0' : *p
;
920 while (whitechar(*p
))
926 if ((q
= strchr(p
, '"')) == NULL
)
929 while (*q
!= '\0' && !whitechar(*q
) && *q
!= ';')
932 sz
= PTR2SIZE(q
- p
);
933 rv
= salloc(q
- p
+1);
942 mime_get_boundary(char *h
, size_t *len
)
948 if ((p
= mime_getparam("boundary", h
)) != NULL
) {
952 q
= salloc(sz
+ 2 +1);
954 memcpy(q
+ 2, p
, sz
);
955 *(q
+ sz
+ 2) = '\0';
962 mime_create_boundary(void)
968 snprintf(bp
, 48, "=_%011lu=-%s=_", (ul_it
)time_current
.tc_time
,
969 getrandstring(47 - (11 + 6)));
975 mime_classify_file(FILE *fp
, char const **contenttype
, char const **charset
,
978 /* TODO classify once only PLEASE PLEASE PLEASE */
979 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
980 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
981 * TODO and report that state to the outer world */
983 #define F_SIZEOF (sizeof(F_) -1)
985 char f_buf
[F_SIZEOF
], *f_p
= f_buf
;
987 _CLEAN
= 0, /* Plain RFC 2822 message */
988 _NCTT
= 1<<0, /* *contenttype == NULL */
989 _ISTXT
= 1<<1, /* *contenttype =~ text/ */
990 _ISTXTCOK
= 1<<2, /* _ISTXT + *mime-allow-text-controls* */
991 _HIGHBIT
= 1<<3, /* Not 7bit clean */
992 _LONGLINES
= 1<<4, /* MIME_LINELEN_LIMIT exceed. */
993 _CTRLCHAR
= 1<<5, /* Control characters seen */
994 _HASNUL
= 1<<6, /* Contains \0 characters */
995 _NOTERMNL
= 1<<7, /* Lacks a final newline */
996 _TRAILWS
= 1<<8, /* Blanks before NL */
997 _FROM_
= 1<<9 /* ^From_ seen */
999 enum conversion convert
;
1004 assert(ftell(fp
) == 0x0l
);
1008 if (*contenttype
== NULL
)
1010 else if (!ascncasecmp(*contenttype
, "text/", 5))
1011 ctt
= ok_blook(mime_allow_text_controls
) ? _ISTXT
| _ISTXTCOK
: _ISTXT
;
1012 convert
= _conversion_by_encoding();
1017 /* We have to inspect the file content */
1018 for (curlen
= 0, c
= EOF
;; ++curlen
) {
1024 if (!(ctt
& _ISTXTCOK
))
1028 if (c
== '\n' || c
== EOF
) {
1029 if (curlen
>= MIME_LINELEN_LIMIT
)
1033 if (blankchar(lastc
))
1039 /* A bit hairy is handling of \r=\x0D=CR.
1041 * Control characters other than TAB, or CR and LF as parts of CRLF
1042 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
1043 * we cannot peek the next character. Thus right here, inspect the last
1044 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
1045 /*else*/ if (lastc
== '\r')
1048 /* Control character? XXX this is all ASCII here */
1049 if (c
< 0x20 || c
== 0x7F) {
1050 /* RFC 2045, 6.7, as above ... */
1051 if (c
!= '\t' && c
!= '\r')
1053 /* If there is a escape sequence in backslash notation defined for
1054 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
1055 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
1056 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
1058 if ((c
>= '\x07' && c
<= '\x0D') || c
== '\x1B')
1060 ctt
|= _HASNUL
; /* Force base64 */
1061 if (!(ctt
& _ISTXTCOK
))
1063 } else if ((ui8_t
)c
& 0x80) {
1065 /* TODO count chars with HIGHBIT? libmagic?
1066 * TODO try encode part - base64 if bails? */
1067 if (!(ctt
& (_NCTT
| _ISTXT
))) { /* TODO _NCTT?? */
1068 ctt
|= _HASNUL
; /* Force base64 */
1071 } else if (!(ctt
& _FROM_
) && UICMP(z
, curlen
, <, F_SIZEOF
)) {
1073 if (UICMP(z
, curlen
, ==, F_SIZEOF
- 1) &&
1074 PTR2SIZE(f_p
- f_buf
) == F_SIZEOF
&&
1075 !memcmp(f_buf
, F_
, F_SIZEOF
))
1083 if (ctt
& _HASNUL
) {
1084 convert
= CONV_TOB64
;
1085 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1086 * on request; else enforce what file(1)/libmagic(3) would suggest */
1087 if (ctt
& _ISTXTCOK
)
1089 if (ctt
& (_NCTT
| _ISTXT
))
1090 *contenttype
= "application/octet-stream";
1091 if (*charset
== NULL
)
1092 *charset
= "binary";
1096 if (ctt
& (_LONGLINES
| _CTRLCHAR
| _NOTERMNL
| _TRAILWS
| _FROM_
)) {
1097 if (convert
!= CONV_TOB64
)
1098 convert
= CONV_TOQP
;
1101 if (ctt
& _HIGHBIT
) {
1103 if (ctt
& (_NCTT
| _ISTXT
))
1104 *do_iconv
= ((ctt
& _HIGHBIT
) != 0);
1107 convert
= CONV_7BIT
;
1109 *contenttype
= "text/plain";
1111 /* Not an attachment with specified charset? */
1113 if (*charset
== NULL
) /* TODO MIME/send: iter active? iter! else */
1114 *charset
= (ctt
& _HIGHBIT
) ? _CS_ITER_GET() : charset_get_7bit();
1123 mime_classify_content_of_part(struct mimepart
const *mip
)
1125 enum mimecontent mc
;
1130 ct
= mip
->m_ct_type_plain
;
1132 if (!asccasecmp(ct
, "application/octet-stream") &&
1133 mip
->m_filename
!= NULL
&& ok_blook(mime_counter_evidence
)) {
1134 ct
= mime_classify_content_type_by_fileext(mip
->m_filename
);
1136 /* TODO how about let *mime-counter-evidence* have
1137 * TODO a value, and if set, saving the attachment in
1138 * TODO a temporary file that mime_classify_file() can
1139 * TODO examine, and using MIME_TEXT if that gives us
1140 * TODO something that seems to be human readable?! */
1143 if (strchr(ct
, '/') == NULL
) /* For compatibility with non-MIME */
1145 else if (!asccasecmp(ct
, "text/plain"))
1146 mc
= MIME_TEXT_PLAIN
;
1147 else if (!asccasecmp(ct
, "text/html"))
1148 mc
= MIME_TEXT_HTML
;
1149 else if (!ascncasecmp(ct
, "text/", 5))
1151 else if (!asccasecmp(ct
, "message/rfc822"))
1153 else if (!ascncasecmp(ct
, "message/", 8))
1155 else if (!asccasecmp(ct
, "multipart/alternative"))
1156 mc
= MIME_ALTERNATIVE
;
1157 else if (!asccasecmp(ct
, "multipart/digest"))
1159 else if (!ascncasecmp(ct
, "multipart/", 10))
1161 else if (!asccasecmp(ct
, "application/x-pkcs7-mime") ||
1162 !asccasecmp(ct
, "application/pkcs7-mime"))
1170 mime_classify_content_type_by_fileext(char const *name
)
1172 char *content
= NULL
;
1177 if ((name
= strrchr(name
, '.')) == NULL
|| *++name
== '\0')
1180 if (_mt_list
== NULL
)
1182 if (NELEM(_mt_bltin
) == 0 && _mt_list
== (struct mtnode
*)-1)
1185 nlen
= strlen(name
);
1186 for (mtn
= _mt_list
; mtn
!= NULL
; mtn
= mtn
->mt_next
) {
1187 char const *ext
= mtn
->mt_line
+ mtn
->mt_mtlen
+ 1,
1190 while (!whitechar(*cp
) && *cp
!= '\0')
1192 /* Better to do case-insensitive comparison on extension, since the
1193 * RFC doesn't specify case of attribute values? */
1194 if (nlen
== PTR2SIZE(cp
- ext
) && !ascncasecmp(name
, ext
, nlen
)) {
1195 content
= savestrbuf(mtn
->mt_line
, mtn
->mt_mtlen
);
1198 while (whitechar(*cp
) && *cp
!= '\0')
1201 } while (*ext
!= '\0');
1209 c_mimetypes(void *v
)
1217 if (argv
[1] != NULL
)
1219 if (!asccasecmp(*argv
, "show"))
1221 if (!asccasecmp(*argv
, "clear"))
1224 fprintf(stderr
, "Synopsis: mimetypes: %s\n",
1225 _("Either <show> (default) or <clear> the mime.types cache"));
1229 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
1235 if (_mt_list
== NULL
)
1237 if (NELEM(_mt_bltin
) == 0 && _mt_list
== (struct mtnode
*)-1) {
1238 fprintf(stderr
, _("Interpolate what file?\n"));
1243 if ((fp
= Ftmp(NULL
, "mimelist", OF_RDWR
| OF_UNLINK
| OF_REGISTER
, 0600)) ==
1250 for (l
= 0, mtn
= _mt_list
; mtn
!= NULL
; ++l
, mtn
= mtn
->mt_next
)
1251 fprintf(fp
, "%s\t%s\n", mtn
->mt_line
, mtn
->mt_line
+ mtn
->mt_mtlen
+ 1);
1253 page_or_print(fp
, l
);
1259 if (NELEM(_mt_bltin
) == 0 && _mt_list
== (struct mtnode
*)-1)
1261 while ((mtn
= _mt_list
) != NULL
) {
1262 _mt_list
= mtn
->mt_next
;
1269 mime_fromhdr(struct str
const *in
, struct str
*out
, enum tdflags flags
)
1271 /* TODO mime_fromhdr(): is called with strings that contain newlines;
1272 * TODO this is the usual newline problem all around the codebase;
1273 * TODO i.e., if we strip it, then the display misses it ;>
1274 * TODO this is why it is so messy and why S-nail v14.2 plus additional
1275 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
1276 * TODO why our display reflects what is contained in the message: the 1:1
1277 * TODO relationship of message content and display!
1278 * TODO instead a header line should be decoded to what it is (a single
1279 * TODO line that is) and it should be objective to the backend wether
1280 * TODO it'll be folded to fit onto the display or not, e.g., for search
1281 * TODO purposes etc. then the only condition we have to honour in here
1282 * TODO is that whitespace in between multiple adjacent MIME encoded words
1283 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
1284 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
1285 struct str cin
, cout
;
1286 char *p
, *op
, *upper
, *cs
, *cbeg
;
1287 ui32_t convert
, lastenc
, lastoutl
;
1290 iconv_t fhicd
= (iconv_t
)-1;
1296 *(out
->s
= smalloc(1)) = '\0';
1302 tcs
= charset_get_lc();
1306 lastenc
= lastoutl
= 0;
1310 if (*p
== '=' && *(p
+ 1) == '?') {
1313 while (p
< upper
&& *p
!= '?')
1314 ++p
; /* strip charset */
1317 cs
= salloc(PTR2SIZE(++p
- cbeg
));
1318 memcpy(cs
, cbeg
, PTR2SIZE(p
- cbeg
- 1));
1319 cs
[p
- cbeg
- 1] = '\0';
1321 if (fhicd
!= (iconv_t
)-1)
1322 n_iconv_close(fhicd
);
1323 fhicd
= asccasecmp(cs
, tcs
) ? n_iconv_open(tcs
, cs
) : (iconv_t
)-1;
1327 convert
= CONV_FROMB64
;
1330 convert
= CONV_FROMQP
;
1332 default: /* invalid, ignore */
1340 if (PTRCMP(p
+ 1, >=, upper
))
1342 if (*p
++ == '?' && *p
== '=')
1351 if (convert
== CONV_FROMB64
) {
1352 /* XXX Take care for, and strip LF from
1353 * XXX [Invalid Base64 encoding ignored] */
1354 if (b64_decode(&cout
, &cin
, NULL
) == STOP
&&
1355 cout
.s
[cout
.l
- 1] == '\n')
1358 qp_decode(&cout
, &cin
, NULL
);
1362 if ((flags
& TD_ICONV
) && fhicd
!= (iconv_t
)-1) {
1363 cin
.s
= NULL
, cin
.l
= 0; /* XXX string pool ! */
1364 convert
= n_iconv_str(fhicd
, &cin
, &cout
, NULL
, TRU1
);
1365 out
= n_str_add(out
, &cin
);
1366 if (convert
) /* EINVAL at EOS */
1367 out
= n_str_add_buf(out
, "?", 1);
1371 out
= n_str_add(out
, &cout
);
1372 lastenc
= lastoutl
= out
->l
;
1379 onlyws
= (lastenc
> 0);
1383 if (op
[0] == '=' && (PTRCMP(op
+ 1, ==, upper
) || op
[1] == '?'))
1385 if (onlyws
&& !blankchar(*op
))
1389 out
= n_str_add_buf(out
, p
, PTR2SIZE(op
- p
));
1391 if (!onlyws
|| lastoutl
!= lastenc
)
1396 out
->s
[out
->l
] = '\0';
1398 if (flags
& TD_ISPR
) {
1399 makeprint(out
, &cout
);
1403 if (flags
& TD_DELCTRL
)
1404 out
->l
= delctrl(out
->s
, out
->l
);
1406 if (fhicd
!= (iconv_t
)-1)
1407 n_iconv_close(fhicd
);
1415 mime_fromaddr(char const *name
)
1417 char const *cp
, *lastcp
;
1419 size_t ressz
= 1, rescur
= 0;
1424 if (*name
== '\0') {
1425 res
= savestr(name
);
1429 if ((cp
= routeaddr(name
)) != NULL
&& cp
> name
) {
1430 addconv(&res
, &ressz
, &rescur
, name
, PTR2SIZE(cp
- name
));
1435 for ( ; *cp
; ++cp
) {
1438 addstr(&res
, &ressz
, &rescur
, lastcp
, PTR2SIZE(cp
- lastcp
+ 1));
1440 cp
= skip_comment(cp
);
1442 addconv(&res
, &ressz
, &rescur
, lastcp
, PTR2SIZE(cp
- lastcp
));
1449 if (*cp
== '\\' && cp
[1] != '\0')
1456 addstr(&res
, &ressz
, &rescur
, lastcp
, PTR2SIZE(cp
- lastcp
));
1457 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1472 xmime_write(char const *ptr
, size_t size
, FILE *f
, enum conversion convert
,
1473 enum tdflags dflags
, struct str
*rest
)
1476 struct quoteflt
*qf
;
1479 quoteflt_reset(qf
= quoteflt_dummy(), f
);
1480 rv
= mime_write(ptr
, size
, f
, convert
, dflags
, qf
, rest
);
1481 assert(quoteflt_flush(qf
) == 0);
1487 mime_write(char const *ptr
, size_t size
, FILE *f
,
1488 enum conversion convert
, enum tdflags dflags
,
1489 struct quoteflt
*qf
, struct str
*rest
)
1491 /* TODO note: after send/MIME layer rewrite we will have a string pool
1492 * TODO so that memory allocation count drops down massively; for now,
1493 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1499 in
.s
= UNCONST(ptr
);
1504 dflags
|= _TD_BUFCOPY
;
1505 if ((sz
= size
) == 0) {
1506 if (rest
!= NULL
&& rest
->l
!= 0)
1512 if ((dflags
& TD_ICONV
) && iconvd
!= (iconv_t
)-1 &&
1513 (convert
== CONV_TOQP
|| convert
== CONV_8BIT
||
1514 convert
== CONV_TOB64
|| convert
== CONV_TOHDR
)) {
1515 if (n_iconv_str(iconvd
, &out
, &in
, NULL
, FAL0
) != 0) {
1516 /* XXX report conversion error? */;
1522 dflags
&= ~_TD_BUFCOPY
;
1529 state
= qp_decode(&out
, &in
, rest
);
1532 qp_encode(&out
, &in
, QP_NONE
);
1535 sz
= quoteflt_push(qf
, in
.s
, in
.l
);
1540 case CONV_FROMB64_T
:
1541 state
= b64_decode(&out
, &in
, rest
);
1543 if ((sz
= out
.l
) != 0) {
1544 ui32_t opl
= qf
->qf_pfix_len
;
1546 qf
->qf_pfix_len
= 0;
1547 sz
= _fwrite_td(&out
, (dflags
& ~_TD_BUFCOPY
), rest
, qf
);
1548 qf
->qf_pfix_len
= opl
;
1554 b64_encode(&out
, &in
, B64_LF
| B64_MULTILINE
);
1556 sz
= fwrite(out
.s
, sizeof *out
.s
, out
.l
, f
);
1557 if (sz
!= (ssize_t
)out
.l
)
1561 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
| (dflags
& TD_DELCTRL
));
1562 sz
= quoteflt_push(qf
, out
.s
, out
.l
);
1565 sz
= mime_write_tohdr(&in
, f
);
1568 sz
= mime_write_tohdr_a(&in
, f
);
1571 sz
= _fwrite_td(&in
, dflags
, NULL
, qf
);
1583 /* vim:set fenc=utf-8:s-it-mode */