*features* and `features' are "the same"
[s-mailx.git] / mime_types.c
blob8cf50ff54e44386259344d85b35c20f19aee5e1a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?mimetype' and other mime.types(5) related facilities.
3 *@ "Keep in sync with" ./mime.types.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #undef n_FILE
20 #define n_FILE mime_types
22 #ifndef HAVE_AMALGAMATION
23 # include "nail.h"
24 #endif
26 enum mime_type {
27 _MT_APPLICATION,
28 _MT_AUDIO,
29 _MT_IMAGE,
30 _MT_MESSAGE,
31 _MT_MULTIPART,
32 _MT_TEXT,
33 _MT_VIDEO,
34 _MT_OTHER,
35 __MT_TMIN = 0,
36 __MT_TMAX = _MT_OTHER,
37 __MT_TMASK = 0x07,
39 _MT_LOADED = 1<< 8, /* Not struct mtbltin */
40 _MT_USR = 1<< 9, /* MIME_TYPES_USR */
41 _MT_SYS = 1<<10, /* MIME_TYPES_SYS */
43 _MT_PLAIN = 1<<16, /* Without pipe handler display as text */
44 _MT_SOUP_h = 2<<16, /* Ditto, but HTML tagsoup parser if possible */
45 _MT_SOUP_H = 3<<16, /* HTML tagsoup parser, else NOT plain text */
46 __MT_MARKMASK = _MT_SOUP_H
49 enum mime_type_class {
50 _MT_C_CLEAN = 0, /* Plain RFC 5322 message */
51 _MT_C_NCTT = 1<<0, /* *contenttype == NULL */
52 _MT_C_ISTXT = 1<<1, /* *contenttype =~ text\/ */
53 _MT_C_ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
54 _MT_C_HIGHBIT = 1<<3, /* Not 7bit clean */
55 _MT_C_LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
56 _MT_C_CTRLCHAR = 1<<5, /* Control characters seen */
57 _MT_C_HASNUL = 1<<6, /* Contains \0 characters */
58 _MT_C_NOTERMNL = 1<<7, /* Lacks a final newline */
59 _MT_C_FROM_ = 1<<8, /* ^From_ seen */
60 _MT_C_SUGGEST_DONE = 1<<16 /* Inspector suggests to stop further parse */
63 struct mtbltin {
64 ui32_t mtb_flags;
65 ui32_t mtb_mtlen;
66 char const *mtb_line;
69 struct mtnode {
70 struct mtnode *mt_next;
71 ui32_t mt_flags;
72 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
73 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
74 * that could already store character data here */
75 char const *mt_line;
78 struct mtlookup {
79 char const *mtl_name;
80 size_t mtl_nlen;
81 struct mtnode const *mtl_node;
82 char *mtl_result; /* If requested, salloc()ed MIME type */
85 struct mt_class_arg {
86 char const *mtca_buf;
87 size_t mtca_len;
88 ssize_t mtca_curlen;
89 char mtca_lastc;
90 char mtca_c;
91 enum mime_type_class mtca_mtc;
94 static struct mtbltin const _mt_bltin[] = {
95 #include "mime_types.h"
98 static char const _mt_typnames[][16] = {
99 "application/", "audio/", "image/",
100 "message/", "multipart/", "text/",
101 "video/"
103 CTA(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
104 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
105 _MT_VIDEO == 6);
107 /* */
108 static bool_t _mt_is_init;
109 static struct mtnode *_mt_list;
111 /* Initialize MIME type list in order */
112 static void _mt_init(void);
113 static bool_t __mt_load_file(ui32_t orflags,
114 char const *file, char **line, size_t *linesize);
116 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
117 * for `mimetype' */
118 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
119 char const *line, size_t len);
121 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
122 * if with_result >mtl_result will be created upon success for the former */
123 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
124 char const *name, bool_t with_result);
125 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
126 char const *mtname);
128 /* In-depth inspection of raw content: call _round() repeatedly, last time with
129 * a 0 length buffer, finally check .mtca_mtc for result.
130 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
131 * as the resulting classification is unambiguous */
132 SINLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
133 enum mime_type_class initval);
134 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
136 /* We need an in-depth inspection of an application/octet-stream part */
137 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp);
139 /* Check wether a *pipe-XY* handler is applicable, and adjust flags according
140 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
141 * isn't changed if mhp doesn't apply */
142 static enum mime_handler_flags _mt_pipe_check(struct mime_handler *mhp);
144 static void
145 _mt_init(void)
147 struct mtnode *tail;
148 char c, *line; /* TODO line pool (below) */
149 size_t linesize;
150 ui32_t i, j;
151 char const *srcs_arr[10], *ccp, **srcs;
152 NYD_ENTER;
154 /*if (_mt_is_init)
155 * goto jleave;*/
157 /* Always load our builtins */
158 for (tail = NULL, i = 0; i < NELEM(_mt_bltin); ++i) {
159 struct mtbltin const *mtbp = _mt_bltin + i;
160 struct mtnode *mtnp = smalloc(sizeof *mtnp);
162 if (tail != NULL)
163 tail->mt_next = mtnp;
164 else
165 _mt_list = mtnp;
166 tail = mtnp;
167 mtnp->mt_next = NULL;
168 mtnp->mt_flags = mtbp->mtb_flags;
169 mtnp->mt_mtlen = mtbp->mtb_mtlen;
170 mtnp->mt_line = mtbp->mtb_line;
173 /* Decide which files sources have to be loaded */
174 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
175 ccp = "US";
176 else if (*ccp == '\0')
177 goto jleave;
179 srcs = srcs_arr + 2;
180 srcs[-1] = srcs[-2] = NULL;
182 if (strchr(ccp, '=') != NULL) {
183 line = savestr(ccp);
185 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
186 switch ((c = *ccp)) {
187 case 'S': case 's':
188 srcs_arr[1] = MIME_TYPES_SYS;
189 if (0) {
190 /* FALLTHRU */
191 case 'U': case 'u':
192 srcs_arr[0] = MIME_TYPES_USR;
194 if (ccp[1] != '\0')
195 goto jecontent;
196 break;
197 case 'F': case 'f':
198 if (*++ccp == '=' && *++ccp != '\0') {
199 if (PTR2SIZE(srcs - srcs_arr) < NELEM(srcs_arr))
200 *srcs++ = ccp;
201 else
202 n_err(_("*mimetypes-load-control*: too many sources, "
203 "skipping \"%s\"\n"), ccp);
204 continue;
206 /* FALLTHRU */
207 default:
208 goto jecontent;
211 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
212 switch (c) {
213 case 'S': case 's': srcs_arr[1] = MIME_TYPES_SYS; break;
214 case 'U': case 'u': srcs_arr[0] = MIME_TYPES_USR; break;
215 default:
216 jecontent:
217 n_err(_("*mimetypes-load-control*: unsupported content: \"%s\"\n"),
218 ccp);
219 goto jleave;
222 /* Load all file-based sources in the desired order */
223 line = NULL;
224 linesize = 0;
225 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
226 i > 0; ++j, ++srcs, --i)
227 if (*srcs == NULL)
228 continue;
229 else if (!__mt_load_file((j == 0 ? _MT_USR : (j == 1 ? _MT_SYS : 0)),
230 *srcs, &line, &linesize)) {
231 if ((options & OPT_D_V) || j > 1)
232 n_err(_("*mimetypes-load-control*: can't open or load \"%s\"\n"),
233 *srcs);
235 if (line != NULL)
236 free(line);
237 jleave:
238 _mt_is_init = TRU1;
239 NYD_LEAVE;
242 static bool_t
243 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
245 char const *cp;
246 FILE *fp;
247 struct mtnode *head, *tail, *mtnp;
248 size_t len;
249 NYD_ENTER;
251 if ((cp = file_expand(file)) == NULL || (fp = Fopen(cp, "r")) == NULL) {
252 cp = NULL;
253 goto jleave;
256 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
257 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
258 if (head == NULL)
259 head = tail = mtnp;
260 else
261 tail->mt_next = mtnp;
262 tail = mtnp;
264 if (head != NULL) {
265 tail->mt_next = _mt_list;
266 _mt_list = head;
269 Fclose(fp);
270 jleave:
271 NYD_LEAVE;
272 return (cp != NULL);
275 static struct mtnode *
276 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
278 struct mtnode *mtnp = NULL;
279 char const *typ, *subtyp;
280 size_t tlen, i;
281 NYD_ENTER;
283 /* Drop anything after a comment first */
284 if ((typ = memchr(line, '#', len)) != NULL)
285 len = PTR2SIZE(typ - line);
287 /* Then trim any trailing whitespace from line (including NL/CR) */
288 while (len > 0 && spacechar(line[len - 1]))
289 --len;
291 /* Isolate MIME type, trim any whitespace from it */
292 while (len > 0 && blankchar(*line))
293 ++line, --len;
294 typ = line;
296 /* (But wait - is there a type marker?) */
297 if (!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@') {
298 if (len < 2)
299 goto jeinval;
300 if (typ[1] == ' ') {
301 orflags |= _MT_PLAIN;
302 typ += 2;
303 len -= 2;
304 line += 2;
305 } else if (len > 4 && typ[2] == '@' && typ[3] == ' ') {
306 switch (typ[1]) {
307 case 't': orflags |= _MT_PLAIN; goto jexttypmar;
308 case 'h': orflags |= _MT_SOUP_h; goto jexttypmar;
309 case 'H': orflags |= _MT_SOUP_H;
310 jexttypmar:
311 typ += 4;
312 len -= 4;
313 line += 4;
314 break;
315 default:
316 goto jeinval;
318 } else
319 goto jeinval;
322 while (len > 0 && !blankchar(*line))
323 ++line, --len;
324 /* Ignore empty lines and even incomplete specifications (only MIME type)
325 * because this is quite common in mime.types(5) files */
326 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
327 if (cmdcalled)
328 n_err(_("Empty MIME type or no extensions given: \"%s\"\n"),
329 (len == 0 ? _("(no value)") : line));
330 goto jleave;
333 if ((subtyp = memchr(typ, '/', tlen)) == NULL) {
334 jeinval:
335 if (cmdcalled || (options & OPT_D_V))
336 n_err(_("%s MIME type: \"%s\"\n"),
337 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")), typ);
338 goto jleave;
340 ++subtyp;
342 /* Map to mime_type */
343 tlen = PTR2SIZE(subtyp - typ);
344 for (i = __MT_TMIN;;) {
345 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
346 orflags |= i;
347 tlen = PTR2SIZE(line - subtyp);
348 typ = subtyp;
349 break;
351 if (++i == __MT_TMAX) {
352 orflags |= _MT_OTHER;
353 tlen = PTR2SIZE(line - typ);
354 break;
358 /* Strip leading whitespace from the list of extensions;
359 * trailing WS has already been trimmed away above.
360 * Be silent on slots which define a mimetype without any value */
361 while (len > 0 && blankchar(*line))
362 ++line, --len;
363 if (len == 0)
364 goto jleave;
366 /* */
367 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
368 mtnp->mt_next = NULL;
369 mtnp->mt_flags = (orflags |= _MT_LOADED);
370 mtnp->mt_mtlen = (ui32_t)tlen;
371 { char *l = (char*)(mtnp + 1);
372 mtnp->mt_line = l;
373 memcpy(l, typ, tlen);
374 memcpy(l + tlen, line, len);
375 tlen += len;
376 l[tlen] = '\0';
379 jleave:
380 NYD_LEAVE;
381 return mtnp;
384 static struct mtlookup *
385 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
387 struct mtnode *mtnp;
388 size_t nlen, i, j;
389 char const *ext, *cp;
390 NYD2_ENTER;
392 memset(mtlp, 0, sizeof *mtlp);
394 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = name)) == 0 ||
395 memchr(name, '.', nlen) == NULL)
396 goto jnull_leave;
398 if (!_mt_is_init)
399 _mt_init();
401 /* ..all the MIME types */
402 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
403 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
404 cp = ext;
405 while (whitechar(*cp))
406 ++cp;
407 ext = cp;
408 while (!whitechar(*cp) && *cp != '\0')
409 ++cp;
411 if ((i = PTR2SIZE(cp - ext)) == 0)
412 break;
413 /* Don't allow neither of ".txt" or "txt" to match "txt" */
414 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
415 ascncasecmp(name + j, ext, i))
416 continue;
418 /* Found it */
419 mtlp->mtl_node = mtnp;
421 if (!with_result)
422 goto jleave;
424 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
425 name = "";
426 j = 0;
427 } else {
428 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
429 j = strlen(name);
431 i = mtnp->mt_mtlen;
432 mtlp->mtl_result = salloc(i + j +1);
433 if (j > 0)
434 memcpy(mtlp->mtl_result, name, j);
435 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
436 mtlp->mtl_result[j += i] = '\0';
437 goto jleave;
439 jnull_leave:
440 mtlp = NULL;
441 jleave:
442 NYD2_LEAVE;
443 return mtlp;
446 static struct mtlookup *
447 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
449 struct mtnode *mtnp;
450 size_t nlen, i, j;
451 char const *cp;
452 NYD2_ENTER;
454 memset(mtlp, 0, sizeof *mtlp);
456 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
457 goto jnull_leave;
459 if (!_mt_is_init)
460 _mt_init();
462 /* ..all the MIME types */
463 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
464 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
465 cp = "";
466 j = 0;
467 } else {
468 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
469 j = strlen(cp);
471 i = mtnp->mt_mtlen;
473 if (i + j == mtlp->mtl_nlen) {
474 char *xmt = ac_alloc(i + j +1);
475 if (j > 0)
476 memcpy(xmt, cp, j);
477 memcpy(xmt + j, mtnp->mt_line, i);
478 xmt[j += i] = '\0';
479 i = asccasecmp(mtname, xmt);
480 ac_free(xmt);
482 if (!i) {
483 /* Found it */
484 mtlp->mtl_node = mtnp;
485 goto jleave;
489 jnull_leave:
490 mtlp = NULL;
491 jleave:
492 NYD2_LEAVE;
493 return mtlp;
496 SINLINE struct mt_class_arg *
497 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
499 NYD2_ENTER;
500 memset(mtcap, 0, sizeof *mtcap);
501 mtcap->mtca_lastc = mtcap->mtca_c = EOF;
502 mtcap->mtca_mtc = initval;
503 NYD2_LEAVE;
504 return mtcap;
507 static enum mime_type_class
508 _mt_classify_round(struct mt_class_arg *mtcap)
510 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
511 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
512 * TODO and report that state to the outer world */
513 #define F_ "From "
514 #define F_SIZEOF (sizeof(F_) -1)
515 char f_buf[F_SIZEOF], *f_p = f_buf;
516 char const *buf;
517 size_t blen;
518 ssize_t curlen;
519 int c, lastc;
520 enum mime_type_class mtc;
521 NYD2_ENTER;
523 buf = mtcap->mtca_buf;
524 blen = mtcap->mtca_len;
525 curlen = mtcap->mtca_curlen;
526 c = mtcap->mtca_c;
527 lastc = mtcap->mtca_lastc;
528 mtc = mtcap->mtca_mtc;
530 for (;; ++curlen) {
531 lastc = c;
532 if (blen == 0) {
533 /* Real EOF, or only current buffer end? */
534 if (mtcap->mtca_len == 0)
535 c = EOF;
536 else
537 break;
538 } else
539 c = (uc_i)*buf++;
540 --blen;
542 if (c == '\0') {
543 mtc |= _MT_C_HASNUL;
544 if (!(mtc & _MT_C_ISTXTCOK)) {
545 mtc |= _MT_C_SUGGEST_DONE;
546 break;
548 continue;
550 if (c == '\n' || c == EOF) {
551 if (curlen >= MIME_LINELEN_LIMIT)
552 mtc |= _MT_C_LONGLINES;
553 if (c == EOF) {
554 break;
556 f_p = f_buf;
557 curlen = -1;
558 continue;
560 /* A bit hairy is handling of \r=\x0D=CR.
561 * RFC 2045, 6.7:
562 * Control characters other than TAB, or CR and LF as parts of CRLF
563 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
564 * we cannot peek the next character. Thus right here, inspect the last
565 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
566 /*else*/ if (lastc == '\r')
567 mtc |= _MT_C_CTRLCHAR;
569 /* Control character? XXX this is all ASCII here */
570 if (c < 0x20 || c == 0x7F) {
571 /* RFC 2045, 6.7, as above ... */
572 if (c != '\t' && c != '\r')
573 mtc |= _MT_C_CTRLCHAR;
574 /* If there is a escape sequence in backslash notation defined for
575 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
576 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
577 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
578 * \e=\x1B=ESC */
579 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
580 continue;
581 mtc |= _MT_C_HASNUL; /* Force base64 */
582 if (!(mtc & _MT_C_ISTXTCOK)) {
583 mtc |= _MT_C_SUGGEST_DONE;
584 break;
586 } else if ((ui8_t)c & 0x80) {
587 mtc |= _MT_C_HIGHBIT;
588 /* TODO count chars with HIGHBIT? libmagic?
589 * TODO try encode part - base64 if bails? */
590 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
591 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
592 break;
594 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
595 *f_p++ = (char)c;
596 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
597 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
598 !memcmp(f_buf, F_, F_SIZEOF))
599 mtc |= _MT_C_FROM_;
602 if (c == EOF && lastc != '\n')
603 mtc |= _MT_C_NOTERMNL;
605 mtcap->mtca_curlen = curlen;
606 mtcap->mtca_lastc = lastc;
607 mtcap->mtca_c = c;
608 mtcap->mtca_mtc = mtc;
609 NYD2_LEAVE;
610 return mtc;
611 #undef F_
612 #undef F_SIZEOF
615 static enum mimecontent
616 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp)
618 struct str in = {NULL, 0}, rest = {NULL, 0}, dec = {NULL, 0};
619 struct mt_class_arg mtca;
620 enum mime_type_class mtc;
621 int lc, c;
622 size_t cnt, lsz;
623 FILE *ibuf;
624 off_t start_off;
625 enum mimecontent mc;
626 NYD2_ENTER;
628 assert(mpp->m_mime_enc != MIMEE_BIN);
630 mc = MIME_UNKNOWN;
631 UNINIT(mtc, 0);
633 /* TODO v15-compat Note we actually bypass our usual file handling by
634 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
635 * TODO all of this, and until then doing it like this is the only option
636 * TODO to integrate nicely into whoever calls us */
637 start_off = ftell(mb.mb_itf);
638 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
639 jos_leave:
640 fseek(mb.mb_itf, start_off, SEEK_SET);
641 goto jleave;
643 cnt = mpp->m_size;
645 /* Skip part headers */
646 for (lc = '\0'; cnt > 0; lc = c, --cnt)
647 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
648 break;
649 if (cnt == 0 || ferror(ibuf))
650 goto jos_leave;
652 /* So now let's inspect the part content, decoding content-transfer-encoding
653 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
654 _mt_classify_init(&mtca, _MT_C_ISTXT);
656 for (lsz = 0;;) {
657 bool_t dobuf;
659 c = (--cnt == 0) ? EOF : getc(ibuf);
660 if ((dobuf = (c == '\n'))) {
661 /* Ignore empty lines */
662 if (lsz == 0)
663 continue;
664 } else if ((dobuf = (c == EOF))) {
665 if (lsz == 0 && rest.l == 0)
666 break;
669 if (in.l + 1 >= lsz)
670 in.s = srealloc(in.s, lsz += LINESIZE);
671 if (c != EOF)
672 in.s[in.l++] = (char)c;
673 if (!dobuf)
674 continue;
676 jdobuf:
677 switch (mpp->m_mime_enc) {
678 case MIMEE_B64:
679 if (b64_decode(&dec, &in, &rest) == STOP) {
680 mtca.mtca_mtc = _MT_C_HASNUL;
681 goto jstopit; /* break;break; */
683 break;
684 case MIMEE_QP:
685 /* Drin */
686 if (qp_decode(&dec, &in, &rest) == STOP) {
687 mtca.mtca_mtc = _MT_C_HASNUL;
688 goto jstopit; /* break;break; */
690 if (dec.l == 0 && c != EOF) {
691 in.l = 0;
692 continue;
694 break;
695 default:
696 /* Temporarily switch those two buffers.. */
697 dec = in;
698 in.s = NULL;
699 in.l = 0;
700 break;
703 mtca.mtca_buf = dec.s;
704 mtca.mtca_len = (ssize_t)dec.l;
705 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
706 mtc = _MT_C_HASNUL;
707 break;
710 if (c == EOF)
711 break;
712 /* ..and restore switched */
713 if (in.s == NULL) {
714 in = dec;
715 dec.s = NULL;
717 in.l = dec.l = 0;
719 if (rest.l > 0) {
720 in.l = 0;
721 goto jdobuf;
723 jstopit:
724 if (in.s != NULL)
725 free(in.s);
726 if (dec.s != NULL)
727 free(dec.s);
728 if (rest.s != NULL)
729 free(rest.s);
731 fseek(mb.mb_itf, start_off, SEEK_SET);
733 if (!(mtc & (_MT_C_HASNUL | _MT_C_CTRLCHAR))) {
734 mc = MIME_TEXT_PLAIN;
735 if (mce & MIMECE_ALL_OVWR)
736 mpp->m_ct_type_plain = "text/plain";
737 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
738 mpp->m_ct_type_usr_ovwr = "text/plain";
740 jleave:
741 NYD2_LEAVE;
742 return mc;
745 static enum mime_handler_flags
746 _mt_pipe_check(struct mime_handler *mhp)
748 enum mime_handler_flags rv_orig, rv;
749 char const *cp;
750 NYD2_ENTER;
752 rv_orig = rv = mhp->mh_flags;
754 /* Do we have any handler for this part? */
755 if (*(cp = mhp->mh_shell_cmd) == '\0')
756 goto jleave;
757 else if (*cp++ != '@') {
758 rv |= MIME_HDL_CMD;
759 goto jleave;
760 } else if (*cp == '\0') {
761 rv |= MIME_HDL_TEXT;
762 goto jleave;
765 jnextc:
766 switch (*cp) {
767 case '*': rv |= MIME_HDL_ALWAYS; ++cp; goto jnextc;
768 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
769 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
770 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
771 case '+':
772 if (rv & MIME_HDL_TMPF)
773 rv |= MIME_HDL_TMPF_UNLINK;
774 rv |= MIME_HDL_TMPF;
775 ++cp;
776 goto jnextc;
777 case '=':
778 rv |= MIME_HDL_TMPF_FILL;
779 ++cp;
780 goto jnextc;
781 case '@':
782 ++cp;
783 /* FALLTHRU */
784 default:
785 break;
787 mhp->mh_shell_cmd = cp;
789 /* Implications */
790 if (rv & MIME_HDL_TMPF_FILL)
791 rv |= MIME_HDL_TMPF;
793 /* Exceptions */
794 if (rv & MIME_HDL_ISQUOTE) {
795 if (rv & MIME_HDL_NOQUOTE)
796 goto jerr;
798 /* Cannot fetch data back from asynchronous process */
799 if (rv & MIME_HDL_ASYNC)
800 goto jerr;
802 /* TODO Can't use a "needsterminal" program for quoting */
803 if (rv & MIME_HDL_NEEDSTERM)
804 goto jerr;
807 if (rv & MIME_HDL_NEEDSTERM) {
808 if (rv & MIME_HDL_ASYNC) {
809 n_err(_("MIME type handlers: can't use \"needsterminal\" and "
810 "\"x-nail-async\" together\n"));
811 goto jerr;
814 /* needsterminal needs a terminal */
815 if (!(options & OPT_INTERACTIVE))
816 goto jerr;
819 if (!(rv & MIME_HDL_ALWAYS) && !(pstate & PS_MSGLIST_DIRECT)) {
820 /* Viewing multiple messages in one go, don't block system */
821 mhp->mh_msg.l = strlen(mhp->mh_msg.s = UNCONST(
822 _("[-- Directly address message only for display --]\n")));
823 rv |= MIME_HDL_MSG;
824 goto jleave;
827 rv |= MIME_HDL_CMD;
828 jleave:
829 mhp->mh_flags = rv;
830 NYD2_LEAVE;
831 return rv;
832 jerr:
833 rv = rv_orig;
834 goto jleave;
837 FL int
838 c_mimetype(void *v)
840 char **argv = v;
841 struct mtnode *mtnp;
842 NYD_ENTER;
844 if (!_mt_is_init)
845 _mt_init();
847 if (*argv == NULL) {
848 FILE *fp;
849 size_t l;
851 if (_mt_list == NULL) {
852 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
853 goto jleave;
856 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
857 NULL) {
858 n_perr(_("tmpfile"), 0);
859 v = NULL;
860 goto jleave;
863 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
864 char const *tmark, *typ;
866 switch (mtnp->mt_flags & __MT_MARKMASK) {
867 case _MT_PLAIN: tmark = "/t"; break;
868 case _MT_SOUP_h: tmark = "/h"; break;
869 case _MT_SOUP_H: tmark = "/H"; break;
870 default: tmark = " "; break;
872 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
873 ? "" : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
875 fprintf(fp, "%c%s %s%.*s <%s>\n",
876 (mtnp->mt_flags & _MT_USR ? 'U'
877 : (mtnp->mt_flags & _MT_SYS ? 'S'
878 : (mtnp->mt_flags & _MT_LOADED ? 'F' : 'B'))),
879 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
880 mtnp->mt_line + mtnp->mt_mtlen);
883 page_or_print(fp, l);
884 Fclose(fp);
885 } else {
886 for (; *argv != NULL; ++argv) {
887 mtnp = _mt_create(TRU1, _MT_LOADED, *argv, strlen(*argv));
888 if (mtnp != NULL) {
889 mtnp->mt_next = _mt_list;
890 _mt_list = mtnp;
891 } else
892 v = NULL;
895 jleave:
896 NYD_LEAVE;
897 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
900 FL int
901 c_unmimetype(void *v)
903 char **argv = v;
904 struct mtnode *lnp, *mtnp;
905 bool_t match;
906 NYD_ENTER;
908 /* Need to load that first as necessary */
909 if (!_mt_is_init)
910 _mt_init();
912 for (; *argv != NULL; ++argv) {
913 if (!asccasecmp(*argv, "reset")) {
914 _mt_is_init = FAL0;
915 goto jdelall;
918 if (argv[0][0] == '*' && argv[0][1] == '\0') {
919 jdelall:
920 while ((mtnp = _mt_list) != NULL) {
921 _mt_list = mtnp->mt_next;
922 free(mtnp);
924 continue;
927 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
928 char const *typ;
929 char *val;
930 size_t i;
932 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
933 typ = "";
934 i = 0;
935 } else {
936 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
937 i = strlen(typ);
940 val = ac_alloc(i + mtnp->mt_mtlen +1);
941 memcpy(val, typ, i);
942 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
943 val[i += mtnp->mt_mtlen] = '\0';
944 i = asccasecmp(val, *argv);
945 ac_free(val);
947 if (!i) {
948 struct mtnode *nnp = mtnp->mt_next;
949 if (lnp == NULL)
950 _mt_list = nnp;
951 else
952 lnp->mt_next = nnp;
953 free(mtnp);
954 mtnp = nnp;
955 match = TRU1;
956 } else
957 lnp = mtnp, mtnp = mtnp->mt_next;
959 if (!match) {
960 if (!(pstate & PS_IN_LOAD) || (options & OPT_D_V))
961 n_err(_("No such MIME type: \"%s\"\n"), *argv);
962 v = NULL;
965 NYD_LEAVE;
966 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
969 FL char *
970 mime_type_classify_filename(char const *name)
972 struct mtlookup mtl;
973 NYD_ENTER;
975 _mt_by_filename(&mtl, name, TRU1);
976 NYD_LEAVE;
977 return mtl.mtl_result;
980 FL enum conversion
981 mime_type_classify_file(FILE *fp, char const **contenttype,
982 char const **charset, int *do_iconv)
984 /* TODO classify once only PLEASE PLEASE PLEASE */
985 enum mime_type_class mtc;
986 enum mime_enc menc;
987 off_t fpsz;
988 NYD_ENTER;
990 assert(ftell(fp) == 0x0l);
992 *do_iconv = 0;
994 if (*contenttype == NULL)
995 mtc = _MT_C_NCTT;
996 else if (!ascncasecmp(*contenttype, "text/", 5))
997 mtc = ok_blook(mime_allow_text_controls)
998 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
999 else
1000 mtc = _MT_C_CLEAN;
1002 menc = mime_enc_target();
1004 if ((fpsz = fsize(fp)) == 0)
1005 goto j7bit;
1006 else {
1007 char buf[BUFFER_SIZE];
1008 struct mt_class_arg mtca;
1010 _mt_classify_init(&mtca, mtc);
1011 for (;;) {
1012 mtca.mtca_len = fread(buf, sizeof(buf[0]), NELEM(buf), fp);
1013 mtca.mtca_buf = buf;
1014 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1015 break;
1016 if (mtca.mtca_len == 0)
1017 break;
1019 /* TODO ferror(fp) ! */
1020 rewind(fp);
1023 if (mtc & _MT_C_HASNUL) {
1024 menc = MIMEE_B64;
1025 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1026 * on request; else enforce what file(1)/libmagic(3) would suggest */
1027 if (mtc & _MT_C_ISTXTCOK)
1028 goto jcharset;
1029 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1030 *contenttype = "application/octet-stream";
1031 if (*charset == NULL)
1032 *charset = "binary";
1033 goto jleave;
1036 if (mtc &
1037 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1038 if (menc != MIMEE_B64)
1039 menc = MIMEE_QP;
1040 goto jstepi;
1042 if (mtc & _MT_C_HIGHBIT) {
1043 jstepi:
1044 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1045 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1046 } else
1047 j7bit:
1048 menc = MIMEE_7B;
1049 if (mtc & _MT_C_NCTT)
1050 *contenttype = "text/plain";
1052 /* Not an attachment with specified charset? */
1053 jcharset:
1054 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1055 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1056 : charset_get_7bit();
1057 jleave:
1058 NYD_LEAVE;
1059 /* TODO mime_type_file_classify() shouldn't return conversion */
1060 return (menc == MIMEE_7B ? CONV_7BIT :
1061 (menc == MIMEE_8B ? CONV_8BIT :
1062 (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1065 FL enum mimecontent
1066 mime_type_classify_part(struct mimepart *mpp) /* FIXME charset=binary ??? */
1068 struct mtlookup mtl;
1069 enum mimecontent mc;
1070 char const *ct;
1071 union {char const *cp; ui32_t f;} mce;
1072 bool_t is_os;
1073 NYD_ENTER;
1075 mc = MIME_UNKNOWN;
1076 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1077 ct = "";
1079 if ((mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
1080 char *eptr;
1081 long l;
1083 l = strtol(mce.cp, &eptr, 0); /* XXX strtol */
1084 if (*mce.cp == '\0')
1085 is_os = FAL0;
1086 else if (*eptr != '\0' || l < 0 || (ui64_t)(ul_i)l >= UI32_MAX) {
1087 n_err(_("Can't parse *mime-counter-evidence* value \"%s\"\n"), mce.cp);
1088 is_os = FAL0;
1089 } else {
1090 mce.f = (ui32_t)l | MIMECE_SET;
1091 is_os = !asccasecmp(ct, "application/octet-stream");
1093 if (mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))) {
1094 if (_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL) {
1095 if (is_os)
1096 goto jos_content_check;
1097 } else if (is_os || asccasecmp(ct, mtl.mtl_result)) {
1098 if (mce.f & MIMECE_ALL_OVWR)
1099 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1100 if (mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1101 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1105 } else
1106 is_os = FAL0;
1108 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1109 mc = MIME_TEXT;
1110 else if (is_asccaseprefix(ct, "text/")) {
1111 ct += sizeof("text/") -1;
1112 if (!asccasecmp(ct, "plain"))
1113 mc = MIME_TEXT_PLAIN;
1114 else if (!asccasecmp(ct, "html"))
1115 mc = MIME_TEXT_HTML;
1116 else
1117 mc = MIME_TEXT;
1118 } else if (is_asccaseprefix(ct, "message/")) {
1119 ct += sizeof("message/") -1;
1120 if (!asccasecmp(ct, "rfc822"))
1121 mc = MIME_822;
1122 else
1123 mc = MIME_MESSAGE;
1124 } else if (!ascncasecmp(ct, "multipart/", 10)) {
1125 ct += sizeof("multipart/") -1;
1126 if (!asccasecmp(ct, "alternative"))
1127 mc = MIME_ALTERNATIVE;
1128 else if (!asccasecmp(ct, "related"))
1129 mc = MIME_RELATED;
1130 else if (!asccasecmp(ct, "digest"))
1131 mc = MIME_DIGEST;
1132 else
1133 mc = MIME_MULTI;
1134 } else if (is_asccaseprefix(ct, "application/")) {
1135 if (is_os)
1136 goto jos_content_check;
1137 ct += sizeof("application/") -1;
1138 if (!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1139 mc = MIME_PKCS7;
1141 jleave:
1142 NYD_LEAVE;
1143 return mc;
1145 jos_content_check:
1146 if ((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1147 mpp->m_charset != NULL && asccasecmp(mpp->m_charset, "binary"))
1148 mc = _mt_classify_os_part(mce.f, mpp);
1149 goto jleave;
1152 FL enum mime_handler_flags
1153 mime_type_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1154 enum sendaction action)
1156 #define __S "pipe-"
1157 #define __L (sizeof(__S) -1)
1158 struct mtlookup mtl;
1159 char *buf, *cp;
1160 enum mime_handler_flags rv;
1161 char const *es, *cs, *ccp;
1162 size_t el, cl, l;
1163 NYD_ENTER;
1165 memset(mhp, 0, sizeof *mhp);
1166 buf = NULL;
1168 rv = MIME_HDL_NULL;
1169 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1170 rv |= MIME_HDL_ISQUOTE;
1171 else if (action != SEND_TODISP && action != SEND_TODISP_ALL)
1172 goto jleave;
1174 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1175 *++es != '\0') ? strlen(es) : 0;
1176 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1177 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1178 if ((l = MAX(el, cl)) == 0) {
1179 /* TODO this should be done during parse time! */
1180 goto jleave;
1183 /* We don't pass the flags around, so ensure carrier is up-to-date */
1184 mhp->mh_flags = rv;
1186 buf = ac_alloc(__L + l +1);
1187 memcpy(buf, __S, __L);
1189 /* File-extension handlers take precedence.
1190 * Yes, we really "fail" here for file extensions which clash MIME types */
1191 if (el > 0) {
1192 memcpy(buf + __L, es, el +1);
1193 for (cp = buf + __L; *cp != '\0'; ++cp)
1194 *cp = lowerconv(*cp);
1196 if ((mhp->mh_shell_cmd = ccp = vok_vlook(buf)) != NULL) {
1197 rv = _mt_pipe_check(mhp);
1198 goto jleave;
1202 /* Then MIME Content-Type:, if any */
1203 if (cl == 0)
1204 goto jleave;
1206 memcpy(buf + __L, cs, cl +1);
1207 for (cp = buf + __L; *cp != '\0'; ++cp)
1208 *cp = lowerconv(*cp);
1210 if ((mhp->mh_shell_cmd = vok_vlook(buf)) != NULL) {
1211 rv = _mt_pipe_check(mhp);
1212 goto jleave;
1215 if (_mt_by_mtname(&mtl, cs) != NULL)
1216 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
1217 #ifndef HAVE_FILTER_HTML_TAGSOUP
1218 case _MT_SOUP_H:
1219 break;
1220 #endif
1221 case _MT_SOUP_h:
1222 #ifdef HAVE_FILTER_HTML_TAGSOUP
1223 case _MT_SOUP_H:
1224 mhp->mh_ptf = &htmlflt_process_main;
1225 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1226 UNCONST(_("Builtin HTML tagsoup filter")));
1227 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1228 goto jleave;
1229 #endif
1230 /* FALLTHRU */
1231 case _MT_PLAIN:
1232 mhp->mh_msg.l = strlen(mhp->mh_msg.s = UNCONST(_("Plain text view")));
1233 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1234 goto jleave;
1235 default:
1236 break;
1239 jleave:
1240 if (buf != NULL)
1241 ac_free(buf);
1243 mhp->mh_flags = rv;
1244 if ((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL)
1245 mhp->mh_msg.l = strlen(mhp->mh_msg.s = UNCONST(
1246 _("[-- No MIME handler installed or none applicable --]")));
1247 NYD_LEAVE;
1248 return rv;
1249 #undef __L
1250 #undef __S
1253 /* s-it-mode */