url_parse(): better take better care for path parts (Stephen Isard)..
[s-mailx.git] / mime_types.c
blobb9608d7bff032b85258fe476ead00b3f4652687d
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 ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = name)) == 0 ||
356 memchr(name, '.', nlen) == NULL)
357 goto jnull_leave;
359 if (!_mt_is_init)
360 _mt_init();
362 /* ..all the MIME types */
363 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
364 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
365 cp = ext;
366 while (whitechar(*cp))
367 ++cp;
368 ext = cp;
369 while (!whitechar(*cp) && *cp != '\0')
370 ++cp;
372 if ((i = PTR2SIZE(cp - ext)) == 0)
373 break;
374 /* Don't allow neither of ".txt" or "txt" to match "txt" */
375 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
376 ascncasecmp(name + j, ext, i))
377 continue;
379 /* Found it */
380 mtlp->mtl_node = mtnp;
382 if (!with_result)
383 goto jleave;
385 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
386 name = "";
387 j = 0;
388 } else {
389 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
390 j = strlen(name);
392 i = mtnp->mt_mtlen;
393 mtlp->mtl_result = salloc(i + j +1);
394 if (j > 0)
395 memcpy(mtlp->mtl_result, name, j);
396 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
397 mtlp->mtl_result[j += i] = '\0';
398 goto jleave;
400 jnull_leave:
401 mtlp = NULL;
402 jleave:
403 NYD2_LEAVE;
404 return mtlp;
407 static struct mtlookup *
408 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
410 struct mtnode *mtnp;
411 size_t nlen, i, j;
412 char const *cp;
413 NYD2_ENTER;
415 memset(mtlp, 0, sizeof *mtlp);
417 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
418 goto jnull_leave;
420 if (!_mt_is_init)
421 _mt_init();
423 /* ..all the MIME types */
424 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
425 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
426 cp = "";
427 j = 0;
428 } else {
429 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
430 j = strlen(cp);
432 i = mtnp->mt_mtlen;
434 if (i + j == mtlp->mtl_nlen) {
435 char *xmt = ac_alloc(i + j +1);
436 if (j > 0)
437 memcpy(xmt, cp, j);
438 memcpy(xmt + j, mtnp->mt_line, i);
439 xmt[j += i] = '\0';
440 i = asccasecmp(mtname, xmt);
441 ac_free(xmt);
443 if (!i) {
444 /* Found it */
445 mtlp->mtl_node = mtnp;
446 goto jleave;
450 jnull_leave:
451 mtlp = NULL;
452 jleave:
453 NYD2_LEAVE;
454 return mtlp;
457 FL int
458 c_mimetype(void *v)
460 char **argv = v;
461 struct mtnode *mtnp;
462 NYD_ENTER;
464 if (!_mt_is_init)
465 _mt_init();
467 if (*argv == NULL) {
468 FILE *fp;
469 size_t l;
471 if (_mt_list == NULL) {
472 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
473 goto jleave;
476 if ((fp = Ftmp(NULL, "mimelist", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600))
477 == NULL) {
478 n_perr(_("tmpfile"), 0);
479 v = NULL;
480 goto jleave;
483 for (l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next) {
484 char const *tmark, *typ;
486 switch (mtnp->mt_flags & __MT_MARKMASK) {
487 case _MT_PLAIN: tmark = "/t"; break;
488 case _MT_SOUP_h: tmark = "/h"; break;
489 case _MT_SOUP_H: tmark = "/H"; break;
490 default: tmark = " "; break;
492 typ = ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER)
493 ? "" : _mt_typnames[mtnp->mt_flags & __MT_TMASK];
495 fprintf(fp, "%c%s %s%.*s <%s>\n",
496 (mtnp->mt_flags & _MT_USR ? 'U'
497 : (mtnp->mt_flags & _MT_SYS ? 'S'
498 : (mtnp->mt_flags & _MT_LOADED ? 'F' : 'B'))),
499 tmark, typ, (int)mtnp->mt_mtlen, mtnp->mt_line,
500 mtnp->mt_line + mtnp->mt_mtlen);
503 page_or_print(fp, l);
504 Fclose(fp);
505 } else {
506 for (; *argv != NULL; ++argv) {
507 mtnp = _mt_create(TRU1, _MT_LOADED, *argv, strlen(*argv));
508 if (mtnp != NULL) {
509 mtnp->mt_next = _mt_list;
510 _mt_list = mtnp;
511 } else
512 v = NULL;
515 jleave:
516 NYD_LEAVE;
517 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
520 FL int
521 c_unmimetype(void *v)
523 char **argv = v;
524 struct mtnode *lnp, *mtnp;
525 bool_t match;
526 NYD_ENTER;
528 /* Need to load that first as necessary */
529 if (!_mt_is_init)
530 _mt_init();
532 for (; *argv != NULL; ++argv) {
533 if (!asccasecmp(*argv, "reset")) {
534 _mt_is_init = FAL0;
535 goto jdelall;
538 if (argv[0][0] == '*' && argv[0][1] == '\0') {
539 jdelall:
540 while ((mtnp = _mt_list) != NULL) {
541 _mt_list = mtnp->mt_next;
542 free(mtnp);
544 continue;
547 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
548 char const *typ;
549 char *val;
550 size_t i;
552 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
553 typ = "";
554 i = 0;
555 } else {
556 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
557 i = strlen(typ);
560 val = ac_alloc(i + mtnp->mt_mtlen +1);
561 memcpy(val, typ, i);
562 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
563 val[i += mtnp->mt_mtlen] = '\0';
564 i = asccasecmp(val, *argv);
565 ac_free(val);
567 if (!i) {
568 struct mtnode *nnp = mtnp->mt_next;
569 if (lnp == NULL)
570 _mt_list = nnp;
571 else
572 lnp->mt_next = nnp;
573 free(mtnp);
574 mtnp = nnp;
575 match = TRU1;
576 } else
577 lnp = mtnp, mtnp = mtnp->mt_next;
579 if (!match) {
580 if (!(pstate & PS_IN_LOAD) || (options & OPT_D_V))
581 n_err(_("No such MIME type: \"%s\"\n"), *argv);
582 v = NULL;
585 NYD_LEAVE;
586 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
589 FL char *
590 mime_type_by_filename(char const *name)
592 struct mtlookup mtl;
593 NYD_ENTER;
595 _mt_by_filename(&mtl, name, TRU1);
596 NYD_LEAVE;
597 return mtl.mtl_result;
600 FL enum conversion
601 mime_type_file_classify(FILE *fp, char const **contenttype,
602 char const **charset, int *do_iconv)
604 /* TODO classify once only PLEASE PLEASE PLEASE */
605 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
606 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
607 * TODO and report that state to the outer world */
608 #define F_ "From "
609 #define F_SIZEOF (sizeof(F_) -1)
611 char f_buf[F_SIZEOF], *f_p = f_buf;
612 enum {
613 _CLEAN = 0, /* Plain RFC 2822 message */
614 _NCTT = 1<<0, /* *contenttype == NULL */
615 _ISTXT = 1<<1, /* *contenttype =~ text/ */
616 _ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
617 _HIGHBIT = 1<<3, /* Not 7bit clean */
618 _LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
619 _CTRLCHAR = 1<<5, /* Control characters seen */
620 _HASNUL = 1<<6, /* Contains \0 characters */
621 _NOTERMNL = 1<<7, /* Lacks a final newline */
622 _FROM_ = 1<<8 /* ^From_ seen */
623 } ctt = _CLEAN;
624 enum mime_enc menc;
625 ssize_t curlen;
626 int c, lastc;
627 NYD_ENTER;
629 assert(ftell(fp) == 0x0l);
631 *do_iconv = 0;
633 if (*contenttype == NULL)
634 ctt = _NCTT;
635 else if (!ascncasecmp(*contenttype, "text/", 5))
636 ctt = ok_blook(mime_allow_text_controls) ? _ISTXT | _ISTXTCOK : _ISTXT;
638 menc = mime_enc_target();
640 if (fsize(fp) == 0)
641 goto j7bit;
643 /* We have to inspect the file content */
644 for (curlen = 0, c = EOF;; ++curlen) {
645 lastc = c;
646 c = getc(fp);
648 if (c == '\0') {
649 ctt |= _HASNUL;
650 if (!(ctt & _ISTXTCOK))
651 break;
652 continue;
654 if (c == '\n' || c == EOF) {
655 if (curlen >= MIME_LINELEN_LIMIT)
656 ctt |= _LONGLINES;
657 if (c == EOF)
658 break;
659 f_p = f_buf;
660 curlen = -1;
661 continue;
663 /* A bit hairy is handling of \r=\x0D=CR.
664 * RFC 2045, 6.7:
665 * Control characters other than TAB, or CR and LF as parts of CRLF
666 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
667 * we cannot peek the next character. Thus right here, inspect the last
668 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
669 /*else*/ if (lastc == '\r')
670 ctt |= _CTRLCHAR;
672 /* Control character? XXX this is all ASCII here */
673 if (c < 0x20 || c == 0x7F) {
674 /* RFC 2045, 6.7, as above ... */
675 if (c != '\t' && c != '\r')
676 ctt |= _CTRLCHAR;
677 /* If there is a escape sequence in backslash notation defined for
678 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
679 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
680 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
681 * \e=\x1B=ESC */
682 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
683 continue;
684 ctt |= _HASNUL; /* Force base64 */
685 if (!(ctt & _ISTXTCOK))
686 break;
687 } else if ((ui8_t)c & 0x80) {
688 ctt |= _HIGHBIT;
689 /* TODO count chars with HIGHBIT? libmagic?
690 * TODO try encode part - base64 if bails? */
691 if (!(ctt & (_NCTT | _ISTXT))) { /* TODO _NCTT?? */
692 ctt |= _HASNUL; /* Force base64 */
693 break;
695 } else if (!(ctt & _FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
696 *f_p++ = (char)c;
697 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
698 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
699 !memcmp(f_buf, F_, F_SIZEOF))
700 ctt |= _FROM_;
703 if (lastc != '\n')
704 ctt |= _NOTERMNL;
705 rewind(fp);
707 if (ctt & _HASNUL) {
708 menc = MIMEE_B64;
709 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
710 * on request; else enforce what file(1)/libmagic(3) would suggest */
711 if (ctt & _ISTXTCOK)
712 goto jcharset;
713 if (ctt & (_NCTT | _ISTXT))
714 *contenttype = "application/octet-stream";
715 if (*charset == NULL)
716 *charset = "binary";
717 goto jleave;
720 if (ctt & (_LONGLINES | _CTRLCHAR | _NOTERMNL | _FROM_)) {
721 if (menc != MIMEE_B64)
722 menc = MIMEE_QP;
723 goto jstepi;
725 if (ctt & _HIGHBIT) {
726 jstepi:
727 if (ctt & (_NCTT | _ISTXT))
728 *do_iconv = ((ctt & _HIGHBIT) != 0);
729 } else
730 j7bit:
731 menc = MIMEE_7B;
732 if (ctt & _NCTT)
733 *contenttype = "text/plain";
735 /* Not an attachment with specified charset? */
736 jcharset:
737 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
738 *charset = (ctt & _HIGHBIT) ? charset_iter_or_fallback()
739 : charset_get_7bit();
740 jleave:
741 NYD_LEAVE;
742 /* TODO mime_type_file_classify() shouldn't return conversion */
743 return (menc == MIMEE_7B ? CONV_7BIT :
744 (menc == MIMEE_8B ? CONV_8BIT :
745 (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
747 #undef F_
748 #undef F_SIZEOF
751 FL enum mimecontent
752 mime_type_mimepart_content(struct mimepart *mpp)
754 struct mtlookup mtl;
755 enum mimecontent mc;
756 char const *ct;
757 union {char const *cp; long l;} mce;
758 NYD_ENTER;
760 mc = MIME_UNKNOWN;
761 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
762 ct = "";
764 if ((mce.cp = ok_vlook(mime_counter_evidence)) != NULL) {
765 char *eptr;
766 long l;
768 l = strtol(mce.cp, &eptr, 0); /* XXX strtol */
769 mce.l = (*mce.cp == '\0' || *eptr != '\0' || l < 0) ? 0 : l | MIMECE_SET;
772 if (mce.l != 0 && mpp->m_filename != NULL) {
773 bool_t is_os = !asccasecmp(ct, "application/octet-stream");
775 if (is_os || (mce.l & MIMECE_ALL_OVWR)) {
776 if (_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL) {
777 if (is_os) {
778 if (mce.l & MIMECE_BIN_PARSE) {
779 /* TODO Code MIMECE_BIN_PARSE (*mime-counter-evidence* bit 4)
780 * TODO This requires v15 framework since we must decode the
781 * TODO single mimepart mpp to a temporary file in order to
782 * TODO inspect the content! */
784 goto jleave;
787 } else if (is_os || asccasecmp(ct, mtl.mtl_result)) {
788 if (mce.l & MIMECE_ALL_OVWR)
789 mpp->m_ct_type_plain = mtl.mtl_result;
790 if (mce.l & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
791 mpp->m_ct_type_usr_ovwr = mtl.mtl_result;
796 if (strchr(ct, '/') == NULL) /* For compatibility with non-MIME */
797 mc = MIME_TEXT;
798 else if (is_asccaseprefix(ct, "text/")) {
799 ct += sizeof("text/") -1;
800 if (!asccasecmp(ct, "plain"))
801 mc = MIME_TEXT_PLAIN;
802 else if (!asccasecmp(ct, "html"))
803 mc = MIME_TEXT_HTML;
804 else
805 mc = MIME_TEXT;
806 } else if (is_asccaseprefix(ct, "message/")) {
807 ct += sizeof("message/") -1;
808 if (!asccasecmp(ct, "rfc822"))
809 mc = MIME_822;
810 else
811 mc = MIME_MESSAGE;
812 } else if (!ascncasecmp(ct, "multipart/", 10)) {
813 ct += sizeof("multipart/") -1;
814 if (!asccasecmp(ct, "alternative"))
815 mc = MIME_ALTERNATIVE;
816 else if (!asccasecmp(ct, "related"))
817 mc = MIME_RELATED;
818 else if (!asccasecmp(ct, "digest"))
819 mc = MIME_DIGEST;
820 else
821 mc = MIME_MULTI;
822 } else if (is_asccaseprefix(ct, "application/")) {
823 ct += sizeof("application/") -1;
824 if (!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
825 mc = MIME_PKCS7;
827 jleave:
828 NYD_LEAVE;
829 return mc;
832 FL char const *
833 mime_type_mimepart_handler(struct mimepart const *mpp)
835 #define __S "pipe-"
836 #define __L (sizeof(__S) -1)
837 struct mtlookup mtl;
838 char const *es, *cs, *rv;
839 size_t el, cl, l;
840 char *buf, *cp;
841 NYD_ENTER;
843 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
844 *++es != '\0') ? strlen(es) : 0;
845 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
846 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
847 if ((l = MAX(el, cl)) == 0) {
849 /* FIXME here and below : another mime-counter-evidence bit, content check */
851 rv = NULL;
852 goto jleave;
855 buf = ac_alloc(__L + l +1);
856 memcpy(buf, __S, __L);
858 /* File-extension handlers take precedence.
859 * Yes, we really "fail" here for file extensions which clash MIME types */
860 if (el > 0) {
861 memcpy(buf + __L, es, el +1);
862 for (cp = buf + __L; *cp != '\0'; ++cp)
863 *cp = lowerconv(*cp);
865 if ((rv = vok_vlook(buf)) != NULL)
866 goto jok;
869 /* Then MIME Content-Type: */
870 if (cl > 0) {
871 memcpy(buf + __L, cs, cl +1);
872 for (cp = buf + __L; *cp != '\0'; ++cp)
873 *cp = lowerconv(*cp);
875 if ((rv = vok_vlook(buf)) != NULL)
876 goto jok;
878 if (_mt_by_mtname(&mtl, cs) != NULL)
879 switch (mtl.mtl_node->mt_flags & __MT_MARKMASK) {
880 #ifndef HAVE_FILTER_HTML_TAGSOUP
881 case _MT_SOUP_H:
882 #endif
883 default:
884 break;
885 case _MT_SOUP_h:
886 #ifdef HAVE_FILTER_HTML_TAGSOUP
887 case _MT_SOUP_H:
888 rv = MIME_TYPE_HANDLER_HTML;
889 goto jok;
890 #endif
891 case _MT_PLAIN:
892 rv = MIME_TYPE_HANDLER_TEXT;
893 goto jok;
897 rv = NULL;
898 jok:
899 ac_free(buf);
900 jleave:
901 NYD_LEAVE;
902 return rv;
903 #undef __L
904 #undef __S
907 /* s-it-mode */