Many: unite many HAVE_ to HAVE_C90AMEND1, plus
[s-mailx.git] / mime.c
blobe57f56cd39526b5db8babfe88d9b1feaec82c265
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 - 2013 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 = value("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 = value("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 = value("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 = value(CHARSET_8BIT_VAR)) == NULL)
326 t = CHARSET_8BIT;
327 return (t);
330 FL char const *
331 charset_get_lc(void)
333 char const *t;
335 if ((t = value("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] = value("sendcharsets")) == NULL &&
350 value("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);
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(value("ORGANIZATION")))
428 goto jneeds;
429 if (hp->h_replyto) {
430 if (name_highbit(hp->h_replyto))
431 goto jneeds;
432 } else if (has_highbit(value("replyto")))
433 goto jneeds;
434 if (hp->h_sender) {
435 if (name_highbit(hp->h_sender))
436 goto jneeds;
437 } else if (has_highbit(value("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 = value("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 (size_t)(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 value("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 cmimetypes(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);
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;
867 * Convert header fields from RFC 1522 format
868 * TODO mime_fromhdr(): NO error handling, fat; REWRITE **ASAP**
870 FL void
871 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
873 /* TODO mime_fromhdr(): is called with strings that contain newlines;
874 * TODO this is the usual newline problem all around the codebase;
875 * TODO i.e., if we strip it, then the display misses it ;} */
876 struct str cin, cout;
877 char *p, *op, *upper, *cs, *cbeg;
878 int convert;
879 size_t lastoutl = (size_t)-1;
880 #ifdef HAVE_ICONV
881 char const *tcs;
882 iconv_t fhicd = (iconv_t)-1;
883 #endif
885 out->l = 0;
886 if (in->l == 0) {
887 *(out->s = smalloc(1)) = '\0';
888 goto jleave;
890 out->s = NULL;
892 #ifdef HAVE_ICONV
893 tcs = charset_get_lc();
894 #endif
895 p = in->s;
896 upper = p + in->l;
898 while (p < upper) {
899 op = p;
900 if (*p == '=' && *(p + 1) == '?') {
901 p += 2;
902 cbeg = p;
903 while (p < upper && *p != '?')
904 p++; /* strip charset */
905 if (p >= upper)
906 goto jnotmime;
907 cs = salloc(++p - cbeg);
908 memcpy(cs, cbeg, p - cbeg - 1);
909 cs[p - cbeg - 1] = '\0';
910 #ifdef HAVE_ICONV
911 if (fhicd != (iconv_t)-1)
912 n_iconv_close(fhicd);
913 if (asccasecmp(cs, tcs) != 0)
914 fhicd = n_iconv_open(tcs, cs);
915 else
916 fhicd = (iconv_t)-1;
917 #endif
918 switch (*p) {
919 case 'B': case 'b':
920 convert = CONV_FROMB64;
921 break;
922 case 'Q': case 'q':
923 convert = CONV_FROMQP;
924 break;
925 default: /* invalid, ignore */
926 goto jnotmime;
928 if (*++p != '?')
929 goto jnotmime;
930 cin.s = ++p;
931 cin.l = 1;
932 for (;;) {
933 if (p + 1 >= upper)
934 goto jnotmime;
935 if (*p++ == '?' && *p == '=')
936 break;
937 cin.l++;
939 ++p;
940 cin.l--;
942 cout.s = NULL;
943 cout.l = 0;
944 if (convert == CONV_FROMB64) {
945 /* XXX Take care for, and strip LF from
946 * XXX [Invalid Base64 encoding ignored] */
947 if (b64_decode(&cout, &cin, NULL) == STOP &&
948 cout.s[cout.l - 1] == '\n')
949 --cout.l;
950 } else
951 (void)qp_decode(&cout, &cin, NULL);
952 if (lastoutl != (size_t)-1)
953 out->l = lastoutl;
954 #ifdef HAVE_ICONV
955 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
956 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
957 convert = n_iconv_str(fhicd, &cin, &cout,
958 NULL, TRU1);
959 out = n_str_add(out, &cin);
960 if (convert) /* EINVAL at EOS */
961 out = n_str_add_buf(out, "?", 1);
962 free(cin.s);
963 } else {
964 #endif
965 out = n_str_add(out, &cout);
966 #ifdef HAVE_ICONV
968 #endif
969 lastoutl = out->l;
970 free(cout.s);
971 } else {
972 jnotmime:
973 p = op;
974 convert = 1;
975 while ((op = p + convert) < upper &&
976 (op[0] != '=' || op[1] != '?'))
977 ++convert;
978 out = n_str_add_buf(out, p, convert);
979 p += convert;
980 if (! blankchar(p[-1]))
981 lastoutl = (size_t)-1;
984 out->s[out->l] = '\0';
986 if (flags & TD_ISPR) {
987 makeprint(out, &cout);
988 free(out->s);
989 *out = cout;
991 if (flags & TD_DELCTRL)
992 out->l = delctrl(out->s, out->l);
993 #ifdef HAVE_ICONV
994 if (fhicd != (iconv_t)-1)
995 n_iconv_close(fhicd);
996 #endif
997 jleave:
998 return;
1002 * Convert header fields to RFC 1522 format and write to the file fo.
1004 static size_t
1005 mime_write_tohdr(struct str *in, FILE *fo) /* TODO rewrite - FAST! */
1007 struct str cin, cout;
1008 char buf[B64_LINESIZE];
1009 char const *charset7, *charset, *upper, *wbeg, *wend, *lastspc,
1010 *lastwordend = NULL;
1011 size_t sz = 0, col = 0, quoteany, wr, charsetlen,
1012 maxcol = 65 /* there is the header field's name, too */;
1013 bool_t highbit, mustquote, broken;
1015 charset7 = charset_get_7bit();
1016 charset = _CHARSET();
1017 wr = strlen(charset7);
1018 charsetlen = strlen(charset);
1019 charsetlen = MAX(charsetlen, wr);
1020 upper = in->s + in->l;
1022 /* xxx note this results in too much hits since =/? force quoting even
1023 * xxx if they don't form =? etc. */
1024 quoteany = mime_cte_mustquote(in->s, in->l, TRU1);
1026 highbit = FAL0;
1027 if (quoteany != 0)
1028 for (wbeg = in->s; wbeg < upper; ++wbeg)
1029 if ((uc_it)*wbeg & 0x80)
1030 highbit = TRU1;
1032 if (quoteany << 1 > in->l) {
1034 * Print the entire field in base64.
1036 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1037 wend = upper;
1038 cin.s = UNCONST(wbeg);
1039 for (;;) {
1040 cin.l = wend - wbeg;
1041 if (cin.l * 4/3 + 7 + charsetlen
1042 < maxcol - col) {
1043 cout.s = buf;
1044 cout.l = sizeof buf;
1045 wr = fprintf(fo, "=?%s?B?%s?=",
1046 highbit ? charset : charset7,
1047 b64_encode(&cout, &cin, B64_BUF
1048 )->s);
1049 sz += wr, col += wr;
1050 if (wend < upper) {
1051 fwrite("\n ", sizeof (char),
1052 2, fo);
1053 sz += 2;
1054 col = 0;
1055 maxcol = 76;
1057 break;
1058 } else {
1059 if (col) {
1060 fprintf(fo, "\n ");
1061 sz += 2;
1062 col = 0;
1063 maxcol = 76;
1064 } else
1065 wend -= 4;
1069 } else {
1071 * Print the field word-wise in quoted-printable.
1073 broken = FAL0;
1074 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1075 lastspc = NULL;
1076 while (wbeg < upper && whitechar(*wbeg)) {
1077 lastspc = lastspc ? lastspc : wbeg;
1078 wbeg++;
1079 col++;
1080 broken = FAL0;
1082 if (wbeg == upper) {
1083 if (lastspc)
1084 while (lastspc < wbeg) {
1085 putc(*lastspc&0377, fo);
1086 lastspc++,
1087 sz++;
1089 break;
1092 if (lastspc != NULL)
1093 broken = FAL0;
1094 highbit = FAL0;
1095 for (wend = wbeg; wend < upper && ! whitechar(*wend);
1096 ++wend)
1097 if ((uc_it)*wend & 0x80)
1098 highbit = TRU1;
1099 mustquote = (mime_cte_mustquote(wbeg,
1100 (size_t)(wend - wbeg), TRU1) != 0);
1102 if (mustquote || broken ||
1103 ((wend - wbeg) >= 76-5 && quoteany)) {
1104 for (cout.s = NULL;;) {
1105 cin.s = UNCONST(lastwordend ?
1106 lastwordend : wbeg);
1107 cin.l = wend - cin.s;
1108 (void)qp_encode(&cout, &cin, QP_ISHEAD);
1109 wr = cout.l + charsetlen + 7;
1110 jqp_retest:
1111 if (col <= maxcol &&
1112 wr <= maxcol - col) {
1113 if (lastspc) {
1114 /* TODO because we inc-
1115 * TODO luded the WS in
1116 * TODO the encoded str,
1117 * TODO put SP only??
1118 * TODO RFC: "any
1119 * 'linear-white-space'
1120 * that separates
1121 * a pair of adjacent
1122 * 'encoded-word's is
1123 * ignored" */
1124 putc(' ', fo);
1125 ++sz;
1126 ++col;
1128 fprintf(fo, "=?%s?Q?%.*s?=",
1129 highbit ? charset
1130 : charset7,
1131 (int)cout.l, cout.s);
1132 sz += wr, col += wr;
1133 break;
1134 } else if (col > 1) {
1135 /* TODO assuming SP separator,
1136 * TODO ignore *lastspc* !?? */
1137 broken = TRU1;
1138 if (lastspc != NULL) {
1139 putc('\n', fo);
1140 ++sz;
1141 col = 0;
1142 } else {
1143 fputs("\n ",
1144 fo);
1145 sz += 2;
1146 col = 1;
1148 maxcol = 76;
1149 goto jqp_retest;
1150 } else {
1151 for (;;) { /* XXX */
1152 wend -= 4;
1153 assert(wend > wbeg);
1154 if (wr - 4 < maxcol)
1155 break;
1156 wr -= 4;
1160 if (cout.s != NULL)
1161 free(cout.s);
1162 lastwordend = wend;
1163 } else {
1164 if (col &&
1165 (size_t)(wend - wbeg) > maxcol - col) {
1166 putc('\n', fo);
1167 sz++;
1168 col = 0;
1169 maxcol = 76;
1170 if (lastspc == NULL) {
1171 putc(' ', fo);
1172 sz++;
1173 maxcol--;
1174 } else
1175 maxcol -= wbeg - lastspc;
1177 if (lastspc)
1178 while (lastspc < wbeg) {
1179 putc(*lastspc&0377, fo);
1180 lastspc++, sz++;
1182 wr = fwrite(wbeg, sizeof *wbeg,
1183 wend - wbeg, fo);
1184 sz += wr, col += wr;
1185 lastwordend = NULL;
1189 return sz;
1193 * Write len characters of the passed string to the passed file,
1194 * doing charset and header conversion.
1196 static size_t
1197 convhdra(char const *str, size_t len, FILE *fp)
1199 #ifdef HAVE_ICONV
1200 struct str ciconv;
1201 #endif
1202 struct str cin;
1203 size_t ret = 0;
1205 cin.s = UNCONST(str);
1206 cin.l = len;
1207 #ifdef HAVE_ICONV
1208 ciconv.s = NULL;
1209 if (iconvd != (iconv_t)-1) {
1210 ciconv.l = 0;
1211 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
1212 goto jleave;
1213 cin = ciconv;
1215 #endif
1216 ret = mime_write_tohdr(&cin, fp);
1217 #ifdef HAVE_ICONV
1218 jleave:
1219 if (ciconv.s != NULL)
1220 free(ciconv.s);
1221 #endif
1222 return ret;
1226 * Write an address to a header field.
1228 static size_t
1229 mime_write_tohdr_a(struct str *in, FILE *f)
1231 char const *cp, *lastcp;
1232 size_t sz = 0;
1234 in->s[in->l] = '\0';
1235 lastcp = in->s;
1236 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
1237 sz += convhdra(lastcp, cp - lastcp, f);
1238 lastcp = cp;
1239 } else
1240 cp = in->s;
1241 for ( ; *cp; cp++) {
1242 switch (*cp) {
1243 case '(':
1244 sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
1245 lastcp = ++cp;
1246 cp = skip_comment(cp);
1247 if (--cp > lastcp)
1248 sz += convhdra(lastcp, cp - lastcp, f);
1249 lastcp = cp;
1250 break;
1251 case '"':
1252 while (*cp) {
1253 if (*++cp == '"')
1254 break;
1255 if (*cp == '\\' && cp[1])
1256 cp++;
1258 break;
1261 if (cp > lastcp)
1262 sz += fwrite(lastcp, 1, cp - lastcp, f);
1263 return sz;
1266 static void
1267 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
1269 *buf = srealloc(*buf, *sz += len);
1270 memcpy(&(*buf)[*pos], str, len);
1271 *pos += len;
1274 static void
1275 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
1277 struct str in, out;
1279 in.s = UNCONST(str);
1280 in.l = len;
1281 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1282 addstr(buf, sz, pos, out.s, out.l);
1283 free(out.s);
1287 * Interpret MIME strings in parts of an address field.
1289 FL char *
1290 mime_fromaddr(char const *name)
1292 char const *cp, *lastcp;
1293 char *res = NULL;
1294 size_t ressz = 1, rescur = 0;
1296 if (name == NULL)
1297 return (res);
1298 if (*name == '\0')
1299 return savestr(name);
1300 if ((cp = routeaddr(name)) != NULL && cp > name) {
1301 addconv(&res, &ressz, &rescur, name, cp - name);
1302 lastcp = cp;
1303 } else
1304 cp = lastcp = name;
1305 for ( ; *cp; cp++) {
1306 switch (*cp) {
1307 case '(':
1308 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
1309 lastcp = ++cp;
1310 cp = skip_comment(cp);
1311 if (--cp > lastcp)
1312 addconv(&res, &ressz, &rescur, lastcp,
1313 cp - lastcp);
1314 lastcp = cp;
1315 break;
1316 case '"':
1317 while (*cp) {
1318 if (*++cp == '"')
1319 break;
1320 if (*cp == '\\' && cp[1])
1321 cp++;
1323 break;
1326 if (cp > lastcp)
1327 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
1328 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1329 if (rescur == 0)
1330 res = UNCONST("");
1331 else
1332 res[rescur] = '\0';
1333 { char *x = res;
1334 res = savestr(res);
1335 free(x);
1337 return (res);
1340 FL ssize_t
1341 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1342 enum tdflags dflags, struct str *rest)
1344 ssize_t rv;
1345 struct quoteflt *qf;
1347 quoteflt_reset(qf = quoteflt_dummy(), f);
1348 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1349 assert(quoteflt_flush(qf) == 0);
1350 return rv;
1353 FL ssize_t
1354 mime_write(char const *ptr, size_t size, FILE *f,
1355 enum conversion convert, enum tdflags dflags,
1356 struct quoteflt *qf, struct str *rest)
1358 /* TODO note: after send/MIME layer rewrite we will have a string pool
1359 * TODO so that memory allocation count drops down massively; for now,
1360 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1361 struct str in, out;
1362 ssize_t sz;
1363 int state;
1365 in.s = UNCONST(ptr);
1366 in.l = size;
1367 out.s = NULL;
1368 out.l = 0;
1370 dflags |= _TD_BUFCOPY;
1371 if ((sz = size) == 0) {
1372 if (rest != NULL && rest->l != 0)
1373 goto jconvert;
1374 goto jleave;
1377 #ifdef HAVE_ICONV
1378 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1379 (convert == CONV_TOQP || convert == CONV_8BIT ||
1380 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1381 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1382 /* XXX report conversion error? */;
1383 sz = -1;
1384 goto jleave;
1386 in = out;
1387 out.s = NULL;
1388 dflags &= ~_TD_BUFCOPY;
1390 #endif
1392 jconvert:
1393 switch (convert) {
1394 case CONV_FROMQP:
1395 state = qp_decode(&out, &in, rest);
1396 goto jqpb64_dec;
1397 case CONV_TOQP:
1398 (void)qp_encode(&out, &in, QP_NONE);
1399 goto jqpb64_enc;
1400 case CONV_8BIT:
1401 sz = quoteflt_push(qf, in.s, in.l);
1402 break;
1403 case CONV_FROMB64:
1404 rest = NULL;
1405 /* FALLTHRU */
1406 case CONV_FROMB64_T:
1407 state = b64_decode(&out, &in, rest);
1408 jqpb64_dec:
1409 if ((sz = out.l) != 0) {
1410 size_t opl = qf->qf_pfix_len;
1411 if (state != OKAY)
1412 qf->qf_pfix_len = 0;
1413 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest,qf);
1414 qf->qf_pfix_len = opl;
1416 if (state != OKAY)
1417 sz = -1;
1418 break;
1419 case CONV_TOB64:
1420 (void)b64_encode(&out, &in, B64_LF|B64_MULTILINE);
1421 jqpb64_enc:
1422 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1423 if (sz != (ssize_t)out.l)
1424 sz = -1;
1425 break;
1426 case CONV_FROMHDR:
1427 mime_fromhdr(&in, &out,
1428 TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1429 sz = quoteflt_push(qf, out.s, out.l);
1430 break;
1431 case CONV_TOHDR:
1432 sz = mime_write_tohdr(&in, f);
1433 break;
1434 case CONV_TOHDR_A:
1435 sz = mime_write_tohdr_a(&in, f);
1436 break;
1437 default:
1438 sz = _fwrite_td(&in, dflags, NULL, qf);
1439 break;
1441 jleave:
1442 if (out.s != NULL)
1443 free(out.s);
1444 if (in.s != ptr)
1445 free(in.s);
1446 return sz;