*inbox*: if empty, only bypass *folder* to $MAIL or builtin default
[s-mailx.git] / mime_types.c
blobd12a15f3f00c0eb7a9eb5b8743c0af95a2ad962a
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?mimetype' and other mime.types(5) related facilities.
3 *@ "Keep in sync with" ./mime.types.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #undef n_FILE
20 #define n_FILE mime_types
22 #ifndef HAVE_AMALGAMATION
23 # include "nail.h"
24 #endif
26 enum mime_type {
27 _MT_APPLICATION,
28 _MT_AUDIO,
29 _MT_IMAGE,
30 _MT_MESSAGE,
31 _MT_MULTIPART,
32 _MT_TEXT,
33 _MT_VIDEO,
34 _MT_OTHER,
35 __MT_TMIN = 0,
36 __MT_TMAX = _MT_OTHER,
37 __MT_TMASK = 0x07,
39 _MT_LOADED = 1<< 8, /* Not struct mtbltin */
40 _MT_USR = 1<< 9, /* MIME_TYPES_USR */
41 _MT_SYS = 1<<10, /* MIME_TYPES_SYS */
43 _MT_PLAIN = 1<<16, /* Without pipe handler display as text */
44 _MT_SOUP_h = 2<<16, /* Ditto, but HTML tagsoup parser if possible */
45 _MT_SOUP_H = 3<<16, /* HTML tagsoup parser, else NOT plain text */
46 __MT_MARKMASK = _MT_SOUP_H
49 struct mtbltin {
50 ui32_t mtb_flags;
51 ui32_t mtb_mtlen;
52 char const *mtb_line;
55 struct mtnode {
56 struct mtnode *mt_next;
57 ui32_t mt_flags;
58 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
59 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
60 * that could already store character data here */
61 char const *mt_line;
64 struct mtlookup {
65 char const *mtl_name;
66 size_t mtl_nlen;
67 struct mtnode const *mtl_node;
68 char *mtl_result; /* If requested, salloc()ed MIME type */
71 static struct mtbltin const _mt_bltin[] = {
72 #include "mime_types.h"
75 static char const _mt_typnames[][16] = {
76 "application/", "audio/", "image/",
77 "message/", "multipart/", "text/",
78 "video/"
80 CTA(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
81 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
82 _MT_VIDEO == 6);
84 /* */
85 static bool_t _mt_is_init;
86 static struct mtnode *_mt_list;
88 /* Initialize MIME type list in order */
89 static void _mt_init(void);
90 static bool_t __mt_load_file(ui32_t orflags,
91 char const *file, char **line, size_t *linesize);
93 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
94 * for `mimetype' */
95 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
96 char const *line, size_t len);
98 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
99 * if with_result >mtl_result will be created upon success for the former */
100 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
101 char const *name, bool_t with_result);
102 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
103 char const *mtname);
105 static void
106 _mt_init(void)
108 struct mtnode *tail;
109 char c, *line; /* TODO line pool (below) */
110 size_t linesize;
111 ui32_t i, j;
112 char const *srcs_arr[10], *ccp, **srcs;
113 NYD_ENTER;
115 /*if (_mt_is_init)
116 * goto jleave;*/
118 /* Always load our builtins */
119 for (tail = NULL, i = 0; i < NELEM(_mt_bltin); ++i) {
120 struct mtbltin const *mtbp = _mt_bltin + i;
121 struct mtnode *mtnp = smalloc(sizeof *mtnp);
123 if (tail != NULL)
124 tail->mt_next = mtnp;
125 else
126 _mt_list = mtnp;
127 tail = mtnp;
128 mtnp->mt_next = NULL;
129 mtnp->mt_flags = mtbp->mtb_flags;
130 mtnp->mt_mtlen = mtbp->mtb_mtlen;
131 mtnp->mt_line = mtbp->mtb_line;
134 /* Decide which files sources have to be loaded */
135 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
136 ccp = "US";
137 else if (*ccp == '\0')
138 goto jleave;
140 srcs = srcs_arr + 2;
141 srcs[-1] = srcs[-2] = NULL;
143 if (strchr(ccp, '=') != NULL) {
144 line = savestr(ccp);
146 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
147 switch ((c = *ccp)) {
148 case 'S': case 's':
149 srcs_arr[1] = MIME_TYPES_SYS;
150 if (0) {
151 /* FALLTHRU */
152 case 'U': case 'u':
153 srcs_arr[0] = MIME_TYPES_USR;
155 if (ccp[1] != '\0')
156 goto jecontent;
157 break;
158 case 'F': case 'f':
159 if (*++ccp == '=' && *++ccp != '\0') {
160 if (PTR2SIZE(srcs - srcs_arr) < NELEM(srcs_arr))
161 *srcs++ = ccp;
162 else
163 n_err(_("*mimetypes-load-control*: too many sources, "
164 "skipping \"%s\"\n"), ccp);
165 continue;
167 /* FALLTHRU */
168 default:
169 goto jecontent;
172 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
173 switch (c) {
174 case 'S': case 's': srcs_arr[1] = MIME_TYPES_SYS; break;
175 case 'U': case 'u': srcs_arr[0] = MIME_TYPES_USR; break;
176 default:
177 jecontent:
178 n_err(_("*mimetypes-load-control*: unsupported content: \"%s\"\n"),
179 ccp);
180 goto jleave;
183 /* Load all file-based sources in the desired order */
184 line = NULL;
185 linesize = 0;
186 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
187 i > 0; ++j, ++srcs, --i)
188 if (*srcs == NULL)
189 continue;
190 else if (!__mt_load_file((j == 0 ? _MT_USR : (j == 1 ? _MT_SYS : 0)),
191 *srcs, &line, &linesize)) {
192 if ((options & OPT_D_V) || j > 1)
193 n_err(_("*mimetypes-load-control*: can't open or load \"%s\"\n"),
194 *srcs);
196 if (line != NULL)
197 free(line);
198 jleave:
199 _mt_is_init = TRU1;
200 NYD_LEAVE;
203 static bool_t
204 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
206 char const *cp;
207 FILE *fp;
208 struct mtnode *head, *tail, *mtnp;
209 size_t len;
210 NYD_ENTER;
212 if ((cp = file_expand(file)) == NULL || (fp = Fopen(cp, "r")) == NULL) {
213 cp = NULL;
214 goto jleave;
217 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
218 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
219 if (head == NULL)
220 head = tail = mtnp;
221 else
222 tail->mt_next = mtnp;
223 tail = mtnp;
225 if (head != NULL) {
226 tail->mt_next = _mt_list;
227 _mt_list = head;
230 Fclose(fp);
231 jleave:
232 NYD_LEAVE;
233 return (cp != NULL);
236 static struct mtnode *
237 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
239 struct mtnode *mtnp = NULL;
240 char const *typ, *subtyp;
241 size_t tlen, i;
242 NYD_ENTER;
244 /* Drop anything after a comment first */
245 if ((typ = memchr(line, '#', len)) != NULL)
246 len = PTR2SIZE(typ - line);
248 /* Then trim any trailing whitespace from line (including NL/CR) */
249 while (len > 0 && spacechar(line[len - 1]))
250 --len;
252 /* Isolate MIME type, trim any whitespace from it */
253 while (len > 0 && blankchar(*line))
254 ++line, --len;
255 typ = line;
257 /* (But wait - is there a type marker?) */
258 if (!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@') {
259 if (len < 2)
260 goto jeinval;
261 if (typ[1] == ' ') {
262 orflags |= _MT_PLAIN;
263 typ += 2;
264 len -= 2;
265 line += 2;
266 } else if (len > 4 && typ[2] == '@' && typ[3] == ' ') {
267 switch (typ[1]) {
268 case 't': orflags |= _MT_PLAIN; goto jexttypmar;
269 case 'h': orflags |= _MT_SOUP_h; goto jexttypmar;
270 case 'H': orflags |= _MT_SOUP_H;
271 jexttypmar:
272 typ += 4;
273 len -= 4;
274 line += 4;
275 break;
276 default:
277 goto jeinval;
279 } else
280 goto jeinval;
283 while (len > 0 && !blankchar(*line))
284 ++line, --len;
285 /* Ignore empty lines and even incomplete specifications (only MIME type)
286 * because this is quite common in mime.types(5) files */
287 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
288 if (cmdcalled)
289 n_err(_("Empty MIME type or no extensions given: \"%s\"\n"),
290 (len == 0 ? _("(no value)") : line));
291 goto jleave;
294 if ((subtyp = memchr(typ, '/', tlen)) == NULL) {
295 jeinval:
296 if (cmdcalled || (options & OPT_D_V))
297 n_err(_("%s MIME type: \"%s\"\n"),
298 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")), typ);
299 goto jleave;
301 ++subtyp;
303 /* Map to mime_type */
304 tlen = PTR2SIZE(subtyp - typ);
305 for (i = __MT_TMIN;;) {
306 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
307 orflags |= i;
308 tlen = PTR2SIZE(line - subtyp);
309 typ = subtyp;
310 break;
312 if (++i == __MT_TMAX) {
313 orflags |= _MT_OTHER;
314 tlen = PTR2SIZE(line - typ);
315 break;
319 /* Strip leading whitespace from the list of extensions;
320 * trailing WS has already been trimmed away above.
321 * Be silent on slots which define a mimetype without any value */
322 while (len > 0 && blankchar(*line))
323 ++line, --len;
324 if (len == 0)
325 goto jleave;
327 /* */
328 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
329 mtnp->mt_next = NULL;
330 mtnp->mt_flags = (orflags |= _MT_LOADED);
331 mtnp->mt_mtlen = (ui32_t)tlen;
332 { char *l = (char*)(mtnp + 1);
333 mtnp->mt_line = l;
334 memcpy(l, typ, tlen);
335 memcpy(l + tlen, line, len);
336 tlen += len;
337 l[tlen] = '\0';
340 jleave:
341 NYD_LEAVE;
342 return mtnp;
345 static struct mtlookup *
346 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
348 struct mtnode *mtnp;
349 size_t nlen, i, j;
350 char const *ext, *cp;
351 NYD2_ENTER;
353 memset(mtlp, 0, sizeof *mtlp);
355 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
356 goto jnull_leave;
357 /* We need a period TODO we should support names like README etc. */
358 for (i = nlen; name[--i] != '.';)
359 if (i == 0 || name[i] == '/') /* XXX no magics */
360 goto jnull_leave;
361 /* While here, basename() it */
362 while (i > 0 && name[i - 1] != '/')
363 --i;
364 name += i;
365 nlen -= i;
366 mtlp->mtl_name = name;
367 mtlp->mtl_nlen = nlen;
369 if (!_mt_is_init)
370 _mt_init();
372 /* ..all the MIME types */
373 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
374 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
375 cp = ext;
376 while (whitechar(*cp))
377 ++cp;
378 ext = cp;
379 while (!whitechar(*cp) && *cp != '\0')
380 ++cp;
382 if ((i = PTR2SIZE(cp - ext)) == 0)
383 break;
384 /* Don't allow neither of ".txt" or "txt" to match "txt" */
385 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
386 ascncasecmp(name + j, ext, i))
387 continue;
389 /* Found it */
390 mtlp->mtl_node = mtnp;
392 if (!with_result)
393 goto jleave;
395 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
396 name = "";
397 j = 0;
398 } else {
399 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
400 j = strlen(name);
402 i = mtnp->mt_mtlen;
403 mtlp->mtl_result = salloc(i + j +1);
404 if (j > 0)
405 memcpy(mtlp->mtl_result, name, j);
406 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
407 mtlp->mtl_result[j += i] = '\0';
408 goto jleave;
410 jnull_leave:
411 mtlp = NULL;
412 jleave:
413 NYD2_LEAVE;
414 return mtlp;
417 static struct mtlookup *
418 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
420 struct mtnode *mtnp;
421 size_t nlen, i, j;
422 char const *cp;
423 NYD2_ENTER;
425 memset(mtlp, 0, sizeof *mtlp);
427 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
428 goto jnull_leave;
430 if (!_mt_is_init)
431 _mt_init();
433 /* ..all the MIME types */
434 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
435 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
436 cp = "";
437 j = 0;
438 } else {
439 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
440 j = strlen(cp);
442 i = mtnp->mt_mtlen;
444 if (i + j == mtlp->mtl_nlen) {
445 char *xmt = ac_alloc(i + j +1);
446 if (j > 0)
447 memcpy(xmt, cp, j);
448 memcpy(xmt + j, mtnp->mt_line, i);
449 xmt[j += i] = '\0';
450 i = asccasecmp(mtname, xmt);
451 ac_free(xmt);
453 if (!i) {
454 /* Found it */
455 mtlp->mtl_node = mtnp;
456 goto jleave;
460 jnull_leave:
461 mtlp = NULL;
462 jleave:
463 NYD2_LEAVE;
464 return mtlp;
467 FL int
468 c_mimetype(void *v)
470 char **argv = v;
471 struct mtnode *mtnp;
472 NYD_ENTER;
474 if (!_mt_is_init)
475 _mt_init();
477 if (*argv == NULL) {
478 FILE *fp;
479 size_t l;
481 if (_mt_list == NULL) {
482 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
483 goto jleave;
486 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600))
487 == NULL) {
488 n_perr(_("tmpfile"), 0);
489 v = NULL;
490 goto jleave;
493 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
494 char const *tmark, *typ;
496 switch (mtnp->mt_flags & __MT_MARKMASK) {
497 case _MT_PLAIN: tmark = "/t"; break;
498 case _MT_SOUP_h: tmark = "/h"; break;
499 case _MT_SOUP_H: tmark = "/H"; break;
500 default: tmark = " "; break;
502 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
503 ? "" : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
505 fprintf(fp, "%c%s %s%.*s <%s>\n",
506 (mtnp->mt_flags & _MT_USR ? 'U'
507 : (mtnp->mt_flags & _MT_SYS ? 'S'
508 : (mtnp->mt_flags & _MT_LOADED ? 'F' : 'B'))),
509 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
510 mtnp->mt_line + mtnp->mt_mtlen);
513 page_or_print(fp, l);
514 Fclose(fp);
515 } else {
516 for (; *argv != NULL; ++argv) {
517 mtnp = _mt_create(TRU1, _MT_LOADED, *argv, strlen(*argv));
518 if (mtnp != NULL) {
519 mtnp->mt_next = _mt_list;
520 _mt_list = mtnp;
521 } else
522 v = NULL;
525 jleave:
526 NYD_LEAVE;
527 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
530 FL int
531 c_unmimetype(void *v)
533 char **argv = v;
534 struct mtnode *lnp, *mtnp;
535 bool_t match;
536 NYD_ENTER;
538 /* Need to load that first as necessary */
539 if (!_mt_is_init)
540 _mt_init();
542 for (; *argv != NULL; ++argv) {
543 if (!asccasecmp(*argv, "reset")) {
544 _mt_is_init = FAL0;
545 goto jdelall;
548 if (argv[0][0] == '*' && argv[0][1] == '\0') {
549 jdelall:
550 while ((mtnp = _mt_list) != NULL) {
551 _mt_list = mtnp->mt_next;
552 free(mtnp);
554 continue;
557 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
558 char const *typ;
559 char *val;
560 size_t i;
562 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
563 typ = "";
564 i = 0;
565 } else {
566 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
567 i = strlen(typ);
570 val = ac_alloc(i + mtnp->mt_mtlen +1);
571 memcpy(val, typ, i);
572 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
573 val[i += mtnp->mt_mtlen] = '\0';
574 i = asccasecmp(val, *argv);
575 ac_free(val);
577 if (!i) {
578 struct mtnode *nnp = mtnp->mt_next;
579 if (lnp == NULL)
580 _mt_list = nnp;
581 else
582 lnp->mt_next = nnp;
583 free(mtnp);
584 mtnp = nnp;
585 match = TRU1;
586 } else
587 lnp = mtnp, mtnp = mtnp->mt_next;
589 if (!match) {
590 if (!(pstate & PS_IN_LOAD) || (options & OPT_D_V))
591 n_err(_("No such MIME type: \"%s\"\n"), *argv);
592 v = NULL;
595 NYD_LEAVE;
596 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
599 FL char *
600 mime_type_by_filename(char const *name)
602 struct mtlookup mtl;
603 NYD_ENTER;
605 _mt_by_filename(&mtl, name, TRU1);
606 NYD_LEAVE;
607 return mtl.mtl_result;
610 FL enum conversion
611 mime_type_file_classify(FILE *fp, char const **contenttype,
612 char const **charset, int *do_iconv)
614 /* TODO classify once only PLEASE PLEASE PLEASE */
615 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
616 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
617 * TODO and report that state to the outer world */
618 #define F_ "From "
619 #define F_SIZEOF (sizeof(F_) -1)
621 char f_buf[F_SIZEOF], *f_p = f_buf;
622 enum {
623 _CLEAN = 0, /* Plain RFC 2822 message */
624 _NCTT = 1<<0, /* *contenttype == NULL */
625 _ISTXT = 1<<1, /* *contenttype =~ text/ */
626 _ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
627 _HIGHBIT = 1<<3, /* Not 7bit clean */
628 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
629 _CTRLCHAR = 1<<5, /* Control characters seen */
630 _HASNUL = 1<<6, /* Contains \0 characters */
631 _NOTERMNL = 1<<7, /* Lacks a final newline */
632 _FROM_ = 1<<8 /* ^From_ seen */
633 } ctt = _CLEAN;
634 enum mime_enc menc;
635 ssize_t curlen;
636 int c, lastc;
637 NYD_ENTER;
639 assert(ftell(fp) == 0x0l);
641 *do_iconv = 0;
643 if (*contenttype == NULL)
644 ctt = _NCTT;
645 else if (!ascncasecmp(*contenttype, "text/", 5))
646 ctt = ok_blook(mime_allow_text_controls) ? _ISTXT | _ISTXTCOK : _ISTXT;
648 menc = mime_enc_target();
650 if (fsize(fp) == 0)
651 goto j7bit;
653 /* We have to inspect the file content */
654 for (curlen = 0, c = EOF;; ++curlen) {
655 lastc = c;
656 c = getc(fp);
658 if (c == '\0') {
659 ctt |= _HASNUL;
660 if (!(ctt & _ISTXTCOK))
661 break;
662 continue;
664 if (c == '\n' || c == EOF) {
665 if (curlen >= MIME_LINELEN_LIMIT)
666 ctt |= _LONGLINES;
667 if (c == EOF)
668 break;
669 f_p = f_buf;
670 curlen = -1;
671 continue;
673 /* A bit hairy is handling of \r=\x0D=CR.
674 * RFC 2045, 6.7:
675 * Control characters other than TAB, or CR and LF as parts of CRLF
676 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
677 * we cannot peek the next character. Thus right here, inspect the last
678 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
679 /*else*/ if (lastc == '\r')
680 ctt |= _CTRLCHAR;
682 /* Control character? XXX this is all ASCII here */
683 if (c < 0x20 || c == 0x7F) {
684 /* RFC 2045, 6.7, as above ... */
685 if (c != '\t' && c != '\r')
686 ctt |= _CTRLCHAR;
687 /* If there is a escape sequence in backslash notation defined for
688 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
689 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
690 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
691 * \e=\x1B=ESC */
692 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
693 continue;
694 ctt |= _HASNUL; /* Force base64 */
695 if (!(ctt & _ISTXTCOK))
696 break;
697 } else if ((ui8_t)c & 0x80) {
698 ctt |= _HIGHBIT;
699 /* TODO count chars with HIGHBIT? libmagic?
700 * TODO try encode part - base64 if bails? */
701 if (!(ctt & (_NCTT | _ISTXT))) { /* TODO _NCTT?? */
702 ctt |= _HASNUL; /* Force base64 */
703 break;
705 } else if (!(ctt & _FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
706 *f_p++ = (char)c;
707 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
708 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
709 !memcmp(f_buf, F_, F_SIZEOF))
710 ctt |= _FROM_;
713 if (lastc != '\n')
714 ctt |= _NOTERMNL;
715 rewind(fp);
717 if (ctt & _HASNUL) {
718 menc = MIMEE_B64;
719 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
720 * on request; else enforce what file(1)/libmagic(3) would suggest */
721 if (ctt & _ISTXTCOK)
722 goto jcharset;
723 if (ctt & (_NCTT | _ISTXT))
724 *contenttype = "application/octet-stream";
725 if (*charset == NULL)
726 *charset = "binary";
727 goto jleave;
730 if (ctt & (_LONGLINES | _CTRLCHAR | _NOTERMNL | _FROM_)) {
731 if (menc != MIMEE_B64)
732 menc = MIMEE_QP;
733 goto jstepi;
735 if (ctt & _HIGHBIT) {
736 jstepi:
737 if (ctt & (_NCTT | _ISTXT))
738 *do_iconv = ((ctt & _HIGHBIT) != 0);
739 } else
740 j7bit:
741 menc = MIMEE_7B;
742 if (ctt & _NCTT)
743 *contenttype = "text/plain";
745 /* Not an attachment with specified charset? */
746 jcharset:
747 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
748 *charset = (ctt & _HIGHBIT) ? charset_iter_or_fallback()
749 : charset_get_7bit();
750 jleave:
751 NYD_LEAVE;
752 /* TODO mime_type_file_classify() shouldn't return conversion */
753 return (menc == MIMEE_7B ? CONV_7BIT :
754 (menc == MIMEE_8B ? CONV_8BIT :
755 (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
757 #undef F_
758 #undef F_SIZEOF
761 FL enum mimecontent
762 mime_type_mimepart_content(struct mimepart *mpp)
764 struct mtlookup mtl;
765 enum mimecontent mc;
766 char const *ct;
767 union {char const *cp; long l;} mce;
768 NYD_ENTER;
770 mc = MIME_UNKNOWN;
771 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
772 ct = "";
774 if ((mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
775 char *eptr;
776 long l;
778 l = strtol(mce.cp, &eptr, 0); /* XXX strtol */
779 mce.l = (*mce.cp == '\0' || *eptr != '\0' || l < 0) ? 0 : l | MIMECE_SET;
782 if (mce.l != 0 && mpp->m_filename != NULL) {
783 bool_t is_os = !asccasecmp(ct, "application/octet-stream");
785 if (is_os || (mce.l & MIMECE_ALL_OVWR)) {
786 if (_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL) {
787 if (is_os) {
788 if (mce.l & MIMECE_BIN_PARSE) {
789 /* TODO Code MIMECE_BIN_PARSE (*mime-counter-evidence* bit 4)
790 * TODO This requires v15 framework since we must decode the
791 * TODO single mimepart mpp to a temporary file in order to
792 * TODO inspect the content! */
794 goto jleave;
797 } else if (is_os || asccasecmp(ct, mtl.mtl_result)) {
798 if (mce.l & MIMECE_ALL_OVWR)
799 mpp->m_ct_type_plain = mtl.mtl_result;
800 if (mce.l & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
801 mpp->m_ct_type_usr_ovwr = mtl.mtl_result;
806 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
807 mc = MIME_TEXT;
808 else if (is_asccaseprefix(ct, "text/")) {
809 ct += sizeof("text/") -1;
810 if (!asccasecmp(ct, "plain"))
811 mc = MIME_TEXT_PLAIN;
812 else if (!asccasecmp(ct, "html"))
813 mc = MIME_TEXT_HTML;
814 else
815 mc = MIME_TEXT;
816 } else if (is_asccaseprefix(ct, "message/")) {
817 ct += sizeof("message/") -1;
818 if (!asccasecmp(ct, "rfc822"))
819 mc = MIME_822;
820 else
821 mc = MIME_MESSAGE;
822 } else if (!ascncasecmp(ct, "multipart/", 10)) {
823 ct += sizeof("multipart/") -1;
824 if (!asccasecmp(ct, "alternative"))
825 mc = MIME_ALTERNATIVE;
826 else if (!asccasecmp(ct, "related"))
827 mc = MIME_RELATED;
828 else if (!asccasecmp(ct, "digest"))
829 mc = MIME_DIGEST;
830 else
831 mc = MIME_MULTI;
832 } else if (is_asccaseprefix(ct, "application/")) {
833 ct += sizeof("application/") -1;
834 if (!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
835 mc = MIME_PKCS7;
837 jleave:
838 NYD_LEAVE;
839 return mc;
842 FL char const *
843 mime_type_mimepart_handler(struct mimepart const *mpp)
845 #define __S "pipe-"
846 #define __L (sizeof(__S) -1)
847 struct mtlookup mtl;
848 char const *es, *cs, *rv;
849 size_t el, cl, l;
850 char *buf, *cp;
851 NYD_ENTER;
853 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
854 *++es != '\0') ? strlen(es) : 0;
855 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
856 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
857 if ((l = MAX(el, cl)) == 0) {
859 /* FIXME here and below : another mime-counter-evidence bit, content check */
861 rv = NULL;
862 goto jleave;
865 buf = ac_alloc(__L + l +1);
866 memcpy(buf, __S, __L);
868 /* File-extension handlers take precedence.
869 * Yes, we really "fail" here for file extensions which clash MIME types */
870 if (el > 0) {
871 memcpy(buf + __L, es, el +1);
872 for (cp = buf + __L; *cp != '\0'; ++cp)
873 *cp = lowerconv(*cp);
875 if ((rv = vok_vlook(buf)) != NULL)
876 goto jok;
879 /* Then MIME Content-Type: */
880 if (cl > 0) {
881 memcpy(buf + __L, cs, cl +1);
882 for (cp = buf + __L; *cp != '\0'; ++cp)
883 *cp = lowerconv(*cp);
885 if ((rv = vok_vlook(buf)) != NULL)
886 goto jok;
888 if (_mt_by_mtname(&mtl, cs) != NULL)
889 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
890 #ifndef HAVE_FILTER_HTML_TAGSOUP
891 case _MT_SOUP_H:
892 #endif
893 default:
894 break;
895 case _MT_SOUP_h:
896 #ifdef HAVE_FILTER_HTML_TAGSOUP
897 case _MT_SOUP_H:
898 rv = MIME_TYPE_HANDLER_HTML;
899 goto jok;
900 #endif
901 case _MT_PLAIN:
902 rv = MIME_TYPE_HANDLER_TEXT;
903 goto jok;
907 rv = NULL;
908 jok:
909 ac_free(buf);
910 jleave:
911 NYD_LEAVE;
912 return rv;
913 #undef __L
914 #undef __S
917 /* s-it-mode */