mime_fromhdr(): fix my rewrite again..
[s-mailx.git] / mime.c
blob6ee55ccb334f316c3b0c36eb7aa74ad5917c4e77
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>.
6 */
7 /*
8 * Copyright (c) 2000
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
13 * are met:
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
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #define _CHARSET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
46 struct mtnode {
47 struct mtnode *mt_next;
48 size_t mt_mtlen; /* Length of MIME type string */
49 char mt_line[VFIELD_SIZE(8)];
52 static char const *const _mt_sources[] = {
53 /* XXX Order fixed due to *mimetypes-load-control* handling! */
54 MIME_TYPES_USR, MIME_TYPES_SYS, NULL
56 *const _mt_bltin[] = {
57 #include "mime_types.h"
58 NULL
61 struct mtnode *_mt_list;
62 char *_cs_iter_base, *_cs_iter;
64 /* Initialize MIME type list */
65 static void _mt_init(void);
66 static void __mt_add_line(char const *line, struct mtnode **tail);
68 /* Get the conversion that matches *encoding* */
69 static enum conversion _conversion_by_encoding(void);
71 /* fwrite(3) while checking for displayability */
72 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
73 struct str *rest, struct quoteflt *qf);
75 static size_t delctrl(char *cp, size_t sz);
76 static int has_highbit(register const char *s);
77 static int is_this_enc(const char *line, const char *encoding);
78 static size_t mime_write_tohdr(struct str *in, FILE *fo);
79 static size_t convhdra(char const *str, size_t len, FILE *fp);
80 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
81 static void addstr(char **buf, size_t *sz, size_t *pos,
82 char const *str, size_t len);
83 static void addconv(char **buf, size_t *sz, size_t *pos,
84 char const *str, size_t len);
86 static void
87 _mt_init(void)
89 struct mtnode *tail = NULL;
90 char *line = NULL;
91 size_t linesize = 0;
92 ui_it idx, idx_ok;
93 char const *ccp, *const*srcs;
94 FILE *fp;
96 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
97 idx_ok = (ui_it)-1;
98 else for (idx_ok = 0; *ccp != '\0'; ++ccp)
99 switch (*ccp) {
100 case 'S':
101 case 's':
102 idx_ok |= 1 << 1;
103 break;
104 case 'U':
105 case 'u':
106 idx_ok |= 1 << 0;
107 break;
108 default:
109 /* XXX bad *mimetypes-load-control*; log error? */
110 break;
113 for (idx = 1, srcs = _mt_sources; *srcs != NULL; idx <<= 1, ++srcs) {
114 if ((idx & idx_ok) == 0 || (ccp = file_expand(*srcs)) == NULL)
115 continue;
116 if ((fp = Fopen(ccp, "r")) == NULL) {
117 /*fprintf(stderr, tr(176, "Cannot open %s\n"), fn);*/
118 continue;
120 while (fgetline(&line, &linesize, NULL, NULL, fp, 0))
121 __mt_add_line(line, &tail);
122 Fclose(fp);
124 if (line != NULL)
125 free(line);
127 for (srcs = _mt_bltin; *srcs != NULL; ++srcs)
128 __mt_add_line(*srcs, &tail);
131 static void
132 __mt_add_line(char const *line, struct mtnode **tail) /* XXX diag? dups!*/
134 char const *typ;
135 size_t tlen, elen;
136 struct mtnode *mtn;
138 if (! alphachar(*line))
139 goto jleave;
141 typ = line;
142 while (blankchar(*line) == 0 && *line != '\0')
143 ++line;
144 if (*line == '\0')
145 goto jleave;
146 tlen = (size_t)(line - typ);
148 while (blankchar(*line) != 0 && *line != '\0')
149 ++line;
150 if (*line == '\0')
151 goto jleave;
153 elen = strlen(line);
154 if (line[elen - 1] == '\n' && line[--elen] == '\0')
155 goto jleave;
157 mtn = smalloc(sizeof(struct mtnode) -
158 VFIELD_SIZEOF(struct mtnode, mt_line) +
159 tlen + 1 + elen + 1);
160 if (*tail != NULL)
161 (*tail)->mt_next = mtn;
162 else
163 _mt_list = mtn;
164 *tail = mtn;
165 mtn->mt_next = NULL;
166 mtn->mt_mtlen = tlen;
167 memcpy(mtn->mt_line, typ, tlen);
168 mtn->mt_line[tlen] = '\0';
169 ++tlen;
170 memcpy(mtn->mt_line + tlen, line, elen);
171 tlen += elen;
172 mtn->mt_line[tlen] = '\0';
173 jleave: ;
176 static enum conversion
177 _conversion_by_encoding(void)
179 char const *cp;
180 enum conversion ret;
182 if ((cp = ok_vlook(encoding)) == NULL)
183 ret = MIME_DEFAULT_ENCODING;
184 else if (strcmp(cp, "quoted-printable") == 0)
185 ret = CONV_TOQP;
186 else if (strcmp(cp, "8bit") == 0)
187 ret = CONV_8BIT;
188 else if (strcmp(cp, "base64") == 0)
189 ret = CONV_TOB64;
190 else {
191 fprintf(stderr, tr(177,
192 "Warning: invalid encoding %s, using base64\n"), cp);
193 ret = CONV_TOB64;
195 return (ret);
198 static ssize_t
199 _fwrite_td(struct str const *input, enum tdflags flags, struct str *rest,
200 struct quoteflt *qf)
202 /* TODO note: after send/MIME layer rewrite we will have a string pool
203 * TODO so that memory allocation count drops down massively; for now,
204 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
205 /* TODO well if we get a broken pipe here, and it happens to
206 * TODO happen pretty easy when sleeping in a full pipe buffer,
207 * TODO then the current codebase performs longjump away;
208 * TODO this leaves memory leaks behind ('think up to 3 per,
209 * TODO dep. upon alloca availability). For this to be fixed
210 * TODO we either need to get rid of the longjmp()s (tm) or
211 * TODO the storage must come from the outside or be tracked
212 * TODO in a carrier struct. Best both. But storage reuse
213 * TODO would be a bigbig win besides */
214 /* *input* _may_ point to non-modifyable buffer; but even then it only
215 * needs to be dup'ed away if we have to transform the content */
216 struct str in, out;
217 ssize_t rv;
218 (void)rest;
220 in = *input;
221 out.s = NULL;
222 out.l = 0;
224 #ifdef HAVE_ICONV
225 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
226 char *buf = NULL;
228 if (rest != NULL && rest->l > 0) {
229 in.l = rest->l + input->l;
230 in.s = buf = smalloc(in.l + 1);
231 memcpy(in.s, rest->s, rest->l);
232 memcpy(in.s + rest->l, input->s, input->l);
233 rest->l = 0;
236 if (n_iconv_str(iconvd, &out, &in, &in, TRU1) != 0 &&
237 rest != NULL && in.l > 0) {
238 /* Incomplete multibyte at EOF is special */
239 if (flags & _TD_EOF) {
240 out.s = srealloc(out.s, out.l + 4);
241 /* TODO 0xFFFD out.s[out.l++] = '[';*/
242 out.s[out.l++] = '?'; /* TODO 0xFFFD !!! */
243 /* TODO 0xFFFD out.s[out.l++] = ']';*/
244 } else
245 (void)n_str_add(rest, &in);
247 in = out;
248 out.s = NULL;
249 flags &= ~_TD_BUFCOPY;
251 if (buf != NULL)
252 free(buf);
254 #endif
256 if (flags & TD_ISPR)
257 makeprint(&in, &out);
258 else if (flags & _TD_BUFCOPY)
259 n_str_dup(&out, &in);
260 else
261 out = in;
262 if (flags & TD_DELCTRL)
263 out.l = delctrl(out.s, out.l);
265 rv = quoteflt_push(qf, out.s, out.l);
267 if (out.s != in.s)
268 free(out.s);
269 if (in.s != input->s)
270 free(in.s);
271 return rv;
274 static size_t
275 delctrl(char *cp, size_t sz)
277 size_t x = 0, y = 0;
279 while (x < sz) {
280 if (!cntrlchar(cp[x]&0377))
281 cp[y++] = cp[x];
282 x++;
284 return y;
287 static int
288 has_highbit(const char *s)
290 if (s) {
292 if (*s & 0200)
293 return 1;
294 while (*s++ != '\0');
296 return 0;
299 static int
300 name_highbit(struct name *np)
302 while (np) {
303 if (has_highbit(np->n_name) || has_highbit(np->n_fullname))
304 return 1;
305 np = np->n_flink;
307 return 0;
310 FL char const *
311 charset_get_7bit(void)
313 char const *t;
315 if ((t = ok_vlook(charset_7bit)) == NULL)
316 t = CHARSET_7BIT;
317 return (t);
320 FL char const *
321 charset_get_8bit(void)
323 char const *t;
325 if ((t = ok_vlook(CHARSET_8BIT_OKEY)) == NULL)
326 t = CHARSET_8BIT;
327 return (t);
330 FL char const *
331 charset_get_lc(void)
333 char const *t;
335 if ((t = ok_vlook(ttycharset)) == NULL)
336 t = CHARSET_8BIT;
337 return (t);
340 FL void
341 charset_iter_reset(char const *a_charset_to_try_first)
343 char const *sarr[3];
344 size_t sarrl[3], len;
345 char *cp;
347 sarr[0] = a_charset_to_try_first;
348 #ifdef HAVE_ICONV
349 if ((sarr[1] = ok_vlook(sendcharsets)) == NULL &&
350 ok_blook(sendcharsets_else_ttycharset))
351 sarr[1] = charset_get_lc();
352 #endif
353 sarr[2] = charset_get_8bit();
355 sarrl[2] = len = strlen(sarr[2]);
356 #ifdef HAVE_ICONV
357 if ((cp = UNCONST(sarr[1])) != NULL)
358 len += (sarrl[1] = strlen(cp));
359 else
360 sarrl[1] = 0;
361 if ((cp = UNCONST(sarr[0])) != NULL)
362 len += (sarrl[0] = strlen(cp));
363 else
364 sarrl[0] = 0;
365 #endif
367 _cs_iter_base = cp = salloc(len + 1 + 1 +1);
369 #ifdef HAVE_ICONV
370 if ((len = sarrl[0]) != 0) {
371 memcpy(cp, sarr[0], len);
372 cp[len] = ',';
373 cp += ++len;
375 if ((len = sarrl[1]) != 0) {
376 memcpy(cp, sarr[1], len);
377 cp[len] = ',';
378 cp += ++len;
380 #endif
381 len = sarrl[2];
382 memcpy(cp, sarr[2], len);
383 cp[len] = '\0';
384 _cs_iter = NULL;
387 FL char const *
388 charset_iter_next(void)
390 return (_cs_iter = strcomma(&_cs_iter_base, 1));
393 FL char const *
394 charset_iter_current(void)
396 return _cs_iter;
399 FL void
400 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
402 outer_storage[0] = _cs_iter_base;
403 outer_storage[1] = _cs_iter;
406 FL void
407 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
409 _cs_iter_base = outer_storage[0];
410 _cs_iter = outer_storage[1];
413 FL char const *
414 need_hdrconv(struct header *hp, enum gfield w)
416 char const *ret = NULL;
418 if (w & GIDENT) {
419 if (hp->h_from != NULL) {
420 if (name_highbit(hp->h_from))
421 goto jneeds;
422 } else if (has_highbit(myaddrs(NULL)))
423 goto jneeds;
424 if (hp->h_organization) {
425 if (has_highbit(hp->h_organization))
426 goto jneeds;
427 } else if (has_highbit(ok_vlook(ORGANIZATION)))
428 goto jneeds;
429 if (hp->h_replyto) {
430 if (name_highbit(hp->h_replyto))
431 goto jneeds;
432 } else if (has_highbit(ok_vlook(replyto)))
433 goto jneeds;
434 if (hp->h_sender) {
435 if (name_highbit(hp->h_sender))
436 goto jneeds;
437 } else if (has_highbit(ok_vlook(sender)))
438 goto jneeds;
440 if (w & GTO && name_highbit(hp->h_to))
441 goto jneeds;
442 if (w & GCC && name_highbit(hp->h_cc))
443 goto jneeds;
444 if (w & GBCC && name_highbit(hp->h_bcc))
445 goto jneeds;
446 if (w & GSUBJECT && has_highbit(hp->h_subject))
447 jneeds: ret = _CHARSET();
448 return (ret);
451 static int
452 is_this_enc(const char *line, const char *encoding)
454 int quoted = 0, c;
456 if (*line == '"')
457 quoted = 1, line++;
458 while (*line && *encoding)
459 if (c = *line++, lowerconv(c) != *encoding++)
460 return 0;
461 if (quoted && *line == '"')
462 return 1;
463 if (*line == '\0' || whitechar(*line & 0377))
464 return 1;
465 return 0;
469 * Get the mime encoding from a Content-Transfer-Encoding header field.
471 FL enum mimeenc
472 mime_getenc(char *p)
474 if (is_this_enc(p, "7bit"))
475 return MIME_7B;
476 if (is_this_enc(p, "8bit"))
477 return MIME_8B;
478 if (is_this_enc(p, "base64"))
479 return MIME_B64;
480 if (is_this_enc(p, "binary"))
481 return MIME_BIN;
482 if (is_this_enc(p, "quoted-printable"))
483 return MIME_QP;
484 return MIME_NONE;
488 * Get a mime style parameter from a header line.
490 FL char *
491 mime_getparam(char const *param, char *h)
493 char *p = h, *q, *r;
494 int c;
495 size_t sz;
497 sz = strlen(param);
498 if (!whitechar(*p & 0377)) {
499 c = '\0';
500 while (*p && (*p != ';' || c == '\\')) {
501 c = c == '\\' ? '\0' : *p;
502 p++;
504 if (*p++ == '\0')
505 return NULL;
507 for (;;) {
508 while (whitechar(*p & 0377))
509 p++;
510 if (ascncasecmp(p, param, sz) == 0) {
511 p += sz;
512 while (whitechar(*p & 0377))
513 p++;
514 if (*p++ == '=')
515 break;
517 c = '\0';
518 while (*p && (*p != ';' || c == '\\')) {
519 if (*p == '"' && c != '\\') {
520 p++;
521 while (*p && (*p != '"' || c == '\\')) {
522 c = c == '\\' ? '\0' : *p;
523 p++;
525 p++;
526 } else {
527 c = c == '\\' ? '\0' : *p;
528 p++;
531 if (*p++ == '\0')
532 return NULL;
534 while (whitechar(*p & 0377))
535 p++;
536 q = p;
537 if (*p == '"') {
538 p++;
539 if ((q = strchr(p, '"')) == NULL)
540 return NULL;
541 } else {
542 while (*q && !whitechar(*q & 0377) && *q != ';')
543 q++;
545 sz = q - p;
546 r = salloc(q - p + 1);
547 memcpy(r, p, sz);
548 *(r + sz) = '\0';
549 return r;
552 FL char *
553 mime_get_boundary(char *h, size_t *len)
555 char *q = NULL, *p;
556 size_t sz;
558 if ((p = mime_getparam("boundary", h)) != NULL) {
559 sz = strlen(p);
560 if (len != NULL)
561 *len = sz + 2;
562 q = salloc(sz + 3);
563 q[0] = q[1] = '-';
564 memcpy(q + 2, p, sz);
565 *(q + sz + 2) = '\0';
567 return (q);
570 FL char *
571 mime_create_boundary(void)
573 char *bp;
575 bp = salloc(48);
576 snprintf(bp, 48, "=_%011lu=-%s=_",
577 (ul_it)time_current.tc_time, getrandstring(47 - (11 + 6)));
578 return bp;
581 FL int
582 mime_classify_file(FILE *fp, char const **contenttype, char const **charset,
583 int *do_iconv)
585 /* TODO classify once only PLEASE PLEASE PLEASE */
586 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
587 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
588 * TODO and report that state to the outer world */
589 #define F_ "From "
590 #define F_SIZEOF (sizeof(F_) - 1)
592 char f_buf[F_SIZEOF], *f_p = f_buf;
593 enum { _CLEAN = 0, /* Plain RFC 2822 message */
594 _NCTT = 1<<0, /* *contenttype == NULL */
595 _ISTXT = 1<<1, /* *contenttype =~ text/ */
596 _ISTXTCOK = 1<<2, /* _ISTXT+*mime-allow-text-controls* */
597 _HIGHBIT = 1<<3, /* Not 7bit clean */
598 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
599 _CTRLCHAR = 1<<5, /* Control characters seen */
600 _HASNUL = 1<<6, /* Contains \0 characters */
601 _NOTERMNL = 1<<7, /* Lacks a final newline */
602 _TRAILWS = 1<<8, /* Blanks before NL */
603 _FROM_ = 1<<9 /* ^From_ seen */
604 } ctt = _CLEAN;
605 enum conversion convert;
606 sl_it curlen;
607 int c, lastc;
609 assert(ftell(fp) == 0x0l);
611 *do_iconv = 0;
613 if (*contenttype == NULL)
614 ctt = _NCTT;
615 else if (ascncasecmp(*contenttype, "text/", 5) == 0)
616 ctt = ok_blook(mime_allow_text_controls)
617 ? _ISTXT | _ISTXTCOK : _ISTXT;
618 convert = _conversion_by_encoding();
620 if (fsize(fp) == 0)
621 goto j7bit;
623 /* We have to inspect the file content */
624 for (curlen = 0, c = EOF;; ++curlen) {
625 lastc = c;
626 c = getc(fp);
628 if (c == '\0') {
629 ctt |= _HASNUL;
630 if ((ctt & _ISTXTCOK) == 0)
631 break;
632 continue;
634 if (c == '\n' || c == EOF) {
635 if (curlen >= MIME_LINELEN_LIMIT)
636 ctt |= _LONGLINES;
637 if (c == EOF)
638 break;
639 if (blankchar(lastc))
640 ctt |= _TRAILWS;
641 f_p = f_buf;
642 curlen = -1;
643 continue;
646 * A bit hairy is handling of \r=\x0D=CR.
647 * RFC 2045, 6.7: Control characters other than TAB, or
648 * CR and LF as parts of CRLF pairs, must not appear.
649 * \r alone does not force _CTRLCHAR below since we cannot peek
650 * the next character.
651 * Thus right here, inspect the last seen character for if its
652 * \r and set _CTRLCHAR in a delayed fashion
654 /*else*/ if (lastc == '\r')
655 ctt |= _CTRLCHAR;
657 /* Control character? */
658 if (c < 0x20 || c == 0x7F) {
659 /* RFC 2045, 6.7, as above ... */
660 if (c != '\t' && c != '\r')
661 ctt |= _CTRLCHAR;
663 * If there is a escape sequence in backslash notation
664 * defined for this in ANSI X3.159-1989 (ANSI C89),
665 * don't treat it as a control for real.
666 * I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT.
667 * Don't follow libmagic(1) in respect to \v=\x0B=VT.
668 * \f=\x0C=NP; do ignore \e=\x1B=ESC.
670 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
671 continue;
672 ctt |= _HASNUL; /* Force base64 */
673 if ((ctt & _ISTXTCOK) == 0)
674 break;
675 } else if (c & 0x80) {
676 ctt |= _HIGHBIT;
677 /* TODO count chars with HIGHBIT? libmagic?
678 * TODO try encode part - base64 if bails? */
679 if ((ctt & (_NCTT|_ISTXT)) == 0) { /* TODO _NCTT?? */
680 ctt |= _HASNUL; /* Force base64 */
681 break;
683 } else if ((ctt & _FROM_) == 0 && curlen < (sl_it)F_SIZEOF) {
684 *f_p++ = (char)c;
685 if (curlen == (sl_it)(F_SIZEOF - 1) &&
686 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
687 memcmp(f_buf, F_, F_SIZEOF) == 0)
688 ctt |= _FROM_;
691 if (lastc != '\n')
692 ctt |= _NOTERMNL;
693 rewind(fp);
695 if (ctt & _HASNUL) {
696 convert = CONV_TOB64;
697 /* Don't overwrite a text content-type to allow UTF-16 and
698 * such, but only on request;
699 * Otherwise enforce what file(1)/libmagic(3) would suggest */
700 if (ctt & _ISTXTCOK)
701 goto jcharset;
702 if (ctt & (_NCTT|_ISTXT))
703 *contenttype = "application/octet-stream";
704 if (*charset == NULL)
705 *charset = "binary";
706 goto jleave;
709 if (ctt & (_LONGLINES|_CTRLCHAR|_NOTERMNL|_TRAILWS|_FROM_)) {
710 convert = CONV_TOQP;
711 goto jstepi;
713 if (ctt & _HIGHBIT) {
714 jstepi: if (ctt & (_NCTT|_ISTXT))
715 *do_iconv = (ctt & _HIGHBIT) != 0;
716 } else
717 j7bit: convert = CONV_7BIT;
718 if (ctt & _NCTT)
719 *contenttype = "text/plain";
721 /* Not an attachment with specified charset? */
722 jcharset:
723 if (*charset == NULL)
724 *charset = (ctt & _HIGHBIT) ? _CHARSET() : charset_get_7bit();
725 jleave:
726 return (convert);
727 #undef F_
728 #undef F_SIZEOF
731 FL enum mimecontent
732 mime_classify_content_of_part(struct mimepart const *mip)
734 enum mimecontent mc = MIME_UNKNOWN;
735 char const *ct = mip->m_ct_type_plain;
737 if (asccasecmp(ct, "application/octet-stream") == 0 &&
738 mip->m_filename != NULL &&
739 ok_blook(mime_counter_evidence)) {
740 ct = mime_classify_content_type_by_fileext(mip->m_filename);
741 if (ct == NULL)
742 /* TODO how about let *mime-counter-evidence* have
743 * TODO a value, and if set, saving the attachment in
744 * TODO a temporary file that mime_classify_file() can
745 * TODO examine, and using MIME_TEXT if that gives us
746 * TODO something that seems to be human readable?! */
747 goto jleave;
749 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
750 mc = MIME_TEXT;
751 else if (asccasecmp(ct, "text/plain") == 0)
752 mc = MIME_TEXT_PLAIN;
753 else if (asccasecmp(ct, "text/html") == 0)
754 mc = MIME_TEXT_HTML;
755 else if (ascncasecmp(ct, "text/", 5) == 0)
756 mc = MIME_TEXT;
757 else if (asccasecmp(ct, "message/rfc822") == 0)
758 mc = MIME_822;
759 else if (ascncasecmp(ct, "message/", 8) == 0)
760 mc = MIME_MESSAGE;
761 else if (asccasecmp(ct, "multipart/alternative") == 0)
762 mc = MIME_ALTERNATIVE;
763 else if (asccasecmp(ct, "multipart/digest") == 0)
764 mc = MIME_DIGEST;
765 else if (ascncasecmp(ct, "multipart/", 10) == 0)
766 mc = MIME_MULTI;
767 else if (asccasecmp(ct, "application/x-pkcs7-mime") == 0 ||
768 asccasecmp(ct, "application/pkcs7-mime") == 0)
769 mc = MIME_PKCS7;
770 jleave:
771 return (mc);
774 FL char *
775 mime_classify_content_type_by_fileext(char const *name)
777 char *content = NULL;
778 struct mtnode *mtn;
779 size_t nlen;
781 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
782 goto jleave;
784 if (_mt_list == NULL)
785 _mt_init();
787 nlen = strlen(name);
788 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
789 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
790 *cp = ext;
791 do {
792 while (! whitechar(*cp) && *cp != '\0')
793 ++cp;
794 /* Better to do case-insensitive comparison on
795 * extension, since the RFC doesn't specify case of
796 * attribute values? */
797 if (nlen == (size_t)(cp - ext) &&
798 ascncasecmp(name, ext, nlen) == 0) {
799 content = savestrbuf(mtn->mt_line,
800 mtn->mt_mtlen);
801 goto jleave;
803 while (whitechar(*cp) && *cp != '\0')
804 ++cp;
805 ext = cp;
806 } while (*ext != '\0');
808 jleave:
809 return (content);
812 FL int
813 c_mimetypes(void *v)
815 char **argv = v;
816 struct mtnode *mtn;
818 if (*argv == NULL)
819 goto jlist;
820 if (argv[1] != NULL)
821 goto jerr;
822 if (asccasecmp(*argv, "show") == 0)
823 goto jlist;
824 if (asccasecmp(*argv, "clear") == 0)
825 goto jclear;
826 jerr:
827 fprintf(stderr, "Synopsis: mimetypes: %s\n", tr(418,
828 "Either <show> (default) or <clear> the mime.types cache"));
829 v = NULL;
830 jleave:
831 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
833 jlist: {
834 FILE *fp;
835 char *cp;
836 size_t l;
838 if (_mt_list == NULL)
839 _mt_init();
841 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
842 perror("tmpfile");
843 v = NULL;
844 goto jleave;
846 rm(cp);
847 Ftfree(&cp);
849 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
850 fprintf(fp, "%s\t%s\n", mtn->mt_line,
851 mtn->mt_line + mtn->mt_mtlen + 1);
853 page_or_print(fp, l);
854 Fclose(fp);
856 goto jleave;
858 jclear:
859 while ((mtn = _mt_list) != NULL) {
860 _mt_list = mtn->mt_next;
861 free(mtn);
863 goto jleave;
866 FL void
867 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
869 /* TODO mime_fromhdr(): is called with strings that contain newlines;
870 * TODO this is the usual newline problem all around the codebase;
871 * TODO i.e., if we strip it, then the display misses it ;>
872 * TODO this is why it is so messy and why S-nail v14.2 plus additional
873 * TODO patch for v14.5.2 (and maybe even v14.5.3 subminor) occurred, and
874 * TODO why our display reflects what is contained in the message: the 1:1
875 * TODO relationship of message content and display!
876 * TODO instead a header line should be decoded to what it is (a single
877 * TODO line that is) and it should be objective to the backend wether
878 * TODO it'll be folded to fit onto the display or not, e.g., for search
879 * TODO purposes etc. then the only condition we have to honour in here
880 * TODO is that whitespace in between multiple adjacent MIME encoded words
881 * TODO á la RFC 2047 is discarded; i.e.: this function should deal with
882 * TODO RFC 2047 and be renamed: mime_fromhdr() -> mime_rfc2047_decode() */
883 struct str cin, cout;
884 char *p, *op, *upper, *cs, *cbeg;
885 ui32_t convert, lastenc, lastoutl;
886 #ifdef HAVE_ICONV
887 char const *tcs;
888 iconv_t fhicd = (iconv_t)-1;
889 #endif
891 out->l = 0;
892 if (in->l == 0) {
893 *(out->s = smalloc(1)) = '\0';
894 goto jleave;
896 out->s = NULL;
898 #ifdef HAVE_ICONV
899 tcs = charset_get_lc();
900 #endif
901 p = in->s;
902 upper = p + in->l;
903 lastenc = lastoutl = 0;
905 while (p < upper) {
906 op = p;
907 if (*p == '=' && *(p + 1) == '?') {
908 p += 2;
909 cbeg = p;
910 while (p < upper && *p != '?')
911 p++; /* strip charset */
912 if (p >= upper)
913 goto jnotmime;
914 cs = salloc(++p - cbeg);
915 memcpy(cs, cbeg, p - cbeg - 1);
916 cs[p - cbeg - 1] = '\0';
917 #ifdef HAVE_ICONV
918 if (fhicd != (iconv_t)-1)
919 n_iconv_close(fhicd);
920 if (asccasecmp(cs, tcs) != 0)
921 fhicd = n_iconv_open(tcs, cs);
922 else
923 fhicd = (iconv_t)-1;
924 #endif
925 switch (*p) {
926 case 'B': case 'b':
927 convert = CONV_FROMB64;
928 break;
929 case 'Q': case 'q':
930 convert = CONV_FROMQP;
931 break;
932 default: /* invalid, ignore */
933 goto jnotmime;
935 if (*++p != '?')
936 goto jnotmime;
937 cin.s = ++p;
938 cin.l = 1;
939 for (;;) {
940 if (p + 1 >= upper)
941 goto jnotmime;
942 if (*p++ == '?' && *p == '=')
943 break;
944 cin.l++;
946 ++p;
947 cin.l--;
949 cout.s = NULL;
950 cout.l = 0;
951 if (convert == CONV_FROMB64) {
952 /* XXX Take care for, and strip LF from
953 * XXX [Invalid Base64 encoding ignored] */
954 if (b64_decode(&cout, &cin, NULL) == STOP &&
955 cout.s[cout.l - 1] == '\n')
956 --cout.l;
957 } else
958 (void)qp_decode(&cout, &cin, NULL);
959 out->l = lastenc;
960 #ifdef HAVE_ICONV
961 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
962 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
963 convert = n_iconv_str(fhicd, &cin, &cout,
964 NULL, TRU1);
965 out = n_str_add(out, &cin);
966 if (convert) /* EINVAL at EOS */
967 out = n_str_add_buf(out, "?", 1);
968 free(cin.s);
969 } else {
970 #endif
971 out = n_str_add(out, &cout);
972 #ifdef HAVE_ICONV
974 #endif
975 lastenc = lastoutl = out->l;
976 free(cout.s);
977 } else
978 jnotmime: {
979 bool_t onlyws;
981 p = op;
982 onlyws = (lastenc > 0);
983 for (;;) {
984 if (++op == upper)
985 break;
986 if (op[0] == '=' && (PTRCMP(op + 1, ==, upper) || op[1] == '?'))
987 break;
988 if (onlyws && !blankchar(*op))
989 onlyws = FAL0;
992 out = n_str_add_buf(out, p, PTR2SIZE(op - p));
993 p = op;
994 if (!onlyws || lastoutl != lastenc)
995 lastenc = out->l;
996 lastoutl = out->l;
999 out->s[out->l] = '\0';
1001 if (flags & TD_ISPR) {
1002 makeprint(out, &cout);
1003 free(out->s);
1004 *out = cout;
1006 if (flags & TD_DELCTRL)
1007 out->l = delctrl(out->s, out->l);
1008 #ifdef HAVE_ICONV
1009 if (fhicd != (iconv_t)-1)
1010 n_iconv_close(fhicd);
1011 #endif
1012 jleave:
1013 return;
1017 * Convert header fields to RFC 1522 format and write to the file fo.
1019 static size_t
1020 mime_write_tohdr(struct str *in, FILE *fo) /* TODO rewrite - FAST! */
1022 struct str cin, cout;
1023 char buf[B64_LINESIZE +1]; /* (No CR/LF used) */
1024 char const *charset7, *charset, *upper, *wbeg, *wend, *lastspc,
1025 *lastwordend = NULL;
1026 size_t sz = 0, col = 0, quoteany, wr, charsetlen,
1027 maxcol = 65 /* there is the header field's name, too */;
1028 bool_t highbit, mustquote, broken;
1030 charset7 = charset_get_7bit();
1031 charset = _CHARSET();
1032 wr = strlen(charset7);
1033 charsetlen = strlen(charset);
1034 charsetlen = MAX(charsetlen, wr);
1035 upper = in->s + in->l;
1037 /* xxx note this results in too much hits since =/? force quoting even
1038 * xxx if they don't form =? etc. */
1039 quoteany = mime_cte_mustquote(in->s, in->l, TRU1);
1041 highbit = FAL0;
1042 if (quoteany != 0)
1043 for (wbeg = in->s; wbeg < upper; ++wbeg)
1044 if ((uc_it)*wbeg & 0x80)
1045 highbit = TRU1;
1047 if (quoteany << 1 > in->l) {
1049 * Print the entire field in base64.
1051 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1052 wend = upper;
1053 cin.s = UNCONST(wbeg);
1054 for (;;) {
1055 cin.l = wend - wbeg;
1056 if (cin.l * 4/3 + 7 + charsetlen
1057 < maxcol - col) {
1058 cout.s = buf;
1059 cout.l = sizeof buf;
1060 wr = fprintf(fo, "=?%s?B?%s?=",
1061 highbit ? charset : charset7,
1062 b64_encode(&cout, &cin, B64_BUF
1063 )->s);
1064 sz += wr, col += wr;
1065 if (wend < upper) {
1066 fwrite("\n ", sizeof (char),
1067 2, fo);
1068 sz += 2;
1069 col = 0;
1070 maxcol = 76;
1072 break;
1073 } else {
1074 if (col) {
1075 fprintf(fo, "\n ");
1076 sz += 2;
1077 col = 0;
1078 maxcol = 76;
1079 } else
1080 wend -= 4;
1084 } else {
1086 * Print the field word-wise in quoted-printable.
1088 broken = FAL0;
1089 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1090 lastspc = NULL;
1091 while (wbeg < upper && whitechar(*wbeg)) {
1092 lastspc = lastspc ? lastspc : wbeg;
1093 wbeg++;
1094 col++;
1095 broken = FAL0;
1097 if (wbeg == upper) {
1098 if (lastspc)
1099 while (lastspc < wbeg) {
1100 putc(*lastspc&0377, fo);
1101 lastspc++,
1102 sz++;
1104 break;
1107 if (lastspc != NULL)
1108 broken = FAL0;
1109 highbit = FAL0;
1110 for (wend = wbeg; wend < upper && ! whitechar(*wend);
1111 ++wend)
1112 if ((uc_it)*wend & 0x80)
1113 highbit = TRU1;
1114 mustquote = (mime_cte_mustquote(wbeg,
1115 (size_t)(wend - wbeg), TRU1) != 0);
1117 if (mustquote || broken ||
1118 ((wend - wbeg) >= 76-5 && quoteany)) {
1119 for (cout.s = NULL;;) {
1120 cin.s = UNCONST(lastwordend ?
1121 lastwordend : wbeg);
1122 cin.l = wend - cin.s;
1123 (void)qp_encode(&cout, &cin, QP_ISHEAD);
1124 wr = cout.l + charsetlen + 7;
1125 jqp_retest:
1126 if (col <= maxcol &&
1127 wr <= maxcol - col) {
1128 if (lastspc) {
1129 /* TODO because we inc-
1130 * TODO luded the WS in
1131 * TODO the encoded str,
1132 * TODO put SP only??
1133 * TODO RFC: "any
1134 * 'linear-white-space'
1135 * that separates
1136 * a pair of adjacent
1137 * 'encoded-word's is
1138 * ignored" */
1139 putc(' ', fo);
1140 ++sz;
1141 ++col;
1143 fprintf(fo, "=?%s?Q?%.*s?=",
1144 highbit ? charset
1145 : charset7,
1146 (int)cout.l, cout.s);
1147 sz += wr, col += wr;
1148 break;
1149 } else if (col > 1) {
1150 /* TODO assuming SP separator,
1151 * TODO ignore *lastspc* !?? */
1152 broken = TRU1;
1153 if (lastspc != NULL) {
1154 putc('\n', fo);
1155 ++sz;
1156 col = 0;
1157 } else {
1158 fputs("\n ",
1159 fo);
1160 sz += 2;
1161 col = 1;
1163 maxcol = 76;
1164 goto jqp_retest;
1165 } else {
1166 for (;;) { /* XXX */
1167 wend -= 4;
1168 assert(wend > wbeg);
1169 if (wr - 4 < maxcol)
1170 break;
1171 wr -= 4;
1175 if (cout.s != NULL)
1176 free(cout.s);
1177 lastwordend = wend;
1178 } else {
1179 if (col &&
1180 (size_t)(wend - wbeg) > maxcol - col) {
1181 putc('\n', fo);
1182 sz++;
1183 col = 0;
1184 maxcol = 76;
1185 if (lastspc == NULL) {
1186 putc(' ', fo);
1187 sz++;
1188 maxcol--;
1189 } else
1190 maxcol -= wbeg - lastspc;
1192 if (lastspc)
1193 while (lastspc < wbeg) {
1194 putc(*lastspc&0377, fo);
1195 lastspc++, sz++;
1197 wr = fwrite(wbeg, sizeof *wbeg,
1198 wend - wbeg, fo);
1199 sz += wr, col += wr;
1200 lastwordend = NULL;
1204 return sz;
1208 * Write len characters of the passed string to the passed file,
1209 * doing charset and header conversion.
1211 static size_t
1212 convhdra(char const *str, size_t len, FILE *fp)
1214 #ifdef HAVE_ICONV
1215 struct str ciconv;
1216 #endif
1217 struct str cin;
1218 size_t ret = 0;
1220 cin.s = UNCONST(str);
1221 cin.l = len;
1222 #ifdef HAVE_ICONV
1223 ciconv.s = NULL;
1224 if (iconvd != (iconv_t)-1) {
1225 ciconv.l = 0;
1226 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
1227 goto jleave;
1228 cin = ciconv;
1230 #endif
1231 ret = mime_write_tohdr(&cin, fp);
1232 #ifdef HAVE_ICONV
1233 jleave:
1234 if (ciconv.s != NULL)
1235 free(ciconv.s);
1236 #endif
1237 return ret;
1241 * Write an address to a header field.
1243 static size_t
1244 mime_write_tohdr_a(struct str *in, FILE *f)
1246 char const *cp, *lastcp;
1247 size_t sz = 0;
1249 in->s[in->l] = '\0';
1250 lastcp = in->s;
1251 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
1252 sz += convhdra(lastcp, cp - lastcp, f);
1253 lastcp = cp;
1254 } else
1255 cp = in->s;
1256 for ( ; *cp; cp++) {
1257 switch (*cp) {
1258 case '(':
1259 sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
1260 lastcp = ++cp;
1261 cp = skip_comment(cp);
1262 if (--cp > lastcp)
1263 sz += convhdra(lastcp, cp - lastcp, f);
1264 lastcp = cp;
1265 break;
1266 case '"':
1267 while (*cp) {
1268 if (*++cp == '"')
1269 break;
1270 if (*cp == '\\' && cp[1])
1271 cp++;
1273 break;
1276 if (cp > lastcp)
1277 sz += fwrite(lastcp, 1, cp - lastcp, f);
1278 return sz;
1281 static void
1282 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
1284 *buf = srealloc(*buf, *sz += len);
1285 memcpy(&(*buf)[*pos], str, len);
1286 *pos += len;
1289 static void
1290 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
1292 struct str in, out;
1294 in.s = UNCONST(str);
1295 in.l = len;
1296 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1297 addstr(buf, sz, pos, out.s, out.l);
1298 free(out.s);
1302 * Interpret MIME strings in parts of an address field.
1304 FL char *
1305 mime_fromaddr(char const *name)
1307 char const *cp, *lastcp;
1308 char *res = NULL;
1309 size_t ressz = 1, rescur = 0;
1311 if (name == NULL)
1312 return (res);
1313 if (*name == '\0')
1314 return savestr(name);
1315 if ((cp = routeaddr(name)) != NULL && cp > name) {
1316 addconv(&res, &ressz, &rescur, name, cp - name);
1317 lastcp = cp;
1318 } else
1319 cp = lastcp = name;
1320 for ( ; *cp; cp++) {
1321 switch (*cp) {
1322 case '(':
1323 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
1324 lastcp = ++cp;
1325 cp = skip_comment(cp);
1326 if (--cp > lastcp)
1327 addconv(&res, &ressz, &rescur, lastcp,
1328 cp - lastcp);
1329 lastcp = cp;
1330 break;
1331 case '"':
1332 while (*cp) {
1333 if (*++cp == '"')
1334 break;
1335 if (*cp == '\\' && cp[1])
1336 cp++;
1338 break;
1341 if (cp > lastcp)
1342 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
1343 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1344 if (rescur == 0)
1345 res = UNCONST("");
1346 else
1347 res[rescur] = '\0';
1348 { char *x = res;
1349 res = savestr(res);
1350 free(x);
1352 return (res);
1355 FL ssize_t
1356 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1357 enum tdflags dflags, struct str *rest)
1359 ssize_t rv;
1360 struct quoteflt *qf;
1362 quoteflt_reset(qf = quoteflt_dummy(), f);
1363 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1364 assert(quoteflt_flush(qf) == 0);
1365 return rv;
1368 FL ssize_t
1369 mime_write(char const *ptr, size_t size, FILE *f,
1370 enum conversion convert, enum tdflags dflags,
1371 struct quoteflt *qf, struct str *rest)
1373 /* TODO note: after send/MIME layer rewrite we will have a string pool
1374 * TODO so that memory allocation count drops down massively; for now,
1375 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1376 struct str in, out;
1377 ssize_t sz;
1378 int state;
1380 in.s = UNCONST(ptr);
1381 in.l = size;
1382 out.s = NULL;
1383 out.l = 0;
1385 dflags |= _TD_BUFCOPY;
1386 if ((sz = size) == 0) {
1387 if (rest != NULL && rest->l != 0)
1388 goto jconvert;
1389 goto jleave;
1392 #ifdef HAVE_ICONV
1393 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1394 (convert == CONV_TOQP || convert == CONV_8BIT ||
1395 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1396 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1397 /* XXX report conversion error? */;
1398 sz = -1;
1399 goto jleave;
1401 in = out;
1402 out.s = NULL;
1403 dflags &= ~_TD_BUFCOPY;
1405 #endif
1407 jconvert:
1408 switch (convert) {
1409 case CONV_FROMQP:
1410 state = qp_decode(&out, &in, rest);
1411 goto jqpb64_dec;
1412 case CONV_TOQP:
1413 (void)qp_encode(&out, &in, QP_NONE);
1414 goto jqpb64_enc;
1415 case CONV_8BIT:
1416 sz = quoteflt_push(qf, in.s, in.l);
1417 break;
1418 case CONV_FROMB64:
1419 rest = NULL;
1420 /* FALLTHRU */
1421 case CONV_FROMB64_T:
1422 state = b64_decode(&out, &in, rest);
1423 jqpb64_dec:
1424 if ((sz = out.l) != 0) {
1425 size_t opl = qf->qf_pfix_len;
1426 if (state != OKAY)
1427 qf->qf_pfix_len = 0;
1428 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest,qf);
1429 qf->qf_pfix_len = opl;
1431 if (state != OKAY)
1432 sz = -1;
1433 break;
1434 case CONV_TOB64:
1435 (void)b64_encode(&out, &in, B64_LF|B64_MULTILINE);
1436 jqpb64_enc:
1437 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1438 if (sz != (ssize_t)out.l)
1439 sz = -1;
1440 break;
1441 case CONV_FROMHDR:
1442 mime_fromhdr(&in, &out,
1443 TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1444 sz = quoteflt_push(qf, out.s, out.l);
1445 break;
1446 case CONV_TOHDR:
1447 sz = mime_write_tohdr(&in, f);
1448 break;
1449 case CONV_TOHDR_A:
1450 sz = mime_write_tohdr_a(&in, f);
1451 break;
1452 default:
1453 sz = _fwrite_td(&in, dflags, NULL, qf);
1454 break;
1456 jleave:
1457 if (out.s != NULL)
1458 free(out.s);
1459 if (in.s != ptr)
1460 free(in.s);
1461 return sz;