Hack: message/rfc822 may only be [78]bit/binary acc. RFC 2046, 5.2.1
[s-mailx.git] / mime_types.c
blobe87f7d71e829f008546089cebb3553f19828a02d
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 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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_CMD = 1<< 8, /* Via `mimetype' (not struct mtbltin) */
40 _MT_USR = 1<< 9, /* MIME_TYPES_USR */
41 _MT_SYS = 1<<10, /* MIME_TYPES_SYS */
42 _MT_FSPEC = 1<<11, /* Loaded via f= *mimetypes-load-control* spec. */
44 _MT_PLAIN = 1<<16, /* Without pipe handler display as text */
45 _MT_SOUP_h = 2<<16, /* Ditto, but HTML tagsoup parser if possible */
46 _MT_SOUP_H = 3<<16, /* HTML tagsoup parser, else NOT plain text */
47 __MT_MARKMASK = _MT_SOUP_H
50 enum mime_type_class {
51 _MT_C_CLEAN = 0, /* Plain RFC 5322 message */
52 _MT_C_NCTT = 1<<0, /* *contenttype == NULL */
53 _MT_C_ISTXT = 1<<1, /* *contenttype =~ text\/ */
54 _MT_C_ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
55 _MT_C_HIGHBIT = 1<<3, /* Not 7bit clean */
56 _MT_C_LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
57 _MT_C_CTRLCHAR = 1<<5, /* Control characters seen */
58 _MT_C_HASNUL = 1<<6, /* Contains \0 characters */
59 _MT_C_NOTERMNL = 1<<7, /* Lacks a final newline */
60 _MT_C_FROM_ = 1<<8, /* ^From_ seen */
61 _MT_C_FROM_1STLINE = 1<<9, /* From_ line seen */
62 _MT_C_SUGGEST_DONE = 1<<16, /* Inspector suggests to stop further parse */
63 _MT_C__1STLINE = 1<<17 /* .. */
66 struct mtbltin {
67 ui32_t mtb_flags;
68 ui32_t mtb_mtlen;
69 char const *mtb_line;
72 struct mtnode {
73 struct mtnode *mt_next;
74 ui32_t mt_flags;
75 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
76 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
77 * that could already store character data here */
78 char const *mt_line;
81 struct mtlookup {
82 char const *mtl_name;
83 size_t mtl_nlen;
84 struct mtnode const *mtl_node;
85 char *mtl_result; /* If requested, salloc()ed MIME type */
88 struct mt_class_arg {
89 char const *mtca_buf;
90 size_t mtca_len;
91 ssize_t mtca_curlen;
92 char mtca_lastc;
93 char mtca_c;
94 enum mime_type_class mtca_mtc;
97 static struct mtbltin const _mt_bltin[] = {
98 #include "mime_types.h"
101 static char const _mt_typnames[][16] = {
102 "application/", "audio/", "image/",
103 "message/", "multipart/", "text/",
104 "video/"
106 n_CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
107 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
108 _MT_VIDEO == 6);
110 /* */
111 static bool_t _mt_is_init;
112 static struct mtnode *_mt_list;
114 /* Initialize MIME type list in order */
115 static void _mt_init(void);
116 static bool_t __mt_load_file(ui32_t orflags,
117 char const *file, char **line, size_t *linesize);
119 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
120 * for `mimetype' */
121 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
122 char const *line, size_t len);
124 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
125 * if with_result >mtl_result will be created upon success for the former */
126 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
127 char const *name, bool_t with_result);
128 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
129 char const *mtname);
131 /* In-depth inspection of raw content: call _round() repeatedly, last time with
132 * a 0 length buffer, finally check .mtca_mtc for result.
133 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
134 * as the resulting classification is unambiguous */
135 SINLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
136 enum mime_type_class initval);
137 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
139 /* We need an in-depth inspection of an application/octet-stream part */
140 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp);
142 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
143 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
144 * isn't changed if mhp doesn't apply */
145 static enum mime_handler_flags _mt_pipe_check(struct mime_handler *mhp);
147 static void
148 _mt_init(void)
150 struct mtnode *tail;
151 char c, *line; /* TODO line pool (below) */
152 size_t linesize;
153 ui32_t i, j;
154 char const *srcs_arr[10], *ccp, **srcs;
155 NYD_ENTER;
157 /*if (_mt_is_init)
158 * goto jleave;*/
160 /* Always load our builtins */
161 for (tail = NULL, i = 0; i < n_NELEM(_mt_bltin); ++i) {
162 struct mtbltin const *mtbp = _mt_bltin + i;
163 struct mtnode *mtnp = smalloc(sizeof *mtnp);
165 if (tail != NULL)
166 tail->mt_next = mtnp;
167 else
168 _mt_list = mtnp;
169 tail = mtnp;
170 mtnp->mt_next = NULL;
171 mtnp->mt_flags = mtbp->mtb_flags;
172 mtnp->mt_mtlen = mtbp->mtb_mtlen;
173 mtnp->mt_line = mtbp->mtb_line;
176 /* Decide which files sources have to be loaded */
177 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
178 ccp = "US";
179 else if (*ccp == '\0')
180 goto jleave;
182 srcs = srcs_arr + 2;
183 srcs[-1] = srcs[-2] = NULL;
185 if (strchr(ccp, '=') != NULL) {
186 line = savestr(ccp);
188 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
189 switch ((c = *ccp)) {
190 case 'S': case 's':
191 srcs_arr[1] = MIME_TYPES_SYS;
192 if (0) {
193 /* FALLTHRU */
194 case 'U': case 'u':
195 srcs_arr[0] = MIME_TYPES_USR;
197 if (ccp[1] != '\0')
198 goto jecontent;
199 break;
200 case 'F': case 'f':
201 if (*++ccp == '=' && *++ccp != '\0') {
202 if (PTR2SIZE(srcs - srcs_arr) < n_NELEM(srcs_arr))
203 *srcs++ = ccp;
204 else
205 n_err(_("*mimetypes-load-control*: too many sources, "
206 "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
207 continue;
209 /* FALLTHRU */
210 default:
211 goto jecontent;
214 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
215 switch (c) {
216 case 'S': case 's': srcs_arr[1] = MIME_TYPES_SYS; break;
217 case 'U': case 'u': srcs_arr[0] = MIME_TYPES_USR; break;
218 default:
219 jecontent:
220 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp);
221 goto jleave;
224 /* Load all file-based sources in the desired order */
225 line = NULL;
226 linesize = 0;
227 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
228 i > 0; ++j, ++srcs, --i)
229 if (*srcs == NULL)
230 continue;
231 else if (!__mt_load_file((j == 0 ? _MT_USR
232 : (j == 1 ? _MT_SYS : _MT_FSPEC)), *srcs, &line, &linesize)) {
233 if ((options & OPT_D_V) || j > 1)
234 n_err(_("*mimetypes-load-control*: can't open or load %s\n"),
235 n_shexp_quote_cp(*srcs, FAL0));
237 if (line != NULL)
238 free(line);
239 jleave:
240 _mt_is_init = TRU1;
241 NYD_LEAVE;
244 static bool_t
245 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
247 char const *cp;
248 FILE *fp;
249 struct mtnode *head, *tail, *mtnp;
250 size_t len;
251 NYD_ENTER;
253 if ((cp = file_expand(file)) == NULL || (fp = Fopen(cp, "r")) == NULL) {
254 cp = NULL;
255 goto jleave;
258 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
259 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
260 if (head == NULL)
261 head = tail = mtnp;
262 else
263 tail->mt_next = mtnp;
264 tail = mtnp;
266 if (head != NULL) {
267 tail->mt_next = _mt_list;
268 _mt_list = head;
271 Fclose(fp);
272 jleave:
273 NYD_LEAVE;
274 return (cp != NULL);
277 static struct mtnode *
278 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
280 struct mtnode *mtnp = NULL;
281 char const *typ, *subtyp;
282 size_t tlen, i;
283 NYD_ENTER;
285 /* Drop anything after a comment first */
286 if ((typ = memchr(line, '#', len)) != NULL)
287 len = PTR2SIZE(typ - line);
289 /* Then trim any trailing whitespace from line (including NL/CR) */
290 while (len > 0 && spacechar(line[len - 1]))
291 --len;
293 /* Isolate MIME type, trim any whitespace from it */
294 while (len > 0 && blankchar(*line))
295 ++line, --len;
296 typ = line;
298 /* (But wait - is there a type marker?) */
299 if (!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@') {
300 if (len < 2)
301 goto jeinval;
302 if (typ[1] == ' ') {
303 orflags |= _MT_PLAIN;
304 typ += 2;
305 len -= 2;
306 line += 2;
307 } else if (len > 4 && typ[2] == '@' && typ[3] == ' ') {
308 switch (typ[1]) {
309 case 't': orflags |= _MT_PLAIN; goto jexttypmar;
310 case 'h': orflags |= _MT_SOUP_h; goto jexttypmar;
311 case 'H': orflags |= _MT_SOUP_H;
312 jexttypmar:
313 typ += 4;
314 len -= 4;
315 line += 4;
316 break;
317 default:
318 goto jeinval;
320 } else
321 goto jeinval;
324 while (len > 0 && !blankchar(*line))
325 ++line, --len;
326 /* Ignore empty lines and even incomplete specifications (only MIME type)
327 * because this is quite common in mime.types(5) files */
328 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
329 if (cmdcalled)
330 n_err(_("Empty MIME type or no extensions given: %s\n"),
331 (len == 0 ? _("(no value)") : line));
332 goto jleave;
335 if ((subtyp = memchr(typ, '/', tlen)) == NULL) {
336 jeinval:
337 if (cmdcalled || (options & OPT_D_V))
338 n_err(_("%s MIME type: %s\n"),
339 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")), typ);
340 goto jleave;
342 ++subtyp;
344 /* Map to mime_type */
345 tlen = PTR2SIZE(subtyp - typ);
346 for (i = __MT_TMIN;;) {
347 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
348 orflags |= i;
349 tlen = PTR2SIZE(line - subtyp);
350 typ = subtyp;
351 break;
353 if (++i == __MT_TMAX) {
354 orflags |= _MT_OTHER;
355 tlen = PTR2SIZE(line - typ);
356 break;
360 /* Strip leading whitespace from the list of extensions;
361 * trailing WS has already been trimmed away above.
362 * Be silent on slots which define a mimetype without any value */
363 while (len > 0 && blankchar(*line))
364 ++line, --len;
365 if (len == 0)
366 goto jleave;
368 /* */
369 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
370 mtnp->mt_next = NULL;
371 mtnp->mt_flags = orflags;
372 mtnp->mt_mtlen = (ui32_t)tlen;
373 { char *l = (char*)(mtnp + 1);
374 mtnp->mt_line = l;
375 memcpy(l, typ, tlen);
376 memcpy(l + tlen, line, len);
377 tlen += len;
378 l[tlen] = '\0';
381 jleave:
382 NYD_LEAVE;
383 return mtnp;
386 static struct mtlookup *
387 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
389 struct mtnode *mtnp;
390 size_t nlen, i, j;
391 char const *ext, *cp;
392 NYD2_ENTER;
394 memset(mtlp, 0, sizeof *mtlp);
396 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
397 goto jnull_leave;
398 /* We need a period TODO we should support names like README etc. */
399 for (i = nlen; name[--i] != '.';)
400 if (i == 0 || name[i] == '/') /* XXX no magics */
401 goto jnull_leave;
402 /* While here, basename() it */
403 while (i > 0 && name[i - 1] != '/')
404 --i;
405 name += i;
406 nlen -= i;
407 mtlp->mtl_name = name;
408 mtlp->mtl_nlen = nlen;
410 if (!_mt_is_init)
411 _mt_init();
413 /* ..all the MIME types */
414 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
415 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
416 cp = ext;
417 while (whitechar(*cp))
418 ++cp;
419 ext = cp;
420 while (!whitechar(*cp) && *cp != '\0')
421 ++cp;
423 if ((i = PTR2SIZE(cp - ext)) == 0)
424 break;
425 /* Don't allow neither of ".txt" or "txt" to match "txt" */
426 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
427 ascncasecmp(name + j, ext, i))
428 continue;
430 /* Found it */
431 mtlp->mtl_node = mtnp;
433 if (!with_result)
434 goto jleave;
436 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
437 name = n_empty;
438 j = 0;
439 } else {
440 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
441 j = strlen(name);
443 i = mtnp->mt_mtlen;
444 mtlp->mtl_result = salloc(i + j +1);
445 if (j > 0)
446 memcpy(mtlp->mtl_result, name, j);
447 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
448 mtlp->mtl_result[j += i] = '\0';
449 goto jleave;
451 jnull_leave:
452 mtlp = NULL;
453 jleave:
454 NYD2_LEAVE;
455 return mtlp;
458 static struct mtlookup *
459 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
461 struct mtnode *mtnp;
462 size_t nlen, i, j;
463 char const *cp;
464 NYD2_ENTER;
466 memset(mtlp, 0, sizeof *mtlp);
468 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
469 goto jnull_leave;
471 if (!_mt_is_init)
472 _mt_init();
474 /* ..all the MIME types */
475 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
476 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
477 cp = n_empty;
478 j = 0;
479 } else {
480 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
481 j = strlen(cp);
483 i = mtnp->mt_mtlen;
485 if (i + j == mtlp->mtl_nlen) {
486 char *xmt = ac_alloc(i + j +1);
487 if (j > 0)
488 memcpy(xmt, cp, j);
489 memcpy(xmt + j, mtnp->mt_line, i);
490 xmt[j += i] = '\0';
491 i = asccasecmp(mtname, xmt);
492 ac_free(xmt);
494 if (!i) {
495 /* Found it */
496 mtlp->mtl_node = mtnp;
497 goto jleave;
501 jnull_leave:
502 mtlp = NULL;
503 jleave:
504 NYD2_LEAVE;
505 return mtlp;
508 SINLINE struct mt_class_arg *
509 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
511 NYD2_ENTER;
512 memset(mtcap, 0, sizeof *mtcap);
513 mtcap->mtca_lastc = mtcap->mtca_c = EOF;
514 mtcap->mtca_mtc = initval | _MT_C__1STLINE;
515 NYD2_LEAVE;
516 return mtcap;
519 static enum mime_type_class
520 _mt_classify_round(struct mt_class_arg *mtcap) /* TODO dig UTF-8 for !text/!! */
522 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
523 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
524 * TODO and report that state to the outer world */
525 #define F_ "From "
526 #define F_SIZEOF (sizeof(F_) -1)
527 char f_buf[F_SIZEOF], *f_p = f_buf;
528 char const *buf;
529 size_t blen;
530 ssize_t curlen;
531 int c, lastc;
532 enum mime_type_class mtc;
533 NYD2_ENTER;
535 buf = mtcap->mtca_buf;
536 blen = mtcap->mtca_len;
537 curlen = mtcap->mtca_curlen;
538 c = mtcap->mtca_c;
539 lastc = mtcap->mtca_lastc;
540 mtc = mtcap->mtca_mtc;
542 for (;; ++curlen) {
543 lastc = c;
544 if (blen == 0) {
545 /* Real EOF, or only current buffer end? */
546 if (mtcap->mtca_len == 0)
547 c = EOF;
548 else
549 break;
550 } else
551 c = (uc_i)*buf++;
552 --blen;
554 if (c == '\0') {
555 mtc |= _MT_C_HASNUL;
556 if (!(mtc & _MT_C_ISTXTCOK)) {
557 mtc |= _MT_C_SUGGEST_DONE;
558 break;
560 continue;
562 if (c == '\n' || c == EOF) {
563 mtc &= ~_MT_C__1STLINE;
564 if (curlen >= MIME_LINELEN_LIMIT)
565 mtc |= _MT_C_LONGLINES;
566 if (c == EOF) {
567 break;
569 f_p = f_buf;
570 curlen = -1;
571 continue;
573 /* A bit hairy is handling of \r=\x0D=CR.
574 * RFC 2045, 6.7:
575 * Control characters other than TAB, or CR and LF as parts of CRLF
576 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
577 * we cannot peek the next character. Thus right here, inspect the last
578 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
579 /*else*/ if (lastc == '\r')
580 mtc |= _MT_C_CTRLCHAR;
582 /* Control character? XXX this is all ASCII here */
583 if (c < 0x20 || c == 0x7F) {
584 /* RFC 2045, 6.7, as above ... */
585 if (c != '\t' && c != '\r')
586 mtc |= _MT_C_CTRLCHAR;
587 /* If there is a escape sequence in backslash notation defined for
588 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
589 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
590 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
591 * \e=\x1B=ESC */
592 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
593 continue;
594 mtc |= _MT_C_HASNUL; /* Force base64 */
595 if (!(mtc & _MT_C_ISTXTCOK)) {
596 mtc |= _MT_C_SUGGEST_DONE;
597 break;
599 } else if ((ui8_t)c & 0x80) {
600 mtc |= _MT_C_HIGHBIT;
601 /* TODO count chars with HIGHBIT? libmagic?
602 * TODO try encode part - base64 if bails? */
603 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
604 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
605 break;
607 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
608 *f_p++ = (char)c;
609 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
610 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
611 !memcmp(f_buf, F_, F_SIZEOF)){
612 mtc |= _MT_C_FROM_;
613 if (mtc & _MT_C__1STLINE)
614 mtc |= _MT_C_FROM_1STLINE;
618 if (c == EOF && lastc != '\n')
619 mtc |= _MT_C_NOTERMNL;
621 mtcap->mtca_curlen = curlen;
622 mtcap->mtca_lastc = lastc;
623 mtcap->mtca_c = c;
624 mtcap->mtca_mtc = mtc;
625 NYD2_LEAVE;
626 return mtc;
627 #undef F_
628 #undef F_SIZEOF
631 static enum mimecontent
632 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp)
634 struct str in = {NULL, 0}, outrest, inrest, dec;
635 struct mt_class_arg mtca;
636 bool_t did_inrest;
637 enum mime_type_class mtc;
638 int lc, c;
639 size_t cnt, lsz;
640 FILE *ibuf;
641 off_t start_off;
642 enum mimecontent mc;
643 NYD2_ENTER;
645 assert(mpp->m_mime_enc != MIMEE_BIN);
647 outrest = inrest = dec = in;
648 mc = MIME_UNKNOWN;
649 n_UNINIT(mtc, 0);
651 /* TODO v15-compat Note we actually bypass our usual file handling by
652 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
653 * TODO all of this, and until then doing it like this is the only option
654 * TODO to integrate nicely into whoever calls us */
655 start_off = ftell(mb.mb_itf);
656 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
657 jos_leave:
658 fseek(mb.mb_itf, start_off, SEEK_SET);
659 goto jleave;
661 cnt = mpp->m_size;
663 /* Skip part headers */
664 for (lc = '\0'; cnt > 0; lc = c, --cnt)
665 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
666 break;
667 if (cnt == 0 || ferror(ibuf))
668 goto jos_leave;
670 /* So now let's inspect the part content, decoding content-transfer-encoding
671 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
672 _mt_classify_init(&mtca, _MT_C_ISTXT);
674 for (lsz = 0;;) {
675 bool_t dobuf;
677 c = (--cnt == 0) ? EOF : getc(ibuf);
678 if ((dobuf = (c == '\n'))) {
679 /* Ignore empty lines */
680 if (lsz == 0)
681 continue;
682 } else if ((dobuf = (c == EOF))) {
683 if (lsz == 0 && outrest.l == 0)
684 break;
687 if (in.l + 1 >= lsz)
688 in.s = srealloc(in.s, lsz += LINESIZE);
689 if (c != EOF)
690 in.s[in.l++] = (char)c;
691 if (!dobuf)
692 continue;
694 jdobuf:
695 switch (mpp->m_mime_enc) {
696 case MIMEE_B64:
697 if (!b64_decode_text(&dec, &in, &outrest,
698 (did_inrest ? NULL : &inrest))) {
699 mtca.mtca_mtc = _MT_C_HASNUL;
700 goto jstopit; /* break;break; */
702 break;
703 case MIMEE_QP:
704 /* Drin */
705 if (!qp_decode_text(&dec, &in, &outrest, &inrest)) {
706 mtca.mtca_mtc = _MT_C_HASNUL;
707 goto jstopit; /* break;break; */
709 if (dec.l == 0 && c != EOF) {
710 in.l = 0;
711 continue;
713 break;
714 default:
715 /* Temporarily switch those two buffers.. */
716 dec = in;
717 in.s = NULL;
718 in.l = 0;
719 break;
722 mtca.mtca_buf = dec.s;
723 mtca.mtca_len = (ssize_t)dec.l;
724 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
725 mtc = _MT_C_HASNUL;
726 break;
729 if (c == EOF)
730 break;
731 /* ..and restore switched */
732 if (in.s == NULL) {
733 in = dec;
734 dec.s = NULL;
736 in.l = dec.l = 0;
739 if ((in.l = inrest.l) > 0) {
740 in.s = inrest.s;
741 did_inrest = TRU1;
742 goto jdobuf;
744 if (outrest.l > 0)
745 goto jdobuf;
746 jstopit:
747 if (in.s != NULL)
748 free(in.s);
749 if (dec.s != NULL)
750 free(dec.s);
751 if (outrest.s != NULL)
752 free(outrest.s);
753 if (inrest.s != NULL)
754 free(inrest.s);
756 fseek(mb.mb_itf, start_off, SEEK_SET);
758 if (!(mtc & (_MT_C_HASNUL | _MT_C_CTRLCHAR))) {
759 mc = MIME_TEXT_PLAIN;
760 if (mce & MIMECE_ALL_OVWR)
761 mpp->m_ct_type_plain = "text/plain";
762 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
763 mpp->m_ct_type_usr_ovwr = "text/plain";
765 jleave:
766 NYD2_LEAVE;
767 return mc;
770 static enum mime_handler_flags
771 _mt_pipe_check(struct mime_handler *mhp)
773 enum mime_handler_flags rv_orig, rv;
774 char const *cp;
775 NYD2_ENTER;
777 rv_orig = rv = mhp->mh_flags;
779 /* Do we have any handler for this part? */
780 if (*(cp = mhp->mh_shell_cmd) == '\0')
781 goto jleave;
782 else if (*cp++ != '@') {
783 rv |= MIME_HDL_CMD;
784 goto jleave;
785 } else if (*cp == '\0') {
786 rv |= MIME_HDL_TEXT;
787 goto jleave;
790 jnextc:
791 switch (*cp) {
792 case '*': rv |= MIME_HDL_ALWAYS; ++cp; goto jnextc;
793 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
794 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
795 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
796 case '+':
797 if (rv & MIME_HDL_TMPF)
798 rv |= MIME_HDL_TMPF_UNLINK;
799 rv |= MIME_HDL_TMPF;
800 ++cp;
801 goto jnextc;
802 case '=':
803 rv |= MIME_HDL_TMPF_FILL;
804 ++cp;
805 goto jnextc;
806 case '@':
807 ++cp;
808 /* FALLTHRU */
809 default:
810 break;
812 mhp->mh_shell_cmd = cp;
814 /* Implications */
815 if (rv & MIME_HDL_TMPF_FILL)
816 rv |= MIME_HDL_TMPF;
818 /* Exceptions */
819 if (rv & MIME_HDL_ISQUOTE) {
820 if (rv & MIME_HDL_NOQUOTE)
821 goto jerr;
823 /* Cannot fetch data back from asynchronous process */
824 if (rv & MIME_HDL_ASYNC)
825 goto jerr;
827 /* TODO Can't use a "needsterminal" program for quoting */
828 if (rv & MIME_HDL_NEEDSTERM)
829 goto jerr;
832 if (rv & MIME_HDL_NEEDSTERM) {
833 if (rv & MIME_HDL_ASYNC) {
834 n_err(_("MIME type handlers: can't use needsterminal and "
835 "x-nail-async together\n"));
836 goto jerr;
839 /* needsterminal needs a terminal */
840 if (!(options & OPT_INTERACTIVE))
841 goto jerr;
844 if (!(rv & MIME_HDL_ALWAYS) && !(pstate & PS_MSGLIST_DIRECT)) {
845 /* Viewing multiple messages in one go, don't block system */
846 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
847 _("[-- Directly address message only for display --]\n")));
848 rv |= MIME_HDL_MSG;
849 goto jleave;
852 rv |= MIME_HDL_CMD;
853 jleave:
854 mhp->mh_flags = rv;
855 NYD2_LEAVE;
856 return rv;
857 jerr:
858 rv = rv_orig;
859 goto jleave;
862 FL int
863 c_mimetype(void *v)
865 char **argv = v;
866 struct mtnode *mtnp;
867 NYD_ENTER;
869 if (!_mt_is_init)
870 _mt_init();
872 if (*argv == NULL) {
873 FILE *fp;
874 size_t l;
876 if (_mt_list == NULL) {
877 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
878 goto jleave;
881 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
882 NULL) {
883 n_perr(_("tmpfile"), 0);
884 v = NULL;
885 goto jleave;
888 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
889 char const *tmark, *typ;
891 switch (mtnp->mt_flags & __MT_MARKMASK) {
892 case _MT_PLAIN: tmark = "/t"; break;
893 case _MT_SOUP_h: tmark = "/h"; break;
894 case _MT_SOUP_H: tmark = "/H"; break;
895 default: tmark = " "; break;
897 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
898 ? n_empty : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
900 fprintf(fp, "%c%s %s%.*s %s\n",
901 (mtnp->mt_flags & _MT_USR ? 'U'
902 : (mtnp->mt_flags & _MT_SYS ? 'S'
903 : (mtnp->mt_flags & _MT_FSPEC ? 'F'
904 : (mtnp->mt_flags & _MT_CMD ? 'C' : 'B')))),
905 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
906 mtnp->mt_line + mtnp->mt_mtlen);
909 page_or_print(fp, l);
910 Fclose(fp);
911 } else {
912 for (; *argv != NULL; ++argv) {
913 mtnp = _mt_create(TRU1, _MT_CMD, *argv, strlen(*argv));
914 if (mtnp != NULL) {
915 mtnp->mt_next = _mt_list;
916 _mt_list = mtnp;
917 } else
918 v = NULL;
921 jleave:
922 NYD_LEAVE;
923 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
926 FL int
927 c_unmimetype(void *v)
929 char **argv = v;
930 struct mtnode *lnp, *mtnp;
931 bool_t match;
932 NYD_ENTER;
934 /* Need to load that first as necessary */
935 if (!_mt_is_init)
936 _mt_init();
938 for (; *argv != NULL; ++argv) {
939 if (!asccasecmp(*argv, "reset")) {
940 _mt_is_init = FAL0;
941 goto jdelall;
944 if (argv[0][0] == '*' && argv[0][1] == '\0') {
945 jdelall:
946 while ((mtnp = _mt_list) != NULL) {
947 _mt_list = mtnp->mt_next;
948 free(mtnp);
950 continue;
953 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
954 char const *typ;
955 char *val;
956 size_t i;
958 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
959 typ = n_empty;
960 i = 0;
961 } else {
962 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
963 i = strlen(typ);
966 val = ac_alloc(i + mtnp->mt_mtlen +1);
967 memcpy(val, typ, i);
968 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
969 val[i += mtnp->mt_mtlen] = '\0';
970 i = asccasecmp(val, *argv);
971 ac_free(val);
973 if (!i) {
974 struct mtnode *nnp = mtnp->mt_next;
975 if (lnp == NULL)
976 _mt_list = nnp;
977 else
978 lnp->mt_next = nnp;
979 free(mtnp);
980 mtnp = nnp;
981 match = TRU1;
982 } else
983 lnp = mtnp, mtnp = mtnp->mt_next;
985 if (!match) {
986 if (!(pstate & PS_ROBOT) || (options & OPT_D_V))
987 n_err(_("No such MIME type: %s\n"), *argv);
988 v = NULL;
991 NYD_LEAVE;
992 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
995 FL bool_t
996 mime_type_check_mtname(char const *name)
998 struct mtlookup mtl;
999 bool_t rv;
1000 NYD_ENTER;
1002 rv = (_mt_by_mtname(&mtl, name) != NULL);
1003 NYD_LEAVE;
1004 return rv;
1007 FL char *
1008 mime_type_classify_filename(char const *name)
1010 struct mtlookup mtl;
1011 NYD_ENTER;
1013 _mt_by_filename(&mtl, name, TRU1);
1014 NYD_LEAVE;
1015 return mtl.mtl_result;
1018 FL enum conversion
1019 mime_type_classify_file(FILE *fp, char const **contenttype,
1020 char const **charset, int *do_iconv)
1022 /* TODO classify once only PLEASE PLEASE PLEASE */
1023 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1024 * TODO binary according to RFC 2046, 5.2.1
1025 * TODO The handling of which is a hack */
1026 bool_t rfc822;
1027 enum mime_type_class mtc;
1028 enum mime_enc menc;
1029 off_t fpsz;
1030 enum conversion c;
1031 NYD_ENTER;
1033 assert(ftell(fp) == 0x0l);
1035 *do_iconv = 0;
1037 if (*contenttype == NULL) {
1038 mtc = _MT_C_NCTT;
1039 rfc822 = FAL0;
1040 } else if (!ascncasecmp(*contenttype, "text/", 5)) {
1041 mtc = ok_blook(mime_allow_text_controls)
1042 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1043 rfc822 = FAL0;
1044 } else if (!asccasecmp(*contenttype, "message/rfc822")) {
1045 mtc = _MT_C_ISTXT;
1046 rfc822 = TRU1;
1047 } else {
1048 mtc = _MT_C_CLEAN;
1049 rfc822 = FAL0;
1052 menc = mime_enc_target();
1054 if ((fpsz = fsize(fp)) == 0)
1055 goto j7bit;
1056 else {
1057 char buf[BUFFER_SIZE];
1058 struct mt_class_arg mtca;
1060 _mt_classify_init(&mtca, mtc);
1061 for (;;) {
1062 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1063 mtca.mtca_buf = buf;
1064 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1065 break;
1066 if (mtca.mtca_len == 0)
1067 break;
1069 /* TODO ferror(fp) ! */
1070 rewind(fp);
1073 if (mtc & _MT_C_HASNUL) {
1074 menc = MIMEE_B64;
1075 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1076 * on request; else enforce what file(1)/libmagic(3) would suggest */
1077 if (mtc & _MT_C_ISTXTCOK)
1078 goto jcharset;
1079 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1080 *contenttype = "application/octet-stream";
1081 if (*charset == NULL)
1082 *charset = "binary";
1083 goto jleave;
1086 if (mtc &
1087 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1088 if (menc != MIMEE_B64)
1089 menc = MIMEE_QP;
1090 goto jstepi;
1092 if (mtc & _MT_C_HIGHBIT) {
1093 jstepi:
1094 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1095 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1096 } else
1097 j7bit:
1098 menc = MIMEE_7B;
1099 if (mtc & _MT_C_NCTT)
1100 *contenttype = "text/plain";
1102 /* Not an attachment with specified charset? */
1103 jcharset:
1104 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1105 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1106 : charset_get_7bit();
1107 jleave:
1108 /* TODO mime_type_file_classify() shouldn't return conversion */
1109 if (rfc822) {
1110 if (mtc & _MT_C_FROM_1STLINE) {
1111 n_err(_("Pre-v15 %s cannot handle message/rfc822 that "
1112 "indeed is a RFC 4155 MBOX!\n"
1113 " Forcing a content-type of application/mbox!\n"),
1114 uagent);
1115 *contenttype = "application/mbox";
1116 goto jnorfc822;
1118 c = (menc == MIMEE_7B ? CONV_7BIT
1119 : (menc == MIMEE_8B ? CONV_8BIT
1120 : CONV_NONE));
1121 } else
1122 jnorfc822:
1123 c = (menc == MIMEE_7B ? CONV_7BIT
1124 : (menc == MIMEE_8B ? CONV_8BIT
1125 : (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1126 NYD_LEAVE;
1127 return c;
1130 FL enum mimecontent
1131 mime_type_classify_part(struct mimepart *mpp) /* FIXME charset=binary ??? */
1133 struct mtlookup mtl;
1134 enum mimecontent mc;
1135 char const *ct;
1136 union {char const *cp; ui32_t f;} mce;
1137 bool_t is_os;
1138 NYD_ENTER;
1140 mc = MIME_UNKNOWN;
1141 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1142 ct = n_empty;
1144 if ((mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
1145 char *eptr;
1146 ul_i ul;
1148 ul = strtoul(mce.cp, &eptr, 0); /* XXX strtol */
1149 if (*mce.cp == '\0')
1150 is_os = FAL0;
1151 else if (*eptr != '\0' || (ui64_t)ul >= UI32_MAX) {
1152 n_err(_("Can't parse *mime-counter-evidence* value: %s\n"), mce.cp);
1153 is_os = FAL0;
1154 } else {
1155 mce.f = (ui32_t)ul | MIMECE_SET;
1156 is_os = !asccasecmp(ct, "application/octet-stream");
1158 if (mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))) {
1159 if (_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL) {
1160 if (is_os)
1161 goto jos_content_check;
1162 } else if (is_os || asccasecmp(ct, mtl.mtl_result)) {
1163 if (mce.f & MIMECE_ALL_OVWR)
1164 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1165 if (mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1166 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1170 } else
1171 is_os = FAL0;
1173 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1174 mc = MIME_TEXT;
1175 else if (is_asccaseprefix(ct, "text/")) {
1176 ct += sizeof("text/") -1;
1177 if (!asccasecmp(ct, "plain"))
1178 mc = MIME_TEXT_PLAIN;
1179 else if (!asccasecmp(ct, "html"))
1180 mc = MIME_TEXT_HTML;
1181 else
1182 mc = MIME_TEXT;
1183 } else if (is_asccaseprefix(ct, "message/")) {
1184 ct += sizeof("message/") -1;
1185 if (!asccasecmp(ct, "rfc822"))
1186 mc = MIME_822;
1187 else
1188 mc = MIME_MESSAGE;
1189 } else if (!ascncasecmp(ct, "multipart/", 10)) {
1190 ct += sizeof("multipart/") -1;
1191 if (!asccasecmp(ct, "alternative"))
1192 mc = MIME_ALTERNATIVE;
1193 else if (!asccasecmp(ct, "related"))
1194 mc = MIME_RELATED;
1195 else if (!asccasecmp(ct, "digest"))
1196 mc = MIME_DIGEST;
1197 else
1198 mc = MIME_MULTI;
1199 } else if (is_asccaseprefix(ct, "application/")) {
1200 if (is_os)
1201 goto jos_content_check;
1202 ct += sizeof("application/") -1;
1203 if (!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1204 mc = MIME_PKCS7;
1206 jleave:
1207 NYD_LEAVE;
1208 return mc;
1210 jos_content_check:
1211 if ((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1212 mpp->m_charset != NULL && asccasecmp(mpp->m_charset, "binary"))
1213 mc = _mt_classify_os_part(mce.f, mpp);
1214 goto jleave;
1217 FL enum mime_handler_flags
1218 mime_type_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1219 enum sendaction action)
1221 #define __S "pipe-"
1222 #define __L (sizeof(__S) -1)
1223 struct mtlookup mtl;
1224 char *buf, *cp;
1225 enum mime_handler_flags rv;
1226 char const *es, *cs, *ccp;
1227 size_t el, cl, l;
1228 NYD_ENTER;
1230 memset(mhp, 0, sizeof *mhp);
1231 buf = NULL;
1233 rv = MIME_HDL_NULL;
1234 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1235 rv |= MIME_HDL_ISQUOTE;
1236 else if (action != SEND_TODISP && action != SEND_TODISP_ALL)
1237 goto jleave;
1239 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1240 *++es != '\0') ? strlen(es) : 0;
1241 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1242 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1243 if ((l = n_MAX(el, cl)) == 0) {
1244 /* TODO this should be done during parse time! */
1245 goto jleave;
1248 /* We don't pass the flags around, so ensure carrier is up-to-date */
1249 mhp->mh_flags = rv;
1251 buf = ac_alloc(__L + l +1);
1252 memcpy(buf, __S, __L);
1254 /* File-extension handlers take precedence.
1255 * Yes, we really "fail" here for file extensions which clash MIME types */
1256 if (el > 0) {
1257 memcpy(buf + __L, es, el +1);
1258 for (cp = buf + __L; *cp != '\0'; ++cp)
1259 *cp = lowerconv(*cp);
1261 if ((mhp->mh_shell_cmd = ccp = vok_vlook(buf)) != NULL) {
1262 rv = _mt_pipe_check(mhp);
1263 goto jleave;
1267 /* Then MIME Content-Type:, if any */
1268 if (cl == 0)
1269 goto jleave;
1271 memcpy(buf + __L, cs, cl +1);
1272 for (cp = buf + __L; *cp != '\0'; ++cp)
1273 *cp = lowerconv(*cp);
1275 if ((mhp->mh_shell_cmd = vok_vlook(buf)) != NULL) {
1276 rv = _mt_pipe_check(mhp);
1277 goto jleave;
1280 if (_mt_by_mtname(&mtl, cs) != NULL)
1281 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
1282 #ifndef HAVE_FILTER_HTML_TAGSOUP
1283 case _MT_SOUP_H:
1284 break;
1285 #endif
1286 case _MT_SOUP_h:
1287 #ifdef HAVE_FILTER_HTML_TAGSOUP
1288 case _MT_SOUP_H:
1289 mhp->mh_ptf = &htmlflt_process_main;
1290 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1291 n_UNCONST(_("Builtin HTML tagsoup filter")));
1292 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1293 goto jleave;
1294 #endif
1295 /* FALLTHRU */
1296 case _MT_PLAIN:
1297 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
1298 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1299 goto jleave;
1300 default:
1301 break;
1304 jleave:
1305 if (buf != NULL)
1306 ac_free(buf);
1308 mhp->mh_flags = rv;
1309 if ((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL)
1310 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1311 _("[-- No MIME handler installed or none applicable --]")));
1312 NYD_LEAVE;
1313 return rv;
1314 #undef __L
1315 #undef __S
1318 /* s-it-mode */