`mimetype': split LOADED in CMD and FSPEC to make that clearer
[s-mailx.git] / mime_types.c
blob98a30ea29f723b91b056d5a2d963747b5d43ceaf
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_SUGGEST_DONE = 1<<16 /* Inspector suggests to stop further parse */
64 struct mtbltin {
65 ui32_t mtb_flags;
66 ui32_t mtb_mtlen;
67 char const *mtb_line;
70 struct mtnode {
71 struct mtnode *mt_next;
72 ui32_t mt_flags;
73 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
74 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
75 * that could already store character data here */
76 char const *mt_line;
79 struct mtlookup {
80 char const *mtl_name;
81 size_t mtl_nlen;
82 struct mtnode const *mtl_node;
83 char *mtl_result; /* If requested, salloc()ed MIME type */
86 struct mt_class_arg {
87 char const *mtca_buf;
88 size_t mtca_len;
89 ssize_t mtca_curlen;
90 char mtca_lastc;
91 char mtca_c;
92 enum mime_type_class mtca_mtc;
95 static struct mtbltin const _mt_bltin[] = {
96 #include "mime_types.h"
99 static char const _mt_typnames[][16] = {
100 "application/", "audio/", "image/",
101 "message/", "multipart/", "text/",
102 "video/"
104 n_CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
105 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
106 _MT_VIDEO == 6);
108 /* */
109 static bool_t _mt_is_init;
110 static struct mtnode *_mt_list;
112 /* Initialize MIME type list in order */
113 static void _mt_init(void);
114 static bool_t __mt_load_file(ui32_t orflags,
115 char const *file, char **line, size_t *linesize);
117 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
118 * for `mimetype' */
119 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
120 char const *line, size_t len);
122 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
123 * if with_result >mtl_result will be created upon success for the former */
124 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
125 char const *name, bool_t with_result);
126 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
127 char const *mtname);
129 /* In-depth inspection of raw content: call _round() repeatedly, last time with
130 * a 0 length buffer, finally check .mtca_mtc for result.
131 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
132 * as the resulting classification is unambiguous */
133 SINLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
134 enum mime_type_class initval);
135 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
137 /* We need an in-depth inspection of an application/octet-stream part */
138 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp);
140 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
141 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
142 * isn't changed if mhp doesn't apply */
143 static enum mime_handler_flags _mt_pipe_check(struct mime_handler *mhp);
145 static void
146 _mt_init(void)
148 struct mtnode *tail;
149 char c, *line; /* TODO line pool (below) */
150 size_t linesize;
151 ui32_t i, j;
152 char const *srcs_arr[10], *ccp, **srcs;
153 NYD_ENTER;
155 /*if (_mt_is_init)
156 * goto jleave;*/
158 /* Always load our builtins */
159 for (tail = NULL, i = 0; i < n_NELEM(_mt_bltin); ++i) {
160 struct mtbltin const *mtbp = _mt_bltin + i;
161 struct mtnode *mtnp = smalloc(sizeof *mtnp);
163 if (tail != NULL)
164 tail->mt_next = mtnp;
165 else
166 _mt_list = mtnp;
167 tail = mtnp;
168 mtnp->mt_next = NULL;
169 mtnp->mt_flags = mtbp->mtb_flags;
170 mtnp->mt_mtlen = mtbp->mtb_mtlen;
171 mtnp->mt_line = mtbp->mtb_line;
174 /* Decide which files sources have to be loaded */
175 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
176 ccp = "US";
177 else if (*ccp == '\0')
178 goto jleave;
180 srcs = srcs_arr + 2;
181 srcs[-1] = srcs[-2] = NULL;
183 if (strchr(ccp, '=') != NULL) {
184 line = savestr(ccp);
186 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
187 switch ((c = *ccp)) {
188 case 'S': case 's':
189 srcs_arr[1] = MIME_TYPES_SYS;
190 if (0) {
191 /* FALLTHRU */
192 case 'U': case 'u':
193 srcs_arr[0] = MIME_TYPES_USR;
195 if (ccp[1] != '\0')
196 goto jecontent;
197 break;
198 case 'F': case 'f':
199 if (*++ccp == '=' && *++ccp != '\0') {
200 if (PTR2SIZE(srcs - srcs_arr) < n_NELEM(srcs_arr))
201 *srcs++ = ccp;
202 else
203 n_err(_("*mimetypes-load-control*: too many sources, "
204 "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
205 continue;
207 /* FALLTHRU */
208 default:
209 goto jecontent;
212 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
213 switch (c) {
214 case 'S': case 's': srcs_arr[1] = MIME_TYPES_SYS; break;
215 case 'U': case 'u': srcs_arr[0] = MIME_TYPES_USR; break;
216 default:
217 jecontent:
218 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), 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
230 : (j == 1 ? _MT_SYS : _MT_FSPEC)), *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 n_shexp_quote_cp(*srcs, FAL0));
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;
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 ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
395 goto jnull_leave;
396 /* We need a period TODO we should support names like README etc. */
397 for (i = nlen; name[--i] != '.';)
398 if (i == 0 || name[i] == '/') /* XXX no magics */
399 goto jnull_leave;
400 /* While here, basename() it */
401 while (i > 0 && name[i - 1] != '/')
402 --i;
403 name += i;
404 nlen -= i;
405 mtlp->mtl_name = name;
406 mtlp->mtl_nlen = nlen;
408 if (!_mt_is_init)
409 _mt_init();
411 /* ..all the MIME types */
412 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
413 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
414 cp = ext;
415 while (whitechar(*cp))
416 ++cp;
417 ext = cp;
418 while (!whitechar(*cp) && *cp != '\0')
419 ++cp;
421 if ((i = PTR2SIZE(cp - ext)) == 0)
422 break;
423 /* Don't allow neither of ".txt" or "txt" to match "txt" */
424 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
425 ascncasecmp(name + j, ext, i))
426 continue;
428 /* Found it */
429 mtlp->mtl_node = mtnp;
431 if (!with_result)
432 goto jleave;
434 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
435 name = n_empty;
436 j = 0;
437 } else {
438 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
439 j = strlen(name);
441 i = mtnp->mt_mtlen;
442 mtlp->mtl_result = salloc(i + j +1);
443 if (j > 0)
444 memcpy(mtlp->mtl_result, name, j);
445 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
446 mtlp->mtl_result[j += i] = '\0';
447 goto jleave;
449 jnull_leave:
450 mtlp = NULL;
451 jleave:
452 NYD2_LEAVE;
453 return mtlp;
456 static struct mtlookup *
457 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
459 struct mtnode *mtnp;
460 size_t nlen, i, j;
461 char const *cp;
462 NYD2_ENTER;
464 memset(mtlp, 0, sizeof *mtlp);
466 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
467 goto jnull_leave;
469 if (!_mt_is_init)
470 _mt_init();
472 /* ..all the MIME types */
473 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
474 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
475 cp = n_empty;
476 j = 0;
477 } else {
478 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
479 j = strlen(cp);
481 i = mtnp->mt_mtlen;
483 if (i + j == mtlp->mtl_nlen) {
484 char *xmt = ac_alloc(i + j +1);
485 if (j > 0)
486 memcpy(xmt, cp, j);
487 memcpy(xmt + j, mtnp->mt_line, i);
488 xmt[j += i] = '\0';
489 i = asccasecmp(mtname, xmt);
490 ac_free(xmt);
492 if (!i) {
493 /* Found it */
494 mtlp->mtl_node = mtnp;
495 goto jleave;
499 jnull_leave:
500 mtlp = NULL;
501 jleave:
502 NYD2_LEAVE;
503 return mtlp;
506 SINLINE struct mt_class_arg *
507 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
509 NYD2_ENTER;
510 memset(mtcap, 0, sizeof *mtcap);
511 mtcap->mtca_lastc = mtcap->mtca_c = EOF;
512 mtcap->mtca_mtc = initval;
513 NYD2_LEAVE;
514 return mtcap;
517 static enum mime_type_class
518 _mt_classify_round(struct mt_class_arg *mtcap)
520 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
521 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
522 * TODO and report that state to the outer world */
523 #define F_ "From "
524 #define F_SIZEOF (sizeof(F_) -1)
525 char f_buf[F_SIZEOF], *f_p = f_buf;
526 char const *buf;
527 size_t blen;
528 ssize_t curlen;
529 int c, lastc;
530 enum mime_type_class mtc;
531 NYD2_ENTER;
533 buf = mtcap->mtca_buf;
534 blen = mtcap->mtca_len;
535 curlen = mtcap->mtca_curlen;
536 c = mtcap->mtca_c;
537 lastc = mtcap->mtca_lastc;
538 mtc = mtcap->mtca_mtc;
540 for (;; ++curlen) {
541 lastc = c;
542 if (blen == 0) {
543 /* Real EOF, or only current buffer end? */
544 if (mtcap->mtca_len == 0)
545 c = EOF;
546 else
547 break;
548 } else
549 c = (uc_i)*buf++;
550 --blen;
552 if (c == '\0') {
553 mtc |= _MT_C_HASNUL;
554 if (!(mtc & _MT_C_ISTXTCOK)) {
555 mtc |= _MT_C_SUGGEST_DONE;
556 break;
558 continue;
560 if (c == '\n' || c == EOF) {
561 if (curlen >= MIME_LINELEN_LIMIT)
562 mtc |= _MT_C_LONGLINES;
563 if (c == EOF) {
564 break;
566 f_p = f_buf;
567 curlen = -1;
568 continue;
570 /* A bit hairy is handling of \r=\x0D=CR.
571 * RFC 2045, 6.7:
572 * Control characters other than TAB, or CR and LF as parts of CRLF
573 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
574 * we cannot peek the next character. Thus right here, inspect the last
575 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
576 /*else*/ if (lastc == '\r')
577 mtc |= _MT_C_CTRLCHAR;
579 /* Control character? XXX this is all ASCII here */
580 if (c < 0x20 || c == 0x7F) {
581 /* RFC 2045, 6.7, as above ... */
582 if (c != '\t' && c != '\r')
583 mtc |= _MT_C_CTRLCHAR;
584 /* If there is a escape sequence in backslash notation defined for
585 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
586 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
587 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
588 * \e=\x1B=ESC */
589 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
590 continue;
591 mtc |= _MT_C_HASNUL; /* Force base64 */
592 if (!(mtc & _MT_C_ISTXTCOK)) {
593 mtc |= _MT_C_SUGGEST_DONE;
594 break;
596 } else if ((ui8_t)c & 0x80) {
597 mtc |= _MT_C_HIGHBIT;
598 /* TODO count chars with HIGHBIT? libmagic?
599 * TODO try encode part - base64 if bails? */
600 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
601 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
602 break;
604 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
605 *f_p++ = (char)c;
606 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
607 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
608 !memcmp(f_buf, F_, F_SIZEOF))
609 mtc |= _MT_C_FROM_;
612 if (c == EOF && lastc != '\n')
613 mtc |= _MT_C_NOTERMNL;
615 mtcap->mtca_curlen = curlen;
616 mtcap->mtca_lastc = lastc;
617 mtcap->mtca_c = c;
618 mtcap->mtca_mtc = mtc;
619 NYD2_LEAVE;
620 return mtc;
621 #undef F_
622 #undef F_SIZEOF
625 static enum mimecontent
626 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp)
628 struct str in = {NULL, 0}, outrest, inrest, dec;
629 struct mt_class_arg mtca;
630 bool_t did_inrest;
631 enum mime_type_class mtc;
632 int lc, c;
633 size_t cnt, lsz;
634 FILE *ibuf;
635 off_t start_off;
636 enum mimecontent mc;
637 NYD2_ENTER;
639 assert(mpp->m_mime_enc != MIMEE_BIN);
641 outrest = inrest = dec = in;
642 mc = MIME_UNKNOWN;
643 n_UNINIT(mtc, 0);
645 /* TODO v15-compat Note we actually bypass our usual file handling by
646 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
647 * TODO all of this, and until then doing it like this is the only option
648 * TODO to integrate nicely into whoever calls us */
649 start_off = ftell(mb.mb_itf);
650 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
651 jos_leave:
652 fseek(mb.mb_itf, start_off, SEEK_SET);
653 goto jleave;
655 cnt = mpp->m_size;
657 /* Skip part headers */
658 for (lc = '\0'; cnt > 0; lc = c, --cnt)
659 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
660 break;
661 if (cnt == 0 || ferror(ibuf))
662 goto jos_leave;
664 /* So now let's inspect the part content, decoding content-transfer-encoding
665 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
666 _mt_classify_init(&mtca, _MT_C_ISTXT);
668 for (lsz = 0;;) {
669 bool_t dobuf;
671 c = (--cnt == 0) ? EOF : getc(ibuf);
672 if ((dobuf = (c == '\n'))) {
673 /* Ignore empty lines */
674 if (lsz == 0)
675 continue;
676 } else if ((dobuf = (c == EOF))) {
677 if (lsz == 0 && outrest.l == 0)
678 break;
681 if (in.l + 1 >= lsz)
682 in.s = srealloc(in.s, lsz += LINESIZE);
683 if (c != EOF)
684 in.s[in.l++] = (char)c;
685 if (!dobuf)
686 continue;
688 jdobuf:
689 switch (mpp->m_mime_enc) {
690 case MIMEE_B64:
691 if (!b64_decode_text(&dec, &in, &outrest,
692 (did_inrest ? NULL : &inrest))) {
693 mtca.mtca_mtc = _MT_C_HASNUL;
694 goto jstopit; /* break;break; */
696 break;
697 case MIMEE_QP:
698 /* Drin */
699 if (!qp_decode_text(&dec, &in, &outrest, &inrest)) {
700 mtca.mtca_mtc = _MT_C_HASNUL;
701 goto jstopit; /* break;break; */
703 if (dec.l == 0 && c != EOF) {
704 in.l = 0;
705 continue;
707 break;
708 default:
709 /* Temporarily switch those two buffers.. */
710 dec = in;
711 in.s = NULL;
712 in.l = 0;
713 break;
716 mtca.mtca_buf = dec.s;
717 mtca.mtca_len = (ssize_t)dec.l;
718 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
719 mtc = _MT_C_HASNUL;
720 break;
723 if (c == EOF)
724 break;
725 /* ..and restore switched */
726 if (in.s == NULL) {
727 in = dec;
728 dec.s = NULL;
730 in.l = dec.l = 0;
733 if ((in.l = inrest.l) > 0) {
734 in.s = inrest.s;
735 did_inrest = TRU1;
736 goto jdobuf;
738 if (outrest.l > 0)
739 goto jdobuf;
740 jstopit:
741 if (in.s != NULL)
742 free(in.s);
743 if (dec.s != NULL)
744 free(dec.s);
745 if (outrest.s != NULL)
746 free(outrest.s);
747 if (inrest.s != NULL)
748 free(inrest.s);
750 fseek(mb.mb_itf, start_off, SEEK_SET);
752 if (!(mtc & (_MT_C_HASNUL | _MT_C_CTRLCHAR))) {
753 mc = MIME_TEXT_PLAIN;
754 if (mce & MIMECE_ALL_OVWR)
755 mpp->m_ct_type_plain = "text/plain";
756 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
757 mpp->m_ct_type_usr_ovwr = "text/plain";
759 jleave:
760 NYD2_LEAVE;
761 return mc;
764 static enum mime_handler_flags
765 _mt_pipe_check(struct mime_handler *mhp)
767 enum mime_handler_flags rv_orig, rv;
768 char const *cp;
769 NYD2_ENTER;
771 rv_orig = rv = mhp->mh_flags;
773 /* Do we have any handler for this part? */
774 if (*(cp = mhp->mh_shell_cmd) == '\0')
775 goto jleave;
776 else if (*cp++ != '@') {
777 rv |= MIME_HDL_CMD;
778 goto jleave;
779 } else if (*cp == '\0') {
780 rv |= MIME_HDL_TEXT;
781 goto jleave;
784 jnextc:
785 switch (*cp) {
786 case '*': rv |= MIME_HDL_ALWAYS; ++cp; goto jnextc;
787 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
788 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
789 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
790 case '+':
791 if (rv & MIME_HDL_TMPF)
792 rv |= MIME_HDL_TMPF_UNLINK;
793 rv |= MIME_HDL_TMPF;
794 ++cp;
795 goto jnextc;
796 case '=':
797 rv |= MIME_HDL_TMPF_FILL;
798 ++cp;
799 goto jnextc;
800 case '@':
801 ++cp;
802 /* FALLTHRU */
803 default:
804 break;
806 mhp->mh_shell_cmd = cp;
808 /* Implications */
809 if (rv & MIME_HDL_TMPF_FILL)
810 rv |= MIME_HDL_TMPF;
812 /* Exceptions */
813 if (rv & MIME_HDL_ISQUOTE) {
814 if (rv & MIME_HDL_NOQUOTE)
815 goto jerr;
817 /* Cannot fetch data back from asynchronous process */
818 if (rv & MIME_HDL_ASYNC)
819 goto jerr;
821 /* TODO Can't use a "needsterminal" program for quoting */
822 if (rv & MIME_HDL_NEEDSTERM)
823 goto jerr;
826 if (rv & MIME_HDL_NEEDSTERM) {
827 if (rv & MIME_HDL_ASYNC) {
828 n_err(_("MIME type handlers: can't use needsterminal and "
829 "x-nail-async together\n"));
830 goto jerr;
833 /* needsterminal needs a terminal */
834 if (!(options & OPT_INTERACTIVE))
835 goto jerr;
838 if (!(rv & MIME_HDL_ALWAYS) && !(pstate & PS_MSGLIST_DIRECT)) {
839 /* Viewing multiple messages in one go, don't block system */
840 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
841 _("[-- Directly address message only for display --]\n")));
842 rv |= MIME_HDL_MSG;
843 goto jleave;
846 rv |= MIME_HDL_CMD;
847 jleave:
848 mhp->mh_flags = rv;
849 NYD2_LEAVE;
850 return rv;
851 jerr:
852 rv = rv_orig;
853 goto jleave;
856 FL int
857 c_mimetype(void *v)
859 char **argv = v;
860 struct mtnode *mtnp;
861 NYD_ENTER;
863 if (!_mt_is_init)
864 _mt_init();
866 if (*argv == NULL) {
867 FILE *fp;
868 size_t l;
870 if (_mt_list == NULL) {
871 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
872 goto jleave;
875 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
876 NULL) {
877 n_perr(_("tmpfile"), 0);
878 v = NULL;
879 goto jleave;
882 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
883 char const *tmark, *typ;
885 switch (mtnp->mt_flags & __MT_MARKMASK) {
886 case _MT_PLAIN: tmark = "/t"; break;
887 case _MT_SOUP_h: tmark = "/h"; break;
888 case _MT_SOUP_H: tmark = "/H"; break;
889 default: tmark = " "; break;
891 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
892 ? n_empty : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
894 fprintf(fp, "%c%s %s%.*s %s\n",
895 (mtnp->mt_flags & _MT_USR ? 'U'
896 : (mtnp->mt_flags & _MT_SYS ? 'S'
897 : (mtnp->mt_flags & _MT_FSPEC ? 'F'
898 : (mtnp->mt_flags & _MT_CMD ? 'C' : 'B')))),
899 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
900 mtnp->mt_line + mtnp->mt_mtlen);
903 page_or_print(fp, l);
904 Fclose(fp);
905 } else {
906 for (; *argv != NULL; ++argv) {
907 mtnp = _mt_create(TRU1, _MT_CMD, *argv, strlen(*argv));
908 if (mtnp != NULL) {
909 mtnp->mt_next = _mt_list;
910 _mt_list = mtnp;
911 } else
912 v = NULL;
915 jleave:
916 NYD_LEAVE;
917 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
920 FL int
921 c_unmimetype(void *v)
923 char **argv = v;
924 struct mtnode *lnp, *mtnp;
925 bool_t match;
926 NYD_ENTER;
928 /* Need to load that first as necessary */
929 if (!_mt_is_init)
930 _mt_init();
932 for (; *argv != NULL; ++argv) {
933 if (!asccasecmp(*argv, "reset")) {
934 _mt_is_init = FAL0;
935 goto jdelall;
938 if (argv[0][0] == '*' && argv[0][1] == '\0') {
939 jdelall:
940 while ((mtnp = _mt_list) != NULL) {
941 _mt_list = mtnp->mt_next;
942 free(mtnp);
944 continue;
947 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
948 char const *typ;
949 char *val;
950 size_t i;
952 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
953 typ = n_empty;
954 i = 0;
955 } else {
956 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
957 i = strlen(typ);
960 val = ac_alloc(i + mtnp->mt_mtlen +1);
961 memcpy(val, typ, i);
962 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
963 val[i += mtnp->mt_mtlen] = '\0';
964 i = asccasecmp(val, *argv);
965 ac_free(val);
967 if (!i) {
968 struct mtnode *nnp = mtnp->mt_next;
969 if (lnp == NULL)
970 _mt_list = nnp;
971 else
972 lnp->mt_next = nnp;
973 free(mtnp);
974 mtnp = nnp;
975 match = TRU1;
976 } else
977 lnp = mtnp, mtnp = mtnp->mt_next;
979 if (!match) {
980 if (!(pstate & PS_ROBOT) || (options & OPT_D_V))
981 n_err(_("No such MIME type: %s\n"), *argv);
982 v = NULL;
985 NYD_LEAVE;
986 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
989 FL bool_t
990 mime_type_check_mtname(char const *name)
992 struct mtlookup mtl;
993 bool_t rv;
994 NYD_ENTER;
996 rv = (_mt_by_mtname(&mtl, name) != NULL);
997 NYD_LEAVE;
998 return rv;
1001 FL char *
1002 mime_type_classify_filename(char const *name)
1004 struct mtlookup mtl;
1005 NYD_ENTER;
1007 _mt_by_filename(&mtl, name, TRU1);
1008 NYD_LEAVE;
1009 return mtl.mtl_result;
1012 FL enum conversion
1013 mime_type_classify_file(FILE *fp, char const **contenttype,
1014 char const **charset, int *do_iconv)
1016 /* TODO classify once only PLEASE PLEASE PLEASE */
1017 enum mime_type_class mtc;
1018 enum mime_enc menc;
1019 off_t fpsz;
1020 NYD_ENTER;
1022 assert(ftell(fp) == 0x0l);
1024 *do_iconv = 0;
1026 if (*contenttype == NULL)
1027 mtc = _MT_C_NCTT;
1028 else if (!ascncasecmp(*contenttype, "text/", 5))
1029 mtc = ok_blook(mime_allow_text_controls)
1030 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1031 else
1032 mtc = _MT_C_CLEAN;
1034 menc = mime_enc_target();
1036 if ((fpsz = fsize(fp)) == 0)
1037 goto j7bit;
1038 else {
1039 char buf[BUFFER_SIZE];
1040 struct mt_class_arg mtca;
1042 _mt_classify_init(&mtca, mtc);
1043 for (;;) {
1044 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1045 mtca.mtca_buf = buf;
1046 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1047 break;
1048 if (mtca.mtca_len == 0)
1049 break;
1051 /* TODO ferror(fp) ! */
1052 rewind(fp);
1055 if (mtc & _MT_C_HASNUL) {
1056 menc = MIMEE_B64;
1057 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1058 * on request; else enforce what file(1)/libmagic(3) would suggest */
1059 if (mtc & _MT_C_ISTXTCOK)
1060 goto jcharset;
1061 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1062 *contenttype = "application/octet-stream";
1063 if (*charset == NULL)
1064 *charset = "binary";
1065 goto jleave;
1068 if (mtc &
1069 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1070 if (menc != MIMEE_B64)
1071 menc = MIMEE_QP;
1072 goto jstepi;
1074 if (mtc & _MT_C_HIGHBIT) {
1075 jstepi:
1076 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1077 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1078 } else
1079 j7bit:
1080 menc = MIMEE_7B;
1081 if (mtc & _MT_C_NCTT)
1082 *contenttype = "text/plain";
1084 /* Not an attachment with specified charset? */
1085 jcharset:
1086 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1087 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1088 : charset_get_7bit();
1089 jleave:
1090 NYD_LEAVE;
1091 /* TODO mime_type_file_classify() shouldn't return conversion */
1092 return (menc == MIMEE_7B ? CONV_7BIT :
1093 (menc == MIMEE_8B ? CONV_8BIT :
1094 (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1097 FL enum mimecontent
1098 mime_type_classify_part(struct mimepart *mpp) /* FIXME charset=binary ??? */
1100 struct mtlookup mtl;
1101 enum mimecontent mc;
1102 char const *ct;
1103 union {char const *cp; ui32_t f;} mce;
1104 bool_t is_os;
1105 NYD_ENTER;
1107 mc = MIME_UNKNOWN;
1108 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1109 ct = n_empty;
1111 if ((mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
1112 char *eptr;
1113 ul_i ul;
1115 ul = strtoul(mce.cp, &eptr, 0); /* XXX strtol */
1116 if (*mce.cp == '\0')
1117 is_os = FAL0;
1118 else if (*eptr != '\0' || (ui64_t)ul >= UI32_MAX) {
1119 n_err(_("Can't parse *mime-counter-evidence* value: %s\n"), mce.cp);
1120 is_os = FAL0;
1121 } else {
1122 mce.f = (ui32_t)ul | MIMECE_SET;
1123 is_os = !asccasecmp(ct, "application/octet-stream");
1125 if (mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))) {
1126 if (_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL) {
1127 if (is_os)
1128 goto jos_content_check;
1129 } else if (is_os || asccasecmp(ct, mtl.mtl_result)) {
1130 if (mce.f & MIMECE_ALL_OVWR)
1131 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1132 if (mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1133 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1137 } else
1138 is_os = FAL0;
1140 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1141 mc = MIME_TEXT;
1142 else if (is_asccaseprefix(ct, "text/")) {
1143 ct += sizeof("text/") -1;
1144 if (!asccasecmp(ct, "plain"))
1145 mc = MIME_TEXT_PLAIN;
1146 else if (!asccasecmp(ct, "html"))
1147 mc = MIME_TEXT_HTML;
1148 else
1149 mc = MIME_TEXT;
1150 } else if (is_asccaseprefix(ct, "message/")) {
1151 ct += sizeof("message/") -1;
1152 if (!asccasecmp(ct, "rfc822"))
1153 mc = MIME_822;
1154 else
1155 mc = MIME_MESSAGE;
1156 } else if (!ascncasecmp(ct, "multipart/", 10)) {
1157 ct += sizeof("multipart/") -1;
1158 if (!asccasecmp(ct, "alternative"))
1159 mc = MIME_ALTERNATIVE;
1160 else if (!asccasecmp(ct, "related"))
1161 mc = MIME_RELATED;
1162 else if (!asccasecmp(ct, "digest"))
1163 mc = MIME_DIGEST;
1164 else
1165 mc = MIME_MULTI;
1166 } else if (is_asccaseprefix(ct, "application/")) {
1167 if (is_os)
1168 goto jos_content_check;
1169 ct += sizeof("application/") -1;
1170 if (!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1171 mc = MIME_PKCS7;
1173 jleave:
1174 NYD_LEAVE;
1175 return mc;
1177 jos_content_check:
1178 if ((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1179 mpp->m_charset != NULL && asccasecmp(mpp->m_charset, "binary"))
1180 mc = _mt_classify_os_part(mce.f, mpp);
1181 goto jleave;
1184 FL enum mime_handler_flags
1185 mime_type_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1186 enum sendaction action)
1188 #define __S "pipe-"
1189 #define __L (sizeof(__S) -1)
1190 struct mtlookup mtl;
1191 char *buf, *cp;
1192 enum mime_handler_flags rv;
1193 char const *es, *cs, *ccp;
1194 size_t el, cl, l;
1195 NYD_ENTER;
1197 memset(mhp, 0, sizeof *mhp);
1198 buf = NULL;
1200 rv = MIME_HDL_NULL;
1201 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1202 rv |= MIME_HDL_ISQUOTE;
1203 else if (action != SEND_TODISP && action != SEND_TODISP_ALL)
1204 goto jleave;
1206 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1207 *++es != '\0') ? strlen(es) : 0;
1208 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1209 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1210 if ((l = n_MAX(el, cl)) == 0) {
1211 /* TODO this should be done during parse time! */
1212 goto jleave;
1215 /* We don't pass the flags around, so ensure carrier is up-to-date */
1216 mhp->mh_flags = rv;
1218 buf = ac_alloc(__L + l +1);
1219 memcpy(buf, __S, __L);
1221 /* File-extension handlers take precedence.
1222 * Yes, we really "fail" here for file extensions which clash MIME types */
1223 if (el > 0) {
1224 memcpy(buf + __L, es, el +1);
1225 for (cp = buf + __L; *cp != '\0'; ++cp)
1226 *cp = lowerconv(*cp);
1228 if ((mhp->mh_shell_cmd = ccp = vok_vlook(buf)) != NULL) {
1229 rv = _mt_pipe_check(mhp);
1230 goto jleave;
1234 /* Then MIME Content-Type:, if any */
1235 if (cl == 0)
1236 goto jleave;
1238 memcpy(buf + __L, cs, cl +1);
1239 for (cp = buf + __L; *cp != '\0'; ++cp)
1240 *cp = lowerconv(*cp);
1242 if ((mhp->mh_shell_cmd = vok_vlook(buf)) != NULL) {
1243 rv = _mt_pipe_check(mhp);
1244 goto jleave;
1247 if (_mt_by_mtname(&mtl, cs) != NULL)
1248 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
1249 #ifndef HAVE_FILTER_HTML_TAGSOUP
1250 case _MT_SOUP_H:
1251 break;
1252 #endif
1253 case _MT_SOUP_h:
1254 #ifdef HAVE_FILTER_HTML_TAGSOUP
1255 case _MT_SOUP_H:
1256 mhp->mh_ptf = &htmlflt_process_main;
1257 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1258 n_UNCONST(_("Builtin HTML tagsoup filter")));
1259 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1260 goto jleave;
1261 #endif
1262 /* FALLTHRU */
1263 case _MT_PLAIN:
1264 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
1265 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1266 goto jleave;
1267 default:
1268 break;
1271 jleave:
1272 if (buf != NULL)
1273 ac_free(buf);
1275 mhp->mh_flags = rv;
1276 if ((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL)
1277 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1278 _("[-- No MIME handler installed or none applicable --]")));
1279 NYD_LEAVE;
1280 return rv;
1281 #undef __L
1282 #undef __S
1285 /* s-it-mode */