Tweak of [3051546]: avoid const cast-away
[s-mailx.git] / mime_types.c
blob13c436f1b93fd23616030337669265d42c5bb9aa
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_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 whether 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"), n_shexp_quote_cp(ccp, FAL0));
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"), ccp);
218 goto jleave;
221 /* Load all file-based sources in the desired order */
222 line = NULL;
223 linesize = 0;
224 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
225 i > 0; ++j, ++srcs, --i)
226 if (*srcs == NULL)
227 continue;
228 else if (!__mt_load_file((j == 0 ? _MT_USR : (j == 1 ? _MT_SYS : 0)),
229 *srcs, &line, &linesize)) {
230 if ((options & OPT_D_V) || j > 1)
231 n_err(_("*mimetypes-load-control*: can't open or load %s\n"),
232 n_shexp_quote_cp(*srcs, FAL0));
234 if (line != NULL)
235 free(line);
236 jleave:
237 _mt_is_init = TRU1;
238 NYD_LEAVE;
241 static bool_t
242 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
244 char const *cp;
245 FILE *fp;
246 struct mtnode *head, *tail, *mtnp;
247 size_t len;
248 NYD_ENTER;
250 if ((cp = file_expand(file)) == NULL || (fp = Fopen(cp, "r")) == NULL) {
251 cp = NULL;
252 goto jleave;
255 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
256 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
257 if (head == NULL)
258 head = tail = mtnp;
259 else
260 tail->mt_next = mtnp;
261 tail = mtnp;
263 if (head != NULL) {
264 tail->mt_next = _mt_list;
265 _mt_list = head;
268 Fclose(fp);
269 jleave:
270 NYD_LEAVE;
271 return (cp != NULL);
274 static struct mtnode *
275 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
277 struct mtnode *mtnp = NULL;
278 char const *typ, *subtyp;
279 size_t tlen, i;
280 NYD_ENTER;
282 /* Drop anything after a comment first */
283 if ((typ = memchr(line, '#', len)) != NULL)
284 len = PTR2SIZE(typ - line);
286 /* Then trim any trailing whitespace from line (including NL/CR) */
287 while (len > 0 && spacechar(line[len - 1]))
288 --len;
290 /* Isolate MIME type, trim any whitespace from it */
291 while (len > 0 && blankchar(*line))
292 ++line, --len;
293 typ = line;
295 /* (But wait - is there a type marker?) */
296 if (!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@') {
297 if (len < 2)
298 goto jeinval;
299 if (typ[1] == ' ') {
300 orflags |= _MT_PLAIN;
301 typ += 2;
302 len -= 2;
303 line += 2;
304 } else if (len > 4 && typ[2] == '@' && typ[3] == ' ') {
305 switch (typ[1]) {
306 case 't': orflags |= _MT_PLAIN; goto jexttypmar;
307 case 'h': orflags |= _MT_SOUP_h; goto jexttypmar;
308 case 'H': orflags |= _MT_SOUP_H;
309 jexttypmar:
310 typ += 4;
311 len -= 4;
312 line += 4;
313 break;
314 default:
315 goto jeinval;
317 } else
318 goto jeinval;
321 while (len > 0 && !blankchar(*line))
322 ++line, --len;
323 /* Ignore empty lines and even incomplete specifications (only MIME type)
324 * because this is quite common in mime.types(5) files */
325 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
326 if (cmdcalled)
327 n_err(_("Empty MIME type or no extensions given: %s\n"),
328 (len == 0 ? _("(no value)") : line));
329 goto jleave;
332 if ((subtyp = memchr(typ, '/', tlen)) == NULL) {
333 jeinval:
334 if (cmdcalled || (options & OPT_D_V))
335 n_err(_("%s MIME type: %s\n"),
336 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")), typ);
337 goto jleave;
339 ++subtyp;
341 /* Map to mime_type */
342 tlen = PTR2SIZE(subtyp - typ);
343 for (i = __MT_TMIN;;) {
344 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
345 orflags |= i;
346 tlen = PTR2SIZE(line - subtyp);
347 typ = subtyp;
348 break;
350 if (++i == __MT_TMAX) {
351 orflags |= _MT_OTHER;
352 tlen = PTR2SIZE(line - typ);
353 break;
357 /* Strip leading whitespace from the list of extensions;
358 * trailing WS has already been trimmed away above.
359 * Be silent on slots which define a mimetype without any value */
360 while (len > 0 && blankchar(*line))
361 ++line, --len;
362 if (len == 0)
363 goto jleave;
365 /* */
366 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
367 mtnp->mt_next = NULL;
368 mtnp->mt_flags = (orflags |= _MT_LOADED);
369 mtnp->mt_mtlen = (ui32_t)tlen;
370 { char *l = (char*)(mtnp + 1);
371 mtnp->mt_line = l;
372 memcpy(l, typ, tlen);
373 memcpy(l + tlen, line, len);
374 tlen += len;
375 l[tlen] = '\0';
378 jleave:
379 NYD_LEAVE;
380 return mtnp;
383 static struct mtlookup *
384 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
386 struct mtnode *mtnp;
387 size_t nlen, i, j;
388 char const *ext, *cp;
389 NYD2_ENTER;
391 memset(mtlp, 0, sizeof *mtlp);
393 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
394 goto jnull_leave;
395 /* We need a period TODO we should support names like README etc. */
396 for (i = nlen; name[--i] != '.';)
397 if (i == 0 || name[i] == '/') /* XXX no magics */
398 goto jnull_leave;
399 /* While here, basename() it */
400 while (i > 0 && name[i - 1] != '/')
401 --i;
402 name += i;
403 nlen -= i;
404 mtlp->mtl_name = name;
405 mtlp->mtl_nlen = nlen;
407 if (!_mt_is_init)
408 _mt_init();
410 /* ..all the MIME types */
411 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
412 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
413 cp = ext;
414 while (whitechar(*cp))
415 ++cp;
416 ext = cp;
417 while (!whitechar(*cp) && *cp != '\0')
418 ++cp;
420 if ((i = PTR2SIZE(cp - ext)) == 0)
421 break;
422 /* Don't allow neither of ".txt" or "txt" to match "txt" */
423 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
424 ascncasecmp(name + j, ext, i))
425 continue;
427 /* Found it */
428 mtlp->mtl_node = mtnp;
430 if (!with_result)
431 goto jleave;
433 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
434 name = "";
435 j = 0;
436 } else {
437 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
438 j = strlen(name);
440 i = mtnp->mt_mtlen;
441 mtlp->mtl_result = salloc(i + j +1);
442 if (j > 0)
443 memcpy(mtlp->mtl_result, name, j);
444 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
445 mtlp->mtl_result[j += i] = '\0';
446 goto jleave;
448 jnull_leave:
449 mtlp = NULL;
450 jleave:
451 NYD2_LEAVE;
452 return mtlp;
455 static struct mtlookup *
456 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
458 struct mtnode *mtnp;
459 size_t nlen, i, j;
460 char const *cp;
461 NYD2_ENTER;
463 memset(mtlp, 0, sizeof *mtlp);
465 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
466 goto jnull_leave;
468 if (!_mt_is_init)
469 _mt_init();
471 /* ..all the MIME types */
472 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
473 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
474 cp = "";
475 j = 0;
476 } else {
477 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
478 j = strlen(cp);
480 i = mtnp->mt_mtlen;
482 if (i + j == mtlp->mtl_nlen) {
483 char *xmt = ac_alloc(i + j +1);
484 if (j > 0)
485 memcpy(xmt, cp, j);
486 memcpy(xmt + j, mtnp->mt_line, i);
487 xmt[j += i] = '\0';
488 i = asccasecmp(mtname, xmt);
489 ac_free(xmt);
491 if (!i) {
492 /* Found it */
493 mtlp->mtl_node = mtnp;
494 goto jleave;
498 jnull_leave:
499 mtlp = NULL;
500 jleave:
501 NYD2_LEAVE;
502 return mtlp;
505 SINLINE struct mt_class_arg *
506 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
508 NYD2_ENTER;
509 memset(mtcap, 0, sizeof *mtcap);
510 mtcap->mtca_lastc = mtcap->mtca_c = EOF;
511 mtcap->mtca_mtc = initval;
512 NYD2_LEAVE;
513 return mtcap;
516 static enum mime_type_class
517 _mt_classify_round(struct mt_class_arg *mtcap)
519 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
520 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
521 * TODO and report that state to the outer world */
522 #define F_ "From "
523 #define F_SIZEOF (sizeof(F_) -1)
524 char f_buf[F_SIZEOF], *f_p = f_buf;
525 char const *buf;
526 size_t blen;
527 ssize_t curlen;
528 int c, lastc;
529 enum mime_type_class mtc;
530 NYD2_ENTER;
532 buf = mtcap->mtca_buf;
533 blen = mtcap->mtca_len;
534 curlen = mtcap->mtca_curlen;
535 c = mtcap->mtca_c;
536 lastc = mtcap->mtca_lastc;
537 mtc = mtcap->mtca_mtc;
539 for (;; ++curlen) {
540 lastc = c;
541 if (blen == 0) {
542 /* Real EOF, or only current buffer end? */
543 if (mtcap->mtca_len == 0)
544 c = EOF;
545 else
546 break;
547 } else
548 c = (uc_i)*buf++;
549 --blen;
551 if (c == '\0') {
552 mtc |= _MT_C_HASNUL;
553 if (!(mtc & _MT_C_ISTXTCOK)) {
554 mtc |= _MT_C_SUGGEST_DONE;
555 break;
557 continue;
559 if (c == '\n' || c == EOF) {
560 if (curlen >= MIME_LINELEN_LIMIT)
561 mtc |= _MT_C_LONGLINES;
562 if (c == EOF) {
563 break;
565 f_p = f_buf;
566 curlen = -1;
567 continue;
569 /* A bit hairy is handling of \r=\x0D=CR.
570 * RFC 2045, 6.7:
571 * Control characters other than TAB, or CR and LF as parts of CRLF
572 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
573 * we cannot peek the next character. Thus right here, inspect the last
574 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
575 /*else*/ if (lastc == '\r')
576 mtc |= _MT_C_CTRLCHAR;
578 /* Control character? XXX this is all ASCII here */
579 if (c < 0x20 || c == 0x7F) {
580 /* RFC 2045, 6.7, as above ... */
581 if (c != '\t' && c != '\r')
582 mtc |= _MT_C_CTRLCHAR;
583 /* If there is a escape sequence in backslash notation defined for
584 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
585 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
586 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
587 * \e=\x1B=ESC */
588 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
589 continue;
590 mtc |= _MT_C_HASNUL; /* Force base64 */
591 if (!(mtc & _MT_C_ISTXTCOK)) {
592 mtc |= _MT_C_SUGGEST_DONE;
593 break;
595 } else if ((ui8_t)c & 0x80) {
596 mtc |= _MT_C_HIGHBIT;
597 /* TODO count chars with HIGHBIT? libmagic?
598 * TODO try encode part - base64 if bails? */
599 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
600 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
601 break;
603 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
604 *f_p++ = (char)c;
605 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
606 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
607 !memcmp(f_buf, F_, F_SIZEOF))
608 mtc |= _MT_C_FROM_;
611 if (c == EOF && lastc != '\n')
612 mtc |= _MT_C_NOTERMNL;
614 mtcap->mtca_curlen = curlen;
615 mtcap->mtca_lastc = lastc;
616 mtcap->mtca_c = c;
617 mtcap->mtca_mtc = mtc;
618 NYD2_LEAVE;
619 return mtc;
620 #undef F_
621 #undef F_SIZEOF
624 static enum mimecontent
625 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp)
627 struct str in = {NULL, 0}, rest = {NULL, 0}, dec = {NULL, 0};
628 struct mt_class_arg mtca;
629 enum mime_type_class mtc;
630 int lc, c;
631 size_t cnt, lsz;
632 FILE *ibuf;
633 off_t start_off;
634 enum mimecontent mc;
635 NYD2_ENTER;
637 assert(mpp->m_mime_enc != MIMEE_BIN);
639 mc = MIME_UNKNOWN;
640 UNINIT(mtc, 0);
642 /* TODO v15-compat Note we actually bypass our usual file handling by
643 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
644 * TODO all of this, and until then doing it like this is the only option
645 * TODO to integrate nicely into whoever calls us */
646 start_off = ftell(mb.mb_itf);
647 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
648 jos_leave:
649 fseek(mb.mb_itf, start_off, SEEK_SET);
650 goto jleave;
652 cnt = mpp->m_size;
654 /* Skip part headers */
655 for (lc = '\0'; cnt > 0; lc = c, --cnt)
656 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
657 break;
658 if (cnt == 0 || ferror(ibuf))
659 goto jos_leave;
661 /* So now let's inspect the part content, decoding content-transfer-encoding
662 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
663 _mt_classify_init(&mtca, _MT_C_ISTXT);
665 for (lsz = 0;;) {
666 bool_t dobuf;
668 c = (--cnt == 0) ? EOF : getc(ibuf);
669 if ((dobuf = (c == '\n'))) {
670 /* Ignore empty lines */
671 if (lsz == 0)
672 continue;
673 } else if ((dobuf = (c == EOF))) {
674 if (lsz == 0 && rest.l == 0)
675 break;
678 if (in.l + 1 >= lsz)
679 in.s = srealloc(in.s, lsz += LINESIZE);
680 if (c != EOF)
681 in.s[in.l++] = (char)c;
682 if (!dobuf)
683 continue;
685 jdobuf:
686 switch (mpp->m_mime_enc) {
687 case MIMEE_B64:
688 if (b64_decode(&dec, &in, &rest) == STOP) {
689 mtca.mtca_mtc = _MT_C_HASNUL;
690 goto jstopit; /* break;break; */
692 break;
693 case MIMEE_QP:
694 /* Drin */
695 if (qp_decode(&dec, &in, &rest) == STOP) {
696 mtca.mtca_mtc = _MT_C_HASNUL;
697 goto jstopit; /* break;break; */
699 if (dec.l == 0 && c != EOF) {
700 in.l = 0;
701 continue;
703 break;
704 default:
705 /* Temporarily switch those two buffers.. */
706 dec = in;
707 in.s = NULL;
708 in.l = 0;
709 break;
712 mtca.mtca_buf = dec.s;
713 mtca.mtca_len = (ssize_t)dec.l;
714 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
715 mtc = _MT_C_HASNUL;
716 break;
719 if (c == EOF)
720 break;
721 /* ..and restore switched */
722 if (in.s == NULL) {
723 in = dec;
724 dec.s = NULL;
726 in.l = dec.l = 0;
728 if (rest.l > 0) {
729 in.l = 0;
730 goto jdobuf;
732 jstopit:
733 if (in.s != NULL)
734 free(in.s);
735 if (dec.s != NULL)
736 free(dec.s);
737 if (rest.s != NULL)
738 free(rest.s);
740 fseek(mb.mb_itf, start_off, SEEK_SET);
742 if (!(mtc & (_MT_C_HASNUL | _MT_C_CTRLCHAR))) {
743 mc = MIME_TEXT_PLAIN;
744 if (mce & MIMECE_ALL_OVWR)
745 mpp->m_ct_type_plain = "text/plain";
746 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
747 mpp->m_ct_type_usr_ovwr = "text/plain";
749 jleave:
750 NYD2_LEAVE;
751 return mc;
754 static enum mime_handler_flags
755 _mt_pipe_check(struct mime_handler *mhp)
757 enum mime_handler_flags rv_orig, rv;
758 char const *cp;
759 NYD2_ENTER;
761 rv_orig = rv = mhp->mh_flags;
763 /* Do we have any handler for this part? */
764 if (*(cp = mhp->mh_shell_cmd) == '\0')
765 goto jleave;
766 else if (*cp++ != '@') {
767 rv |= MIME_HDL_CMD;
768 goto jleave;
769 } else if (*cp == '\0') {
770 rv |= MIME_HDL_TEXT;
771 goto jleave;
774 jnextc:
775 switch (*cp) {
776 case '*': rv |= MIME_HDL_ALWAYS; ++cp; goto jnextc;
777 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
778 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
779 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
780 case '+':
781 if (rv & MIME_HDL_TMPF)
782 rv |= MIME_HDL_TMPF_UNLINK;
783 rv |= MIME_HDL_TMPF;
784 ++cp;
785 goto jnextc;
786 case '=':
787 rv |= MIME_HDL_TMPF_FILL;
788 ++cp;
789 goto jnextc;
790 case '@':
791 ++cp;
792 /* FALLTHRU */
793 default:
794 break;
796 mhp->mh_shell_cmd = cp;
798 /* Implications */
799 if (rv & MIME_HDL_TMPF_FILL)
800 rv |= MIME_HDL_TMPF;
802 /* Exceptions */
803 if (rv & MIME_HDL_ISQUOTE) {
804 if (rv & MIME_HDL_NOQUOTE)
805 goto jerr;
807 /* Cannot fetch data back from asynchronous process */
808 if (rv & MIME_HDL_ASYNC)
809 goto jerr;
811 /* TODO Can't use a "needsterminal" program for quoting */
812 if (rv & MIME_HDL_NEEDSTERM)
813 goto jerr;
816 if (rv & MIME_HDL_NEEDSTERM) {
817 if (rv & MIME_HDL_ASYNC) {
818 n_err(_("MIME type handlers: can't use needsterminal and "
819 "x-nail-async together\n"));
820 goto jerr;
823 /* needsterminal needs a terminal */
824 if (!(options & OPT_INTERACTIVE))
825 goto jerr;
828 if (!(rv & MIME_HDL_ALWAYS) && !(pstate & PS_MSGLIST_DIRECT)) {
829 /* Viewing multiple messages in one go, don't block system */
830 mhp->mh_msg.l = strlen(mhp->mh_msg.s = UNCONST(
831 _("[-- Directly address message only for display --]\n")));
832 rv |= MIME_HDL_MSG;
833 goto jleave;
836 rv |= MIME_HDL_CMD;
837 jleave:
838 mhp->mh_flags = rv;
839 NYD2_LEAVE;
840 return rv;
841 jerr:
842 rv = rv_orig;
843 goto jleave;
846 FL int
847 c_mimetype(void *v)
849 char **argv = v;
850 struct mtnode *mtnp;
851 NYD_ENTER;
853 if (!_mt_is_init)
854 _mt_init();
856 if (*argv == NULL) {
857 FILE *fp;
858 size_t l;
860 if (_mt_list == NULL) {
861 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
862 goto jleave;
865 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
866 NULL) {
867 n_perr(_("tmpfile"), 0);
868 v = NULL;
869 goto jleave;
872 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
873 char const *tmark, *typ;
875 switch (mtnp->mt_flags & __MT_MARKMASK) {
876 case _MT_PLAIN: tmark = "/t"; break;
877 case _MT_SOUP_h: tmark = "/h"; break;
878 case _MT_SOUP_H: tmark = "/H"; break;
879 default: tmark = " "; break;
881 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
882 ? "" : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
884 fprintf(fp, "%c%s %s%.*s <%s>\n",
885 (mtnp->mt_flags & _MT_USR ? 'U'
886 : (mtnp->mt_flags & _MT_SYS ? 'S'
887 : (mtnp->mt_flags & _MT_LOADED ? 'F' : 'B'))),
888 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
889 mtnp->mt_line + mtnp->mt_mtlen);
892 page_or_print(fp, l);
893 Fclose(fp);
894 } else {
895 for (; *argv != NULL; ++argv) {
896 mtnp = _mt_create(TRU1, _MT_LOADED, *argv, strlen(*argv));
897 if (mtnp != NULL) {
898 mtnp->mt_next = _mt_list;
899 _mt_list = mtnp;
900 } else
901 v = NULL;
904 jleave:
905 NYD_LEAVE;
906 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
909 FL int
910 c_unmimetype(void *v)
912 char **argv = v;
913 struct mtnode *lnp, *mtnp;
914 bool_t match;
915 NYD_ENTER;
917 /* Need to load that first as necessary */
918 if (!_mt_is_init)
919 _mt_init();
921 for (; *argv != NULL; ++argv) {
922 if (!asccasecmp(*argv, "reset")) {
923 _mt_is_init = FAL0;
924 goto jdelall;
927 if (argv[0][0] == '*' && argv[0][1] == '\0') {
928 jdelall:
929 while ((mtnp = _mt_list) != NULL) {
930 _mt_list = mtnp->mt_next;
931 free(mtnp);
933 continue;
936 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
937 char const *typ;
938 char *val;
939 size_t i;
941 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
942 typ = "";
943 i = 0;
944 } else {
945 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
946 i = strlen(typ);
949 val = ac_alloc(i + mtnp->mt_mtlen +1);
950 memcpy(val, typ, i);
951 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
952 val[i += mtnp->mt_mtlen] = '\0';
953 i = asccasecmp(val, *argv);
954 ac_free(val);
956 if (!i) {
957 struct mtnode *nnp = mtnp->mt_next;
958 if (lnp == NULL)
959 _mt_list = nnp;
960 else
961 lnp->mt_next = nnp;
962 free(mtnp);
963 mtnp = nnp;
964 match = TRU1;
965 } else
966 lnp = mtnp, mtnp = mtnp->mt_next;
968 if (!match) {
969 if (!(pstate & PS_ROBOT) || (options & OPT_D_V))
970 n_err(_("No such MIME type: %s\n"), *argv);
971 v = NULL;
974 NYD_LEAVE;
975 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
978 FL bool_t
979 mime_type_check_mtname(char const *name)
981 struct mtlookup mtl;
982 bool_t rv;
983 NYD_ENTER;
985 rv = (_mt_by_mtname(&mtl, name) != NULL);
986 NYD_LEAVE;
987 return rv;
990 FL char *
991 mime_type_classify_filename(char const *name)
993 struct mtlookup mtl;
994 NYD_ENTER;
996 _mt_by_filename(&mtl, name, TRU1);
997 NYD_LEAVE;
998 return mtl.mtl_result;
1001 FL enum conversion
1002 mime_type_classify_file(FILE *fp, char const **contenttype,
1003 char const **charset, int *do_iconv)
1005 /* TODO classify once only PLEASE PLEASE PLEASE */
1006 enum mime_type_class mtc;
1007 enum mime_enc menc;
1008 off_t fpsz;
1009 NYD_ENTER;
1011 assert(ftell(fp) == 0x0l);
1013 *do_iconv = 0;
1015 if (*contenttype == NULL)
1016 mtc = _MT_C_NCTT;
1017 else if (!ascncasecmp(*contenttype, "text/", 5))
1018 mtc = ok_blook(mime_allow_text_controls)
1019 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1020 else
1021 mtc = _MT_C_CLEAN;
1023 menc = mime_enc_target();
1025 if ((fpsz = fsize(fp)) == 0)
1026 goto j7bit;
1027 else {
1028 char buf[BUFFER_SIZE];
1029 struct mt_class_arg mtca;
1031 _mt_classify_init(&mtca, mtc);
1032 for (;;) {
1033 mtca.mtca_len = fread(buf, sizeof(buf[0]), NELEM(buf), fp);
1034 mtca.mtca_buf = buf;
1035 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1036 break;
1037 if (mtca.mtca_len == 0)
1038 break;
1040 /* TODO ferror(fp) ! */
1041 rewind(fp);
1044 if (mtc & _MT_C_HASNUL) {
1045 menc = MIMEE_B64;
1046 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1047 * on request; else enforce what file(1)/libmagic(3) would suggest */
1048 if (mtc & _MT_C_ISTXTCOK)
1049 goto jcharset;
1050 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1051 *contenttype = "application/octet-stream";
1052 if (*charset == NULL)
1053 *charset = "binary";
1054 goto jleave;
1057 if (mtc &
1058 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1059 if (menc != MIMEE_B64)
1060 menc = MIMEE_QP;
1061 goto jstepi;
1063 if (mtc & _MT_C_HIGHBIT) {
1064 jstepi:
1065 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1066 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1067 } else
1068 j7bit:
1069 menc = MIMEE_7B;
1070 if (mtc & _MT_C_NCTT)
1071 *contenttype = "text/plain";
1073 /* Not an attachment with specified charset? */
1074 jcharset:
1075 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1076 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1077 : charset_get_7bit();
1078 jleave:
1079 NYD_LEAVE;
1080 /* TODO mime_type_file_classify() shouldn't return conversion */
1081 return (menc == MIMEE_7B ? CONV_7BIT :
1082 (menc == MIMEE_8B ? CONV_8BIT :
1083 (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1086 FL enum mimecontent
1087 mime_type_classify_part(struct mimepart *mpp) /* FIXME charset=binary ??? */
1089 struct mtlookup mtl;
1090 enum mimecontent mc;
1091 char const *ct;
1092 union {char const *cp; ui32_t f;} mce;
1093 bool_t is_os;
1094 NYD_ENTER;
1096 mc = MIME_UNKNOWN;
1097 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1098 ct = "";
1100 if ((mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
1101 char *eptr;
1102 ul_i ul;
1104 ul = strtoul(mce.cp, &eptr, 0); /* XXX strtol */
1105 if (*mce.cp == '\0')
1106 is_os = FAL0;
1107 else if (*eptr != '\0' || (ui64_t)ul >= UI32_MAX) {
1108 n_err(_("Can't parse *mime-counter-evidence* value: %s\n"), mce.cp);
1109 is_os = FAL0;
1110 } else {
1111 mce.f = (ui32_t)ul | MIMECE_SET;
1112 is_os = !asccasecmp(ct, "application/octet-stream");
1114 if (mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))) {
1115 if (_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL) {
1116 if (is_os)
1117 goto jos_content_check;
1118 } else if (is_os || asccasecmp(ct, mtl.mtl_result)) {
1119 if (mce.f & MIMECE_ALL_OVWR)
1120 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1121 if (mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1122 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1126 } else
1127 is_os = FAL0;
1129 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
1130 mc = MIME_TEXT;
1131 else if (is_asccaseprefix(ct, "text/")) {
1132 ct += sizeof("text/") -1;
1133 if (!asccasecmp(ct, "plain"))
1134 mc = MIME_TEXT_PLAIN;
1135 else if (!asccasecmp(ct, "html"))
1136 mc = MIME_TEXT_HTML;
1137 else
1138 mc = MIME_TEXT;
1139 } else if (is_asccaseprefix(ct, "message/")) {
1140 ct += sizeof("message/") -1;
1141 if (!asccasecmp(ct, "rfc822"))
1142 mc = MIME_822;
1143 else
1144 mc = MIME_MESSAGE;
1145 } else if (!ascncasecmp(ct, "multipart/", 10)) {
1146 ct += sizeof("multipart/") -1;
1147 if (!asccasecmp(ct, "alternative"))
1148 mc = MIME_ALTERNATIVE;
1149 else if (!asccasecmp(ct, "related"))
1150 mc = MIME_RELATED;
1151 else if (!asccasecmp(ct, "digest"))
1152 mc = MIME_DIGEST;
1153 else
1154 mc = MIME_MULTI;
1155 } else if (is_asccaseprefix(ct, "application/")) {
1156 if (is_os)
1157 goto jos_content_check;
1158 ct += sizeof("application/") -1;
1159 if (!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1160 mc = MIME_PKCS7;
1162 jleave:
1163 NYD_LEAVE;
1164 return mc;
1166 jos_content_check:
1167 if ((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1168 mpp->m_charset != NULL && asccasecmp(mpp->m_charset, "binary"))
1169 mc = _mt_classify_os_part(mce.f, mpp);
1170 goto jleave;
1173 FL enum mime_handler_flags
1174 mime_type_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1175 enum sendaction action)
1177 #define __S "pipe-"
1178 #define __L (sizeof(__S) -1)
1179 struct mtlookup mtl;
1180 char *buf, *cp;
1181 enum mime_handler_flags rv;
1182 char const *es, *cs, *ccp;
1183 size_t el, cl, l;
1184 NYD_ENTER;
1186 memset(mhp, 0, sizeof *mhp);
1187 buf = NULL;
1189 rv = MIME_HDL_NULL;
1190 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1191 rv |= MIME_HDL_ISQUOTE;
1192 else if (action != SEND_TODISP && action != SEND_TODISP_ALL)
1193 goto jleave;
1195 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1196 *++es != '\0') ? strlen(es) : 0;
1197 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1198 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1199 if ((l = MAX(el, cl)) == 0) {
1200 /* TODO this should be done during parse time! */
1201 goto jleave;
1204 /* We don't pass the flags around, so ensure carrier is up-to-date */
1205 mhp->mh_flags = rv;
1207 buf = ac_alloc(__L + l +1);
1208 memcpy(buf, __S, __L);
1210 /* File-extension handlers take precedence.
1211 * Yes, we really "fail" here for file extensions which clash MIME types */
1212 if (el > 0) {
1213 memcpy(buf + __L, es, el +1);
1214 for (cp = buf + __L; *cp != '\0'; ++cp)
1215 *cp = lowerconv(*cp);
1217 if ((mhp->mh_shell_cmd = ccp = vok_vlook(buf)) != NULL) {
1218 rv = _mt_pipe_check(mhp);
1219 goto jleave;
1223 /* Then MIME Content-Type:, if any */
1224 if (cl == 0)
1225 goto jleave;
1227 memcpy(buf + __L, cs, cl +1);
1228 for (cp = buf + __L; *cp != '\0'; ++cp)
1229 *cp = lowerconv(*cp);
1231 if ((mhp->mh_shell_cmd = vok_vlook(buf)) != NULL) {
1232 rv = _mt_pipe_check(mhp);
1233 goto jleave;
1236 if (_mt_by_mtname(&mtl, cs) != NULL)
1237 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
1238 #ifndef HAVE_FILTER_HTML_TAGSOUP
1239 case _MT_SOUP_H:
1240 break;
1241 #endif
1242 case _MT_SOUP_h:
1243 #ifdef HAVE_FILTER_HTML_TAGSOUP
1244 case _MT_SOUP_H:
1245 mhp->mh_ptf = &htmlflt_process_main;
1246 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1247 UNCONST(_("Builtin HTML tagsoup filter")));
1248 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1249 goto jleave;
1250 #endif
1251 /* FALLTHRU */
1252 case _MT_PLAIN:
1253 mhp->mh_msg.l = strlen(mhp->mh_msg.s = UNCONST(_("Plain text view")));
1254 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1255 goto jleave;
1256 default:
1257 break;
1260 jleave:
1261 if (buf != NULL)
1262 ac_free(buf);
1264 mhp->mh_flags = rv;
1265 if ((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL)
1266 mhp->mh_msg.l = strlen(mhp->mh_msg.s = UNCONST(
1267 _("[-- No MIME handler installed or none applicable --]")));
1268 NYD_LEAVE;
1269 return rv;
1270 #undef __L
1271 #undef __S
1274 /* s-it-mode */