Enable WANT_QUOTE_FOLD, *_ASSERT -> *_DEBUG, more cleanup
[s-mailx.git] / mime.c
blob59d5d986b775812f6757632f6059777f272b9f19
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 #include "nail.h"
42 #define _CHARSET() ((_cs_iter != NULL) ? _cs_iter : charset_get_8bit())
44 struct mtnode {
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"
56 NULL
59 struct mtnode *_mt_list;
60 char *_cs_iter_base, *_cs_iter;
62 /* Initialize MIME type list */
63 static void _mt_init(void);
64 static void __mt_add_line(char const *line, struct mtnode **tail);
66 /* Get the conversion that matches *encoding* */
67 static enum conversion _conversion_by_encoding(void);
69 /* fwrite(3) while checking for displayability */
70 static ssize_t _fwrite_td(struct str const *input, enum tdflags flags,
71 struct str *rest, struct quoteflt *qf);
73 static size_t delctrl(char *cp, size_t sz);
74 static int has_highbit(register const char *s);
75 static int is_this_enc(const char *line, const char *encoding);
76 static size_t mime_write_tohdr(struct str *in, FILE *fo);
77 static size_t convhdra(char const *str, size_t len, FILE *fp);
78 static size_t mime_write_tohdr_a(struct str *in, FILE *f);
79 static void addstr(char **buf, size_t *sz, size_t *pos,
80 char const *str, size_t len);
81 static void addconv(char **buf, size_t *sz, size_t *pos,
82 char const *str, size_t len);
84 static void
85 _mt_init(void)
87 struct mtnode *tail = NULL;
88 char *line = NULL;
89 size_t linesize = 0;
90 ui_it idx, idx_ok;
91 char const *ccp, *const*srcs;
92 FILE *fp;
94 if ((ccp = value("mimetypes-load-control")) == NULL)
95 idx_ok = (ui_it)-1;
96 else for (idx_ok = 0; *ccp != '\0'; ++ccp)
97 switch (*ccp) {
98 case 'S':
99 case 's':
100 idx_ok |= 1 << 1;
101 break;
102 case 'U':
103 case 'u':
104 idx_ok |= 1 << 0;
105 break;
106 default:
107 /* XXX bad *mimetypes-load-control*; log error? */
108 break;
111 for (idx = 1, srcs = _mt_sources; *srcs != NULL; idx <<= 1, ++srcs) {
112 if ((idx & idx_ok) == 0 || (ccp = file_expand(*srcs)) == NULL)
113 continue;
114 if ((fp = Fopen(ccp, "r")) == NULL) {
115 /*fprintf(stderr, tr(176, "Cannot open %s\n"), fn);*/
116 continue;
118 while (fgetline(&line, &linesize, NULL, NULL, fp, 0))
119 __mt_add_line(line, &tail);
120 Fclose(fp);
122 if (line != NULL)
123 free(line);
125 for (srcs = _mt_bltin; *srcs != NULL; ++srcs)
126 __mt_add_line(*srcs, &tail);
129 static void
130 __mt_add_line(char const *line, struct mtnode **tail) /* XXX diag? dups!*/
132 char const *typ;
133 size_t tlen, elen;
134 struct mtnode *mtn;
136 if (! alphachar(*line))
137 goto jleave;
139 typ = line;
140 while (blankchar(*line) == 0 && *line != '\0')
141 ++line;
142 if (*line == '\0')
143 goto jleave;
144 tlen = (size_t)(line - typ);
146 while (blankchar(*line) != 0 && *line != '\0')
147 ++line;
148 if (*line == '\0')
149 goto jleave;
151 elen = strlen(line);
152 if (line[elen - 1] == '\n' && line[--elen] == '\0')
153 goto jleave;
155 mtn = smalloc(sizeof(struct mtnode) -
156 VFIELD_SIZEOF(struct mtnode, mt_line) +
157 tlen + 1 + elen + 1);
158 if (*tail != NULL)
159 (*tail)->mt_next = mtn;
160 else
161 _mt_list = mtn;
162 *tail = mtn;
163 mtn->mt_next = NULL;
164 mtn->mt_mtlen = tlen;
165 memcpy(mtn->mt_line, typ, tlen);
166 mtn->mt_line[tlen] = '\0';
167 ++tlen;
168 memcpy(mtn->mt_line + tlen, line, elen);
169 tlen += elen;
170 mtn->mt_line[tlen] = '\0';
171 jleave: ;
174 static enum conversion
175 _conversion_by_encoding(void)
177 char const *cp;
178 enum conversion ret;
180 if ((cp = value("encoding")) == NULL)
181 ret = MIME_DEFAULT_ENCODING;
182 else if (strcmp(cp, "quoted-printable") == 0)
183 ret = CONV_TOQP;
184 else if (strcmp(cp, "8bit") == 0)
185 ret = CONV_8BIT;
186 else if (strcmp(cp, "base64") == 0)
187 ret = CONV_TOB64;
188 else {
189 fprintf(stderr, tr(177,
190 "Warning: invalid encoding %s, using base64\n"), cp);
191 ret = CONV_TOB64;
193 return (ret);
196 static ssize_t
197 _fwrite_td(struct str const *input, enum tdflags flags, struct str *rest,
198 struct quoteflt *qf)
200 /* TODO note: after send/MIME layer rewrite we will have a string pool
201 * TODO so that memory allocation count drops down massively; for now,
202 * TODO v14.* that is, we pay a lot & heavily depend on the allocator */
203 /* TODO well if we get a broken pipe here, and it happens to
204 * TODO happen pretty easy when sleeping in a full pipe buffer,
205 * TODO then the current codebase performs longjump away;
206 * TODO this leaves memory leaks behind ('think up to 3 per,
207 * TODO dep. upon alloca availability). For this to be fixed
208 * TODO we either need to get rid of the longjmp()s (tm) or
209 * TODO the storage must come from the outside or be tracked
210 * TODO in a carrier struct. Best both. But storage reuse
211 * TODO would be a bigbig win besides */
212 /* *input* _may_ point to non-modifyable buffer; but even then it only
213 * needs to be dup'ed away if we have to transform the content */
214 struct str in, out;
215 ssize_t rv;
216 (void)rest;
218 in = *input;
219 out.s = NULL;
220 out.l = 0;
222 #ifdef HAVE_ICONV
223 if ((flags & TD_ICONV) && iconvd != (iconv_t)-1) {
224 char *buf = NULL;
226 if (rest != NULL && rest->l > 0) {
227 in.l = rest->l + input->l;
228 in.s = buf = smalloc(in.l + 1);
229 memcpy(in.s, rest->s, rest->l);
230 memcpy(in.s + rest->l, input->s, input->l);
231 rest->l = 0;
234 if (n_iconv_str(iconvd, &out, &in, &in, TRU1) != 0 &&
235 rest != NULL && in.l > 0) {
236 /* Incomplete multibyte at EOF is special */
237 if (flags & _TD_EOF) {
238 out.s = srealloc(out.s, out.l + 4);
239 /* TODO 0xFFFD out.s[out.l++] = '[';*/
240 out.s[out.l++] = '?'; /* TODO 0xFFFD !!! */
241 /* TODO 0xFFFD out.s[out.l++] = ']';*/
242 } else
243 (void)n_str_add(rest, &in);
245 in = out;
246 out.s = NULL;
247 flags &= ~_TD_BUFCOPY;
249 if (buf != NULL)
250 free(buf);
252 #endif
254 if (flags & TD_ISPR)
255 makeprint(&in, &out);
256 else if (flags & _TD_BUFCOPY)
257 n_str_dup(&out, &in);
258 else
259 out = in;
260 if (flags & TD_DELCTRL)
261 out.l = delctrl(out.s, out.l);
263 rv = quoteflt_push(qf, out.s, out.l);
265 if (out.s != in.s)
266 free(out.s);
267 if (in.s != input->s)
268 free(in.s);
269 return rv;
272 static size_t
273 delctrl(char *cp, size_t sz)
275 size_t x = 0, y = 0;
277 while (x < sz) {
278 if (!cntrlchar(cp[x]&0377))
279 cp[y++] = cp[x];
280 x++;
282 return y;
285 static int
286 has_highbit(const char *s)
288 if (s) {
290 if (*s & 0200)
291 return 1;
292 while (*s++ != '\0');
294 return 0;
297 static int
298 name_highbit(struct name *np)
300 while (np) {
301 if (has_highbit(np->n_name) || has_highbit(np->n_fullname))
302 return 1;
303 np = np->n_flink;
305 return 0;
308 char const *
309 charset_get_7bit(void)
311 char const *t;
313 if ((t = value("charset-7bit")) == NULL)
314 t = CHARSET_7BIT;
315 return (t);
318 char const *
319 charset_get_8bit(void)
321 char const *t;
323 if ((t = value(CHARSET_8BIT_VAR)) == NULL)
324 t = CHARSET_8BIT;
325 return (t);
328 char const *
329 charset_get_lc(void)
331 char const *t;
333 if ((t = value("ttycharset")) == NULL)
334 t = CHARSET_8BIT;
335 return (t);
338 void
339 charset_iter_reset(char const *a_charset_to_try_first)
341 char const *sarr[3];
342 size_t sarrl[3], len;
343 char *cp;
345 sarr[0] = a_charset_to_try_first;
346 #ifdef HAVE_ICONV
347 if ((sarr[1] = value("sendcharsets")) == NULL &&
348 value("sendcharsets-else-ttycharset"))
349 sarr[1] = charset_get_lc();
350 #endif
351 sarr[2] = charset_get_8bit();
353 sarrl[2] = len = strlen(sarr[2]);
354 #ifdef HAVE_ICONV
355 if ((cp = UNCONST(sarr[1])) != NULL)
356 len += (sarrl[1] = strlen(cp));
357 else
358 sarrl[1] = 0;
359 if ((cp = UNCONST(sarr[0])) != NULL)
360 len += (sarrl[0] = strlen(cp));
361 else
362 sarrl[0] = 0;
363 #endif
365 _cs_iter_base = cp = salloc(len + 1);
367 #ifdef HAVE_ICONV
368 if ((len = sarrl[0]) != 0) {
369 memcpy(cp, sarr[0], len);
370 cp[len] = ',';
371 cp += ++len;
373 if ((len = sarrl[1]) != 0) {
374 memcpy(cp, sarr[1], len);
375 cp[len] = ',';
376 cp += ++len;
378 #endif
379 len = sarrl[2];
380 memcpy(cp, sarr[2], len);
381 cp[len] = '\0';
382 _cs_iter = NULL;
385 char const *
386 charset_iter_next(void)
388 return (_cs_iter = strcomma(&_cs_iter_base, 1));
391 char const *
392 charset_iter_current(void)
394 return _cs_iter;
397 void
398 charset_iter_recurse(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
400 outer_storage[0] = _cs_iter_base;
401 outer_storage[1] = _cs_iter;
404 void
405 charset_iter_restore(char *outer_storage[2]) /* TODO LEGACY FUN, REMOVE */
407 _cs_iter_base = outer_storage[0];
408 _cs_iter = outer_storage[1];
411 char const *
412 need_hdrconv(struct header *hp, enum gfield w)
414 char const *ret = NULL;
416 if (w & GIDENT) {
417 if (hp->h_from != NULL) {
418 if (name_highbit(hp->h_from))
419 goto jneeds;
420 } else if (has_highbit(myaddrs(NULL)))
421 goto jneeds;
422 if (hp->h_organization) {
423 if (has_highbit(hp->h_organization))
424 goto jneeds;
425 } else if (has_highbit(value("ORGANIZATION")))
426 goto jneeds;
427 if (hp->h_replyto) {
428 if (name_highbit(hp->h_replyto))
429 goto jneeds;
430 } else if (has_highbit(value("replyto")))
431 goto jneeds;
432 if (hp->h_sender) {
433 if (name_highbit(hp->h_sender))
434 goto jneeds;
435 } else if (has_highbit(value("sender")))
436 goto jneeds;
438 if (w & GTO && name_highbit(hp->h_to))
439 goto jneeds;
440 if (w & GCC && name_highbit(hp->h_cc))
441 goto jneeds;
442 if (w & GBCC && name_highbit(hp->h_bcc))
443 goto jneeds;
444 if (w & GSUBJECT && has_highbit(hp->h_subject))
445 jneeds: ret = _CHARSET();
446 return (ret);
449 static int
450 is_this_enc(const char *line, const char *encoding)
452 int quoted = 0, c;
454 if (*line == '"')
455 quoted = 1, line++;
456 while (*line && *encoding)
457 if (c = *line++, lowerconv(c) != *encoding++)
458 return 0;
459 if (quoted && *line == '"')
460 return 1;
461 if (*line == '\0' || whitechar(*line & 0377))
462 return 1;
463 return 0;
467 * Get the mime encoding from a Content-Transfer-Encoding header field.
469 enum mimeenc
470 mime_getenc(char *p)
472 if (is_this_enc(p, "7bit"))
473 return MIME_7B;
474 if (is_this_enc(p, "8bit"))
475 return MIME_8B;
476 if (is_this_enc(p, "base64"))
477 return MIME_B64;
478 if (is_this_enc(p, "binary"))
479 return MIME_BIN;
480 if (is_this_enc(p, "quoted-printable"))
481 return MIME_QP;
482 return MIME_NONE;
486 * Get a mime style parameter from a header line.
488 char *
489 mime_getparam(char const *param, char *h)
491 char *p = h, *q, *r;
492 int c;
493 size_t sz;
495 sz = strlen(param);
496 if (!whitechar(*p & 0377)) {
497 c = '\0';
498 while (*p && (*p != ';' || c == '\\')) {
499 c = c == '\\' ? '\0' : *p;
500 p++;
502 if (*p++ == '\0')
503 return NULL;
505 for (;;) {
506 while (whitechar(*p & 0377))
507 p++;
508 if (ascncasecmp(p, param, sz) == 0) {
509 p += sz;
510 while (whitechar(*p & 0377))
511 p++;
512 if (*p++ == '=')
513 break;
515 c = '\0';
516 while (*p && (*p != ';' || c == '\\')) {
517 if (*p == '"' && c != '\\') {
518 p++;
519 while (*p && (*p != '"' || c == '\\')) {
520 c = c == '\\' ? '\0' : *p;
521 p++;
523 p++;
524 } else {
525 c = c == '\\' ? '\0' : *p;
526 p++;
529 if (*p++ == '\0')
530 return NULL;
532 while (whitechar(*p & 0377))
533 p++;
534 q = p;
535 if (*p == '"') {
536 p++;
537 if ((q = strchr(p, '"')) == NULL)
538 return NULL;
539 } else {
540 while (*q && !whitechar(*q & 0377) && *q != ';')
541 q++;
543 sz = q - p;
544 r = salloc(q - p + 1);
545 memcpy(r, p, sz);
546 *(r + sz) = '\0';
547 return r;
550 char *
551 mime_get_boundary(char *h, size_t *len)
553 char *q = NULL, *p;
554 size_t sz;
556 if ((p = mime_getparam("boundary", h)) != NULL) {
557 sz = strlen(p);
558 if (len != NULL)
559 *len = sz + 2;
560 q = salloc(sz + 3);
561 q[0] = q[1] = '-';
562 memcpy(q + 2, p, sz);
563 *(q + sz + 2) = '\0';
565 return (q);
568 char *
569 mime_create_boundary(void)
571 char *bp;
573 bp = salloc(48);
574 snprintf(bp, 48, "=_%011lu=-%s=_",
575 (ul_it)time_current.tc_time, getrandstring(47 - (11 + 6)));
576 return bp;
580 mime_classify_file(FILE *fp, char const **contenttype, char const **charset,
581 int *do_iconv)
583 /* TODO classify once only PLEASE PLEASE PLEASE */
584 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
585 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
586 * TODO and report that state to the outer world */
587 #define F_ "From "
588 #define F_SIZEOF (sizeof(F_) - 1)
590 char f_buf[F_SIZEOF], *f_p = f_buf;
591 enum { _CLEAN = 0, /* Plain RFC 2822 message */
592 _NCTT = 1<<0, /* *contenttype == NULL */
593 _ISTXT = 1<<1, /* *contenttype =~ text/ */
594 _ISTXTCOK = 1<<2, /* _ISTXT+*mime-allow-text-controls* */
595 _HIGHBIT = 1<<3, /* Not 7bit clean */
596 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
597 _CTRLCHAR = 1<<5, /* Control characters seen */
598 _HASNUL = 1<<6, /* Contains \0 characters */
599 _NOTERMNL = 1<<7, /* Lacks a final newline */
600 _TRAILWS = 1<<8, /* Blanks before NL */
601 _FROM_ = 1<<9 /* ^From_ seen */
602 } ctt = _CLEAN;
603 enum conversion convert;
604 sl_it curlen;
605 int c, lastc;
607 assert(ftell(fp) == 0x0l);
609 *do_iconv = 0;
611 if (*contenttype == NULL)
612 ctt = _NCTT;
613 else if (ascncasecmp(*contenttype, "text/", 5) == 0)
614 ctt = value("mime-allow-text-controls")
615 ? _ISTXT | _ISTXTCOK : _ISTXT;
616 convert = _conversion_by_encoding();
618 if (fsize(fp) == 0)
619 goto j7bit;
621 /* We have to inspect the file content */
622 for (curlen = 0, c = EOF;; ++curlen) {
623 lastc = c;
624 c = getc(fp);
626 if (c == '\0') {
627 ctt |= _HASNUL;
628 if ((ctt & _ISTXTCOK) == 0)
629 break;
630 continue;
632 if (c == '\n' || c == EOF) {
633 if (curlen >= MIME_LINELEN_LIMIT)
634 ctt |= _LONGLINES;
635 if (c == EOF)
636 break;
637 if (blankchar(lastc))
638 ctt |= _TRAILWS;
639 f_p = f_buf;
640 curlen = -1;
641 continue;
644 * A bit hairy is handling of \r=\x0D=CR.
645 * RFC 2045, 6.7: Control characters other than TAB, or
646 * CR and LF as parts of CRLF pairs, must not appear.
647 * \r alone does not force _CTRLCHAR below since we cannot peek
648 * the next character.
649 * Thus right here, inspect the last seen character for if its
650 * \r and set _CTRLCHAR in a delayed fashion
652 /*else*/ if (lastc == '\r')
653 ctt |= _CTRLCHAR;
655 /* Control character? */
656 if (c < 0x20 || c == 0x7F) {
657 /* RFC 2045, 6.7, as above ... */
658 if (c != '\t' && c != '\r')
659 ctt |= _CTRLCHAR;
661 * If there is a escape sequence in backslash notation
662 * defined for this in ANSI X3.159-1989 (ANSI C89),
663 * don't treat it as a control for real.
664 * I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT.
665 * Don't follow libmagic(1) in respect to \v=\x0B=VT.
666 * \f=\x0C=NP; do ignore \e=\x1B=ESC.
668 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
669 continue;
670 ctt |= _HASNUL; /* Force base64 */
671 if ((ctt & _ISTXTCOK) == 0)
672 break;
673 } else if (c & 0x80) {
674 ctt |= _HIGHBIT;
675 /* TODO count chars with HIGHBIT? libmagic?
676 * TODO try encode part - base64 if bails? */
677 if ((ctt & (_NCTT|_ISTXT)) == 0) { /* TODO _NCTT?? */
678 ctt |= _HASNUL; /* Force base64 */
679 break;
681 } else if ((ctt & _FROM_) == 0 && curlen < (sl_it)F_SIZEOF) {
682 *f_p++ = (char)c;
683 if (curlen == (sl_it)(F_SIZEOF - 1) &&
684 (size_t)(f_p - f_buf) == F_SIZEOF &&
685 memcmp(f_buf, F_, F_SIZEOF) == 0)
686 ctt |= _FROM_;
689 if (lastc != '\n')
690 ctt |= _NOTERMNL;
691 rewind(fp);
693 if (ctt & _HASNUL) {
694 convert = CONV_TOB64;
695 /* Don't overwrite a text content-type to allow UTF-16 and
696 * such, but only on request;
697 * Otherwise enforce what file(1)/libmagic(3) would suggest */
698 if (ctt & _ISTXTCOK)
699 goto jcharset;
700 if (ctt & (_NCTT|_ISTXT))
701 *contenttype = "application/octet-stream";
702 if (*charset == NULL)
703 *charset = "binary";
704 goto jleave;
707 if (ctt & (_LONGLINES|_CTRLCHAR|_NOTERMNL|_TRAILWS|_FROM_)) {
708 convert = CONV_TOQP;
709 goto jstepi;
711 if (ctt & _HIGHBIT) {
712 jstepi: if (ctt & (_NCTT|_ISTXT))
713 *do_iconv = (ctt & _HIGHBIT) != 0;
714 } else
715 j7bit: convert = CONV_7BIT;
716 if (ctt & _NCTT)
717 *contenttype = "text/plain";
719 /* Not an attachment with specified charset? */
720 jcharset:
721 if (*charset == NULL)
722 *charset = (ctt & _HIGHBIT) ? _CHARSET() : charset_get_7bit();
723 jleave:
724 return (convert);
725 #undef F_
726 #undef F_SIZEOF
729 enum mimecontent
730 mime_classify_content_of_part(struct mimepart const *mip)
732 enum mimecontent mc = MIME_UNKNOWN;
733 char const *ct = mip->m_ct_type_plain;
735 if (asccasecmp(ct, "application/octet-stream") == 0 &&
736 mip->m_filename != NULL &&
737 value("mime-counter-evidence")) {
738 ct = mime_classify_content_type_by_fileext(mip->m_filename);
739 if (ct == NULL)
740 /* TODO how about let *mime-counter-evidence* have
741 * TODO a value, and if set, saving the attachment in
742 * TODO a temporary file that mime_classify_file() can
743 * TODO examine, and using MIME_TEXT if that gives us
744 * TODO something that seems to be human readable?! */
745 goto jleave;
747 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
748 mc = MIME_TEXT;
749 else if (asccasecmp(ct, "text/plain") == 0)
750 mc = MIME_TEXT_PLAIN;
751 else if (asccasecmp(ct, "text/html") == 0)
752 mc = MIME_TEXT_HTML;
753 else if (ascncasecmp(ct, "text/", 5) == 0)
754 mc = MIME_TEXT;
755 else if (asccasecmp(ct, "message/rfc822") == 0)
756 mc = MIME_822;
757 else if (ascncasecmp(ct, "message/", 8) == 0)
758 mc = MIME_MESSAGE;
759 else if (asccasecmp(ct, "multipart/alternative") == 0)
760 mc = MIME_ALTERNATIVE;
761 else if (asccasecmp(ct, "multipart/digest") == 0)
762 mc = MIME_DIGEST;
763 else if (ascncasecmp(ct, "multipart/", 10) == 0)
764 mc = MIME_MULTI;
765 else if (asccasecmp(ct, "application/x-pkcs7-mime") == 0 ||
766 asccasecmp(ct, "application/pkcs7-mime") == 0)
767 mc = MIME_PKCS7;
768 jleave:
769 return (mc);
772 char *
773 mime_classify_content_type_by_fileext(char const *name)
775 char *content = NULL;
776 struct mtnode *mtn;
777 size_t nlen;
779 if ((name = strrchr(name, '.')) == NULL || *++name == '\0')
780 goto jleave;
782 if (_mt_list == NULL)
783 _mt_init();
785 nlen = strlen(name);
786 for (mtn = _mt_list; mtn != NULL; mtn = mtn->mt_next) {
787 char const *ext = mtn->mt_line + mtn->mt_mtlen + 1,
788 *cp = ext;
789 do {
790 while (! whitechar(*cp) && *cp != '\0')
791 ++cp;
792 /* Better to do case-insensitive comparison on
793 * extension, since the RFC doesn't specify case of
794 * attribute values? */
795 if (nlen == (size_t)(cp - ext) &&
796 ascncasecmp(name, ext, nlen) == 0) {
797 content = savestrbuf(mtn->mt_line,
798 mtn->mt_mtlen);
799 goto jleave;
801 while (whitechar(*cp) && *cp != '\0')
802 ++cp;
803 ext = cp;
804 } while (*ext != '\0');
806 jleave:
807 return (content);
811 cmimetypes(void *v)
813 char **argv = v;
814 struct mtnode *mtn;
816 if (*argv == NULL)
817 goto jlist;
818 if (argv[1] != NULL)
819 goto jerr;
820 if (asccasecmp(*argv, "show") == 0)
821 goto jlist;
822 if (asccasecmp(*argv, "clear") == 0)
823 goto jclear;
824 jerr:
825 fprintf(stderr, "Synopsis: mimetypes: %s\n", tr(418,
826 "Either <show> (default) or <clear> the mime.types cache"));
827 v = NULL;
828 jleave:
829 return (v == NULL ? STOP : OKAY);
831 jlist: {
832 FILE *fp;
833 char *cp;
834 size_t l;
836 if (_mt_list == NULL)
837 _mt_init();
839 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
840 perror("tmpfile");
841 v = NULL;
842 goto jleave;
844 rm(cp);
845 Ftfree(&cp);
847 for (l = 0, mtn = _mt_list; mtn != NULL; ++l, mtn = mtn->mt_next)
848 fprintf(fp, "%s\t%s\n", mtn->mt_line,
849 mtn->mt_line + mtn->mt_mtlen + 1);
851 page_or_print(fp, l);
852 Fclose(fp);
854 goto jleave;
856 jclear:
857 while ((mtn = _mt_list) != NULL) {
858 _mt_list = mtn->mt_next;
859 free(mtn);
861 goto jleave;
865 * Convert header fields from RFC 1522 format
866 * TODO mime_fromhdr(): NO error handling, fat; REWRITE **ASAP**
868 void
869 mime_fromhdr(struct str const *in, struct str *out, enum tdflags flags)
871 /* TODO mime_fromhdr(): is called with strings that contain newlines;
872 * TODO this is the usual newline problem all around the codebase;
873 * TODO i.e., if we strip it, then the display misses it ;} */
874 struct str cin, cout;
875 char *p, *op, *upper, *cs, *cbeg;
876 int convert;
877 size_t lastoutl = (size_t)-1;
878 #ifdef HAVE_ICONV
879 char const *tcs;
880 iconv_t fhicd = (iconv_t)-1;
881 #endif
883 out->l = 0;
884 if (in->l == 0) {
885 *(out->s = smalloc(1)) = '\0';
886 goto jleave;
888 out->s = NULL;
890 #ifdef HAVE_ICONV
891 tcs = charset_get_lc();
892 #endif
893 p = in->s;
894 upper = p + in->l;
896 while (p < upper) {
897 op = p;
898 if (*p == '=' && *(p + 1) == '?') {
899 p += 2;
900 cbeg = p;
901 while (p < upper && *p != '?')
902 p++; /* strip charset */
903 if (p >= upper)
904 goto jnotmime;
905 cs = salloc(++p - cbeg);
906 memcpy(cs, cbeg, p - cbeg - 1);
907 cs[p - cbeg - 1] = '\0';
908 #ifdef HAVE_ICONV
909 if (fhicd != (iconv_t)-1)
910 n_iconv_close(fhicd);
911 if (asccasecmp(cs, tcs) != 0)
912 fhicd = n_iconv_open(tcs, cs);
913 else
914 fhicd = (iconv_t)-1;
915 #endif
916 switch (*p) {
917 case 'B': case 'b':
918 convert = CONV_FROMB64;
919 break;
920 case 'Q': case 'q':
921 convert = CONV_FROMQP;
922 break;
923 default: /* invalid, ignore */
924 goto jnotmime;
926 if (*++p != '?')
927 goto jnotmime;
928 cin.s = ++p;
929 cin.l = 1;
930 for (;;) {
931 if (p + 1 >= upper)
932 goto jnotmime;
933 if (*p++ == '?' && *p == '=')
934 break;
935 cin.l++;
937 ++p;
938 cin.l--;
940 cout.s = NULL;
941 cout.l = 0;
942 if (convert == CONV_FROMB64) {
943 /* XXX Take care for, and strip LF from
944 * XXX [Invalid Base64 encoding ignored] */
945 if (b64_decode(&cout, &cin, NULL) == STOP &&
946 cout.s[cout.l - 1] == '\n')
947 --cout.l;
948 } else
949 (void)qp_decode(&cout, &cin, NULL);
950 if (lastoutl != (size_t)-1)
951 out->l = lastoutl;
952 #ifdef HAVE_ICONV
953 if ((flags & TD_ICONV) && fhicd != (iconv_t)-1) {
954 cin.s = NULL, cin.l = 0; /* XXX string pool ! */
955 convert = n_iconv_str(fhicd, &cin, &cout,
956 NULL, TRU1);
957 out = n_str_add(out, &cin);
958 if (convert) /* EINVAL at EOS */
959 out = n_str_add_buf(out, "?", 1);
960 free(cin.s);
961 } else {
962 #endif
963 out = n_str_add(out, &cout);
964 #ifdef HAVE_ICONV
966 #endif
967 lastoutl = out->l;
968 free(cout.s);
969 } else {
970 jnotmime:
971 p = op;
972 convert = 1;
973 while ((op = p + convert) < upper &&
974 (op[0] != '=' || op[1] != '?'))
975 ++convert;
976 out = n_str_add_buf(out, p, convert);
977 p += convert;
978 if (! blankchar(p[-1]))
979 lastoutl = (size_t)-1;
982 out->s[out->l] = '\0';
984 if (flags & TD_ISPR) {
985 makeprint(out, &cout);
986 free(out->s);
987 *out = cout;
989 if (flags & TD_DELCTRL)
990 out->l = delctrl(out->s, out->l);
991 #ifdef HAVE_ICONV
992 if (fhicd != (iconv_t)-1)
993 n_iconv_close(fhicd);
994 #endif
995 jleave:
996 return;
1000 * Convert header fields to RFC 1522 format and write to the file fo.
1002 static size_t
1003 mime_write_tohdr(struct str *in, FILE *fo) /* TODO rewrite - FAST! */
1005 struct str cin, cout;
1006 char buf[B64_LINESIZE];
1007 char const *charset7, *charset, *upper, *wbeg, *wend, *lastspc,
1008 *lastwordend = NULL;
1009 size_t sz = 0, col = 0, quoteany, wr, charsetlen,
1010 maxcol = 65 /* there is the header field's name, too */;
1011 bool_t highbit, mustquote, broken;
1013 charset7 = charset_get_7bit();
1014 charset = _CHARSET();
1015 wr = strlen(charset7);
1016 charsetlen = strlen(charset);
1017 charsetlen = MAX(charsetlen, wr);
1018 upper = in->s + in->l;
1020 /* xxx note this results in too much hits since =/? force quoting even
1021 * xxx if they don't form =? etc. */
1022 quoteany = mime_cte_mustquote(in->s, in->l, TRU1);
1024 highbit = FAL0;
1025 if (quoteany != 0)
1026 for (wbeg = in->s; wbeg < upper; ++wbeg)
1027 if ((uc_it)*wbeg & 0x80)
1028 highbit = TRU1;
1030 if (quoteany << 1 > in->l) {
1032 * Print the entire field in base64.
1034 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1035 wend = upper;
1036 cin.s = UNCONST(wbeg);
1037 for (;;) {
1038 cin.l = wend - wbeg;
1039 if (cin.l * 4/3 + 7 + charsetlen
1040 < maxcol - col) {
1041 cout.s = buf;
1042 cout.l = sizeof buf;
1043 wr = fprintf(fo, "=?%s?B?%s?=",
1044 highbit ? charset : charset7,
1045 b64_encode(&cout, &cin, B64_BUF
1046 )->s);
1047 sz += wr, col += wr;
1048 if (wend < upper) {
1049 fwrite("\n ", sizeof (char),
1050 2, fo);
1051 sz += 2;
1052 col = 0;
1053 maxcol = 76;
1055 break;
1056 } else {
1057 if (col) {
1058 fprintf(fo, "\n ");
1059 sz += 2;
1060 col = 0;
1061 maxcol = 76;
1062 } else
1063 wend -= 4;
1067 } else {
1069 * Print the field word-wise in quoted-printable.
1071 broken = FAL0;
1072 for (wbeg = in->s; wbeg < upper; wbeg = wend) {
1073 lastspc = NULL;
1074 while (wbeg < upper && whitechar(*wbeg)) {
1075 lastspc = lastspc ? lastspc : wbeg;
1076 wbeg++;
1077 col++;
1078 broken = FAL0;
1080 if (wbeg == upper) {
1081 if (lastspc)
1082 while (lastspc < wbeg) {
1083 putc(*lastspc&0377, fo);
1084 lastspc++,
1085 sz++;
1087 break;
1090 if (lastspc != NULL)
1091 broken = FAL0;
1092 highbit = FAL0;
1093 for (wend = wbeg; wend < upper && ! whitechar(*wend);
1094 ++wend)
1095 if ((uc_it)*wend & 0x80)
1096 highbit = TRU1;
1097 mustquote = (mime_cte_mustquote(wbeg,
1098 (size_t)(wend - wbeg), TRU1) != 0);
1100 if (mustquote || broken ||
1101 ((wend - wbeg) >= 76-5 && quoteany)) {
1102 for (cout.s = NULL;;) {
1103 cin.s = UNCONST(lastwordend ?
1104 lastwordend : wbeg);
1105 cin.l = wend - cin.s;
1106 (void)qp_encode(&cout, &cin, QP_ISHEAD);
1107 wr = cout.l + charsetlen + 7;
1108 jqp_retest:
1109 if (col <= maxcol &&
1110 wr <= maxcol - col) {
1111 if (lastspc) {
1112 /* TODO because we inc-
1113 * TODO luded the WS in
1114 * TODO the encoded str,
1115 * TODO put SP only??
1116 * TODO RFC: "any
1117 * 'linear-white-space'
1118 * that separates
1119 * a pair of adjacent
1120 * 'encoded-word's is
1121 * ignored" */
1122 putc(' ', fo);
1123 ++sz;
1124 ++col;
1126 fprintf(fo, "=?%s?Q?%.*s?=",
1127 highbit ? charset
1128 : charset7,
1129 (int)cout.l, cout.s);
1130 sz += wr, col += wr;
1131 break;
1132 } else if (col > 1) {
1133 /* TODO assuming SP separator,
1134 * TODO ignore *lastspc* !?? */
1135 broken = TRU1;
1136 if (lastspc != NULL) {
1137 putc('\n', fo);
1138 ++sz;
1139 col = 0;
1140 } else {
1141 fputs("\n ",
1142 fo);
1143 sz += 2;
1144 col = 1;
1146 maxcol = 76;
1147 goto jqp_retest;
1148 } else {
1149 for (;;) { /* XXX */
1150 wend -= 4;
1151 assert(wend > wbeg);
1152 if (wr - 4 < maxcol)
1153 break;
1154 wr -= 4;
1158 if (cout.s != NULL)
1159 free(cout.s);
1160 lastwordend = wend;
1161 } else {
1162 if (col &&
1163 (size_t)(wend - wbeg) > maxcol - col) {
1164 putc('\n', fo);
1165 sz++;
1166 col = 0;
1167 maxcol = 76;
1168 if (lastspc == NULL) {
1169 putc(' ', fo);
1170 sz++;
1171 maxcol--;
1172 } else
1173 maxcol -= wbeg - lastspc;
1175 if (lastspc)
1176 while (lastspc < wbeg) {
1177 putc(*lastspc&0377, fo);
1178 lastspc++, sz++;
1180 wr = fwrite(wbeg, sizeof *wbeg,
1181 wend - wbeg, fo);
1182 sz += wr, col += wr;
1183 lastwordend = NULL;
1187 return sz;
1191 * Write len characters of the passed string to the passed file,
1192 * doing charset and header conversion.
1194 static size_t
1195 convhdra(char const *str, size_t len, FILE *fp)
1197 #ifdef HAVE_ICONV
1198 struct str ciconv;
1199 #endif
1200 struct str cin;
1201 size_t ret = 0;
1203 cin.s = UNCONST(str);
1204 cin.l = len;
1205 #ifdef HAVE_ICONV
1206 ciconv.s = NULL;
1207 if (iconvd != (iconv_t)-1) {
1208 ciconv.l = 0;
1209 if (n_iconv_str(iconvd, &ciconv, &cin, NULL, FAL0) != 0)
1210 goto jleave;
1211 cin = ciconv;
1213 #endif
1214 ret = mime_write_tohdr(&cin, fp);
1215 #ifdef HAVE_ICONV
1216 jleave:
1217 if (ciconv.s != NULL)
1218 free(ciconv.s);
1219 #endif
1220 return ret;
1224 * Write an address to a header field.
1226 static size_t
1227 mime_write_tohdr_a(struct str *in, FILE *f)
1229 char const *cp, *lastcp;
1230 size_t sz = 0;
1232 in->s[in->l] = '\0';
1233 lastcp = in->s;
1234 if ((cp = routeaddr(in->s)) != NULL && cp > lastcp) {
1235 sz += convhdra(lastcp, cp - lastcp, f);
1236 lastcp = cp;
1237 } else
1238 cp = in->s;
1239 for ( ; *cp; cp++) {
1240 switch (*cp) {
1241 case '(':
1242 sz += fwrite(lastcp, 1, cp - lastcp + 1, f);
1243 lastcp = ++cp;
1244 cp = skip_comment(cp);
1245 if (--cp > lastcp)
1246 sz += convhdra(lastcp, cp - lastcp, f);
1247 lastcp = cp;
1248 break;
1249 case '"':
1250 while (*cp) {
1251 if (*++cp == '"')
1252 break;
1253 if (*cp == '\\' && cp[1])
1254 cp++;
1256 break;
1259 if (cp > lastcp)
1260 sz += fwrite(lastcp, 1, cp - lastcp, f);
1261 return sz;
1264 static void
1265 addstr(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
1267 *buf = srealloc(*buf, *sz += len);
1268 memcpy(&(*buf)[*pos], str, len);
1269 *pos += len;
1272 static void
1273 addconv(char **buf, size_t *sz, size_t *pos, char const *str, size_t len)
1275 struct str in, out;
1277 in.s = UNCONST(str);
1278 in.l = len;
1279 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
1280 addstr(buf, sz, pos, out.s, out.l);
1281 free(out.s);
1285 * Interpret MIME strings in parts of an address field.
1287 char *
1288 mime_fromaddr(char const *name)
1290 char const *cp, *lastcp;
1291 char *res = NULL;
1292 size_t ressz = 1, rescur = 0;
1294 if (name == NULL)
1295 return (res);
1296 if (*name == '\0')
1297 return savestr(name);
1298 if ((cp = routeaddr(name)) != NULL && cp > name) {
1299 addconv(&res, &ressz, &rescur, name, cp - name);
1300 lastcp = cp;
1301 } else
1302 cp = lastcp = name;
1303 for ( ; *cp; cp++) {
1304 switch (*cp) {
1305 case '(':
1306 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp + 1);
1307 lastcp = ++cp;
1308 cp = skip_comment(cp);
1309 if (--cp > lastcp)
1310 addconv(&res, &ressz, &rescur, lastcp,
1311 cp - lastcp);
1312 lastcp = cp;
1313 break;
1314 case '"':
1315 while (*cp) {
1316 if (*++cp == '"')
1317 break;
1318 if (*cp == '\\' && cp[1])
1319 cp++;
1321 break;
1324 if (cp > lastcp)
1325 addstr(&res, &ressz, &rescur, lastcp, cp - lastcp);
1326 /* TODO rescur==0: inserted to silence Coverity ...; check that */
1327 if (rescur == 0)
1328 res = UNCONST("");
1329 else
1330 res[rescur] = '\0';
1331 { char *x = res;
1332 res = savestr(res);
1333 free(x);
1335 return (res);
1338 ssize_t
1339 xmime_write(char const *ptr, size_t size, FILE *f, enum conversion convert,
1340 enum tdflags dflags, struct str *rest)
1342 ssize_t rv;
1343 struct quoteflt *qf;
1345 quoteflt_reset(qf = quoteflt_dummy(), f);
1346 rv = mime_write(ptr, size, f, convert, dflags, qf, rest);
1347 assert(quoteflt_flush(qf) == 0);
1348 return rv;
1351 ssize_t
1352 mime_write(char const *ptr, size_t size, FILE *f,
1353 enum conversion convert, enum tdflags dflags,
1354 struct quoteflt *qf, struct str *rest)
1356 /* TODO note: after send/MIME layer rewrite we will have a string pool
1357 * TODO so that memory allocation count drops down massively; for now,
1358 * TODO v14.0 that is, we pay a lot & heavily depend on the allocator */
1359 struct str in, out;
1360 ssize_t sz;
1361 int state;
1363 in.s = UNCONST(ptr);
1364 in.l = size;
1365 out.s = NULL;
1366 out.l = 0;
1368 dflags |= _TD_BUFCOPY;
1369 if ((sz = size) == 0) {
1370 if (rest != NULL && rest->l != 0)
1371 goto jconvert;
1372 goto jleave;
1375 #ifdef HAVE_ICONV
1376 if ((dflags & TD_ICONV) && iconvd != (iconv_t)-1 &&
1377 (convert == CONV_TOQP || convert == CONV_8BIT ||
1378 convert == CONV_TOB64 || convert == CONV_TOHDR)) {
1379 if (n_iconv_str(iconvd, &out, &in, NULL, FAL0) != 0) {
1380 /* XXX report conversion error? */;
1381 sz = -1;
1382 goto jleave;
1384 in = out;
1385 out.s = NULL;
1386 dflags &= ~_TD_BUFCOPY;
1388 #endif
1390 jconvert:
1391 switch (convert) {
1392 case CONV_FROMQP:
1393 state = qp_decode(&out, &in, rest);
1394 goto jqpb64_dec;
1395 case CONV_TOQP:
1396 (void)qp_encode(&out, &in, QP_NONE);
1397 goto jqpb64_enc;
1398 case CONV_8BIT:
1399 sz = quoteflt_push(qf, in.s, in.l);
1400 break;
1401 case CONV_FROMB64:
1402 rest = NULL;
1403 /* FALLTHRU */
1404 case CONV_FROMB64_T:
1405 state = b64_decode(&out, &in, rest);
1406 jqpb64_dec:
1407 if ((sz = out.l) != 0) {
1408 size_t opl = qf->qf_pfix_len;
1409 if (state != OKAY)
1410 qf->qf_pfix_len = 0;
1411 sz = _fwrite_td(&out, (dflags & ~_TD_BUFCOPY), rest,qf);
1412 qf->qf_pfix_len = opl;
1414 if (state != OKAY)
1415 sz = -1;
1416 break;
1417 case CONV_TOB64:
1418 (void)b64_encode(&out, &in, B64_LF|B64_MULTILINE);
1419 jqpb64_enc:
1420 sz = fwrite(out.s, sizeof *out.s, out.l, f);
1421 if (sz != (ssize_t)out.l)
1422 sz = -1;
1423 break;
1424 case CONV_FROMHDR:
1425 mime_fromhdr(&in, &out,
1426 TD_ISPR | TD_ICONV | (dflags & TD_DELCTRL));
1427 sz = quoteflt_push(qf, out.s, out.l);
1428 break;
1429 case CONV_TOHDR:
1430 sz = mime_write_tohdr(&in, f);
1431 break;
1432 case CONV_TOHDR_A:
1433 sz = mime_write_tohdr_a(&in, f);
1434 break;
1435 default:
1436 sz = _fwrite_td(&in, dflags, NULL, qf);
1437 break;
1439 jleave:
1440 if (out.s != NULL)
1441 free(out.s);
1442 if (in.s != ptr)
1443 free(in.s);
1444 return sz;