make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / mime-types.c
blobbba91f25a91feb0e6b1115e0068d3b8f22d00a35
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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: ISC
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #undef n_FILE
21 #define n_FILE mime_types
23 #ifndef HAVE_AMALGAMATION
24 # include "nail.h"
25 #endif
27 enum mime_type {
28 _MT_APPLICATION,
29 _MT_AUDIO,
30 _MT_IMAGE,
31 _MT_MESSAGE,
32 _MT_MULTIPART,
33 _MT_TEXT,
34 _MT_VIDEO,
35 _MT_OTHER,
36 __MT_TMIN = 0u,
37 __MT_TMAX = _MT_OTHER,
38 __MT_TMASK = 0x07u,
40 _MT_CMD = 1u<< 8, /* Via `mimetype' (not struct mtbltin) */
41 _MT_USR = 1u<< 9, /* VAL_MIME_TYPES_USR */
42 _MT_SYS = 1u<<10, /* VAL_MIME_TYPES_SYS */
43 _MT_FSPEC = 1u<<11, /* Loaded via f= *mimetypes-load-control* spec. */
45 a_MT_TM_PLAIN = 1u<<16, /* Without pipe handler display as text */
46 a_MT_TM_SOUP_h = 2u<<16, /* Ditto, but HTML tagsoup parser if possible */
47 a_MT_TM_SOUP_H = 3u<<16, /* HTML tagsoup parser, else NOT plain text */
48 a_MT_TM_QUIET = 4u<<16, /* No "no mime handler available" message */
49 a_MT__TM_MARKMASK = 7u<<16
52 enum mime_type_class {
53 _MT_C_NONE,
54 _MT_C_CLEAN = _MT_C_NONE, /* Plain RFC 5322 message */
55 _MT_C_DEEP_INSPECT = 1u<<0, /* Always test all the file */
56 _MT_C_NCTT = 1u<<1, /* *contenttype == NULL */
57 _MT_C_ISTXT = 1u<<2, /* *contenttype =~ text\/ */
58 _MT_C_ISTXTCOK = 1u<<3, /* _ISTXT + *mime-allow-text-controls* */
59 _MT_C_HIGHBIT = 1u<<4, /* Not 7bit clean */
60 _MT_C_LONGLINES = 1u<<5, /* MIME_LINELEN_LIMIT exceed. */
61 _MT_C_CTRLCHAR = 1u<<6, /* Control characters seen */
62 _MT_C_HASNUL = 1u<<7, /* Contains \0 characters */
63 _MT_C_NOTERMNL = 1u<<8, /* Lacks a final newline */
64 _MT_C_FROM_ = 1u<<9, /* ^From_ seen */
65 _MT_C_FROM_1STLINE = 1u<<10, /* From_ line seen */
66 _MT_C_SUGGEST_DONE = 1u<<16, /* Inspector suggests to stop further parse */
67 _MT_C__1STLINE = 1u<<17 /* .. */
70 struct mtbltin {
71 ui32_t mtb_flags;
72 ui32_t mtb_mtlen;
73 char const *mtb_line;
76 struct mtnode {
77 struct mtnode *mt_next;
78 ui32_t mt_flags;
79 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
80 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
81 * that could already store character data here */
82 char const *mt_line;
85 struct mtlookup {
86 char const *mtl_name;
87 size_t mtl_nlen;
88 struct mtnode const *mtl_node;
89 char *mtl_result; /* If requested, salloc()ed MIME type */
92 struct mt_class_arg {
93 char const *mtca_buf;
94 size_t mtca_len;
95 ssize_t mtca_curlnlen;
96 /*char mtca_lastc;*/
97 char mtca_c;
98 ui8_t mtca__dummy[3];
99 enum mime_type_class mtca_mtc;
100 ui64_t mtca_all_len;
101 ui64_t mtca_all_highbit; /* TODO not yet interpreted */
102 ui64_t mtca_all_bogus;
105 static struct mtbltin const _mt_bltin[] = {
106 #include <gen-mime-types.h>
109 static char const _mt_typnames[][16] = {
110 "application/", "audio/", "image/",
111 "message/", "multipart/", "text/",
112 "video/"
114 n_CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
115 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
116 _MT_VIDEO == 6);
118 /* */
119 static bool_t _mt_is_init;
120 static struct mtnode *_mt_list;
122 /* Initialize MIME type list in order */
123 static void _mt_init(void);
124 static bool_t __mt_load_file(ui32_t orflags,
125 char const *file, char **line, size_t *linesize);
127 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
128 * for `mimetype' */
129 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
130 char const *line, size_t len);
132 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
133 * if with_result >mtl_result will be created upon success for the former */
134 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
135 char const *name, bool_t with_result);
136 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
137 char const *mtname);
139 /* In-depth inspection of raw content: call _round() repeatedly, last time with
140 * a 0 length buffer, finally check .mtca_mtc for result.
141 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
142 * as the resulting classification is unambiguous */
143 n_INLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
144 enum mime_type_class initval);
145 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
147 /* We need an in-depth inspection of an application/octet-stream part */
148 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp,
149 bool_t deep_inspect);
151 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
152 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
153 * isn't changed if mhp doesn't apply */
154 static enum mime_handler_flags a_mt_pipe_check(struct mime_handler *mhp);
156 static void
157 _mt_init(void)
159 struct mtnode *tail;
160 char c, *line; /* TODO line pool (below) */
161 size_t linesize;
162 ui32_t i, j;
163 char const *srcs_arr[10], *ccp, **srcs;
164 NYD_ENTER;
166 /*if (_mt_is_init)
167 * goto jleave;*/
169 /* Always load our built-ins */
170 for (tail = NULL, i = 0; i < n_NELEM(_mt_bltin); ++i) {
171 struct mtbltin const *mtbp = _mt_bltin + i;
172 struct mtnode *mtnp = n_alloc(sizeof *mtnp);
174 if (tail != NULL)
175 tail->mt_next = mtnp;
176 else
177 _mt_list = mtnp;
178 tail = mtnp;
179 mtnp->mt_next = NULL;
180 mtnp->mt_flags = mtbp->mtb_flags;
181 mtnp->mt_mtlen = mtbp->mtb_mtlen;
182 mtnp->mt_line = mtbp->mtb_line;
185 /* Decide which files sources have to be loaded */
186 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
187 ccp = "US";
188 else if (*ccp == '\0')
189 goto jleave;
191 srcs = srcs_arr + 2;
192 srcs[-1] = srcs[-2] = NULL;
194 if (strchr(ccp, '=') != NULL) {
195 line = savestr(ccp);
197 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
198 switch ((c = *ccp)) {
199 case 'S': case 's':
200 srcs_arr[1] = VAL_MIME_TYPES_SYS;
201 if (0) {
202 /* FALLTHRU */
203 case 'U': case 'u':
204 srcs_arr[0] = VAL_MIME_TYPES_USR;
206 if (ccp[1] != '\0')
207 goto jecontent;
208 break;
209 case 'F': case 'f':
210 if (*++ccp == '=' && *++ccp != '\0') {
211 if (PTR2SIZE(srcs - srcs_arr) < n_NELEM(srcs_arr))
212 *srcs++ = ccp;
213 else
214 n_err(_("*mimetypes-load-control*: too many sources, "
215 "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
216 continue;
218 /* FALLTHRU */
219 default:
220 goto jecontent;
223 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
224 switch (c) {
225 case 'S': case 's': srcs_arr[1] = VAL_MIME_TYPES_SYS; break;
226 case 'U': case 'u': srcs_arr[0] = VAL_MIME_TYPES_USR; break;
227 default:
228 jecontent:
229 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp);
230 goto jleave;
233 /* Load all file-based sources in the desired order */
234 line = NULL;
235 linesize = 0;
236 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
237 i > 0; ++j, ++srcs, --i)
238 if (*srcs == NULL)
239 continue;
240 else if (!__mt_load_file((j == 0 ? _MT_USR
241 : (j == 1 ? _MT_SYS : _MT_FSPEC)), *srcs, &line, &linesize)) {
242 if ((n_poption & n_PO_D_V) || j > 1)
243 n_err(_("*mimetypes-load-control*: cannot open or load %s\n"),
244 n_shexp_quote_cp(*srcs, FAL0));
246 if (line != NULL)
247 n_free(line);
248 jleave:
249 _mt_is_init = TRU1;
250 NYD_LEAVE;
253 static bool_t
254 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
256 char const *cp;
257 FILE *fp;
258 struct mtnode *head, *tail, *mtnp;
259 size_t len;
260 NYD_ENTER;
262 if ((cp = fexpand(file, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
263 (fp = Fopen(cp, "r")) == NULL) {
264 cp = NULL;
265 goto jleave;
268 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
269 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
270 if (head == NULL)
271 head = tail = mtnp;
272 else
273 tail->mt_next = mtnp;
274 tail = mtnp;
276 if (head != NULL) {
277 tail->mt_next = _mt_list;
278 _mt_list = head;
281 Fclose(fp);
282 jleave:
283 NYD_LEAVE;
284 return (cp != NULL);
287 static struct mtnode *
288 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
290 struct mtnode *mtnp;
291 char const *typ, *subtyp;
292 size_t tlen, i;
293 NYD_ENTER;
295 mtnp = NULL;
297 /* Drop anything after a comment first TODO v15: only when read from file */
298 if ((typ = memchr(line, '#', len)) != NULL)
299 len = PTR2SIZE(typ - line);
301 /* Then trim any trailing whitespace from line (including NL/CR) */
302 /* C99 */{
303 struct str work;
305 work.s = n_UNCONST(line);
306 work.l = len;
307 line = n_str_trim(&work, n_STR_TRIM_BOTH)->s;
308 len = work.l;
310 typ = line;
312 /* (But wait - is there a type marker?) */
313 tlen = len;
314 if(!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@'){
315 if(len < 2)
316 goto jeinval;
317 if(typ[1] == ' '){
318 orflags |= a_MT_TM_PLAIN;
319 typ += 2;
320 len -= 2;
321 line += 2;
322 }else if(len > 3){
323 if(typ[2] == ' ')
324 i = 3;
325 else if(len > 4 && typ[2] == '@' && typ[3] == ' '){
326 n_OBSOLETE("`mimetype': the trailing \"@\" in \"type-marker\" "
327 "is redundant");
328 i = 4;
329 }else
330 goto jeinval;
332 switch(typ[1]){
333 default: goto jeinval;
334 case 't': orflags |= a_MT_TM_PLAIN; break;
335 case 'h': orflags |= a_MT_TM_SOUP_h; break;
336 case 'H': orflags |= a_MT_TM_SOUP_H; break;
337 case 'q': orflags |= a_MT_TM_QUIET; break;
339 typ += i;
340 len -= i;
341 line += i;
342 }else
343 goto jeinval;
346 while (len > 0 && !blankchar(*line))
347 ++line, --len;
348 /* Ignore empty lines and even incomplete specifications (only MIME type)
349 * because this is quite common in mime.types(5) files */
350 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
351 if (cmdcalled || (orflags & _MT_FSPEC)) {
352 if(len == 0){
353 line = _("(no value)");
354 len = strlen(line);
356 n_err(_("Empty MIME type or no extensions given: %.*s\n"),
357 (int)len, line);
359 goto jleave;
362 if ((subtyp = memchr(typ, '/', tlen)) == NULL || subtyp[1] == '\0' ||
363 spacechar(subtyp[1])) {
364 jeinval:
365 if(cmdcalled || (orflags & _MT_FSPEC) || (n_poption & n_PO_D_V))
366 n_err(_("%s MIME type: %.*s\n"),
367 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")),
368 (int)tlen, typ);
369 goto jleave;
371 ++subtyp;
373 /* Map to mime_type */
374 tlen = PTR2SIZE(subtyp - typ);
375 for (i = __MT_TMIN;;) {
376 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
377 orflags |= i;
378 tlen = PTR2SIZE(line - subtyp);
379 typ = subtyp;
380 break;
382 if (++i == __MT_TMAX) {
383 orflags |= _MT_OTHER;
384 tlen = PTR2SIZE(line - typ);
385 break;
389 /* Strip leading whitespace from the list of extensions;
390 * trailing WS has already been trimmed away above.
391 * Be silent on slots which define a mimetype without any value */
392 while (len > 0 && blankchar(*line))
393 ++line, --len;
394 if (len == 0)
395 goto jleave;
397 /* */
398 mtnp = n_alloc(sizeof(*mtnp) + tlen + len +1);
399 mtnp->mt_next = NULL;
400 mtnp->mt_flags = orflags;
401 mtnp->mt_mtlen = (ui32_t)tlen;
402 { char *l = (char*)(mtnp + 1);
403 mtnp->mt_line = l;
404 memcpy(l, typ, tlen);
405 memcpy(l + tlen, line, len);
406 tlen += len;
407 l[tlen] = '\0';
410 jleave:
411 NYD_LEAVE;
412 return mtnp;
415 static struct mtlookup *
416 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
418 struct mtnode *mtnp;
419 size_t nlen, i, j;
420 char const *ext, *cp;
421 NYD2_ENTER;
423 memset(mtlp, 0, sizeof *mtlp);
425 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
426 goto jnull_leave;
427 /* We need a period TODO we should support names like README etc. */
428 for (i = nlen; name[--i] != '.';)
429 if (i == 0 || name[i] == '/') /* XXX no magics */
430 goto jnull_leave;
431 /* While here, basename() it */
432 while (i > 0 && name[i - 1] != '/')
433 --i;
434 name += i;
435 nlen -= i;
436 mtlp->mtl_name = name;
437 mtlp->mtl_nlen = nlen;
439 if (!_mt_is_init)
440 _mt_init();
442 /* ..all the MIME types */
443 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
444 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
445 cp = ext;
446 while (whitechar(*cp))
447 ++cp;
448 ext = cp;
449 while (!whitechar(*cp) && *cp != '\0')
450 ++cp;
452 if ((i = PTR2SIZE(cp - ext)) == 0)
453 break;
454 /* Don't allow neither of ".txt" or "txt" to match "txt" */
455 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
456 ascncasecmp(name + j, ext, i))
457 continue;
459 /* Found it */
460 mtlp->mtl_node = mtnp;
462 if (!with_result)
463 goto jleave;
465 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
466 name = n_empty;
467 j = 0;
468 } else {
469 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
470 j = strlen(name);
472 i = mtnp->mt_mtlen;
473 mtlp->mtl_result = n_autorec_alloc(i + j +1);
474 if (j > 0)
475 memcpy(mtlp->mtl_result, name, j);
476 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
477 mtlp->mtl_result[j += i] = '\0';
478 goto jleave;
480 jnull_leave:
481 mtlp = NULL;
482 jleave:
483 NYD2_LEAVE;
484 return mtlp;
487 static struct mtlookup *
488 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
490 struct mtnode *mtnp;
491 size_t nlen, i, j;
492 char const *cp;
493 NYD2_ENTER;
495 memset(mtlp, 0, sizeof *mtlp);
497 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
498 goto jnull_leave;
500 if (!_mt_is_init)
501 _mt_init();
503 /* ..all the MIME types */
504 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
505 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
506 cp = n_empty;
507 j = 0;
508 } else {
509 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
510 j = strlen(cp);
512 i = mtnp->mt_mtlen;
514 if (i + j == mtlp->mtl_nlen) {
515 char *xmt = n_lofi_alloc(i + j +1);
516 if (j > 0)
517 memcpy(xmt, cp, j);
518 memcpy(xmt + j, mtnp->mt_line, i);
519 xmt[j += i] = '\0';
520 i = asccasecmp(mtname, xmt);
521 n_lofi_free(xmt);
523 if (!i) {
524 /* Found it */
525 mtlp->mtl_node = mtnp;
526 goto jleave;
530 jnull_leave:
531 mtlp = NULL;
532 jleave:
533 NYD2_LEAVE;
534 return mtlp;
537 n_INLINE struct mt_class_arg *
538 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
540 NYD2_ENTER;
541 memset(mtcap, 0, sizeof *mtcap);
542 /*mtcap->mtca_lastc =*/ mtcap->mtca_c = EOF;
543 mtcap->mtca_mtc = initval | _MT_C__1STLINE;
544 NYD2_LEAVE;
545 return mtcap;
548 static enum mime_type_class
549 _mt_classify_round(struct mt_class_arg *mtcap) /* TODO dig UTF-8 for !text/!! */
551 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
552 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
553 * TODO and report that state to the outer world */
554 #define F_ "From "
555 #define F_SIZEOF (sizeof(F_) -1)
556 char f_buf[F_SIZEOF], *f_p = f_buf;
557 char const *buf;
558 size_t blen;
559 ssize_t curlnlen;
560 si64_t alllen;
561 int c, lastc;
562 enum mime_type_class mtc;
563 NYD2_ENTER;
565 buf = mtcap->mtca_buf;
566 blen = mtcap->mtca_len;
567 curlnlen = mtcap->mtca_curlnlen;
568 alllen = mtcap->mtca_all_len;
569 c = mtcap->mtca_c;
570 /*lastc = mtcap->mtca_lastc;*/
571 mtc = mtcap->mtca_mtc;
573 for (;; ++curlnlen) {
574 if(blen == 0){
575 /* Real EOF, or only current buffer end? */
576 if(mtcap->mtca_len == 0){
577 lastc = c;
578 c = EOF;
579 }else{
580 lastc = EOF;
581 break;
583 }else{
584 ++alllen;
585 lastc = c;
586 c = (uc_i)*buf++;
588 --blen;
590 if (c == '\0') {
591 mtc |= _MT_C_HASNUL;
592 if (!(mtc & _MT_C_ISTXTCOK)) {
593 mtc |= _MT_C_SUGGEST_DONE;
594 break;
596 continue;
598 if (c == '\n' || c == EOF) {
599 mtc &= ~_MT_C__1STLINE;
600 if (curlnlen >= MIME_LINELEN_LIMIT)
601 mtc |= _MT_C_LONGLINES;
602 if (c == EOF)
603 break;
604 f_p = f_buf;
605 curlnlen = -1;
606 continue;
608 /* A bit hairy is handling of \r=\x0D=CR.
609 * RFC 2045, 6.7:
610 * Control characters other than TAB, or CR and LF as parts of CRLF
611 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
612 * we cannot peek the next character. Thus right here, inspect the last
613 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
614 /*else*/ if (lastc == '\r')
615 mtc |= _MT_C_CTRLCHAR;
617 /* Control character? XXX this is all ASCII here */
618 if (c < 0x20 || c == 0x7F) {
619 /* RFC 2045, 6.7, as above ... */
620 if (c != '\t' && c != '\r')
621 mtc |= _MT_C_CTRLCHAR;
623 /* If there is a escape sequence in reverse solidus notation defined
624 * for this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
625 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
626 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
627 * \e=\x1B=ESC */
628 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
629 continue;
631 /* As a special case, if we are going for displaying data to the user
632 * or quoting a message then simply continue this, in the end, in case
633 * we get there, we will decide upon the all_len/all_bogus ratio
634 * whether this is usable plain text or not */
635 ++mtcap->mtca_all_bogus;
636 if(mtc & _MT_C_DEEP_INSPECT)
637 continue;
639 mtc |= _MT_C_HASNUL; /* Force base64 */
640 if (!(mtc & _MT_C_ISTXTCOK)) {
641 mtc |= _MT_C_SUGGEST_DONE;
642 break;
644 } else if ((ui8_t)c & 0x80) {
645 mtc |= _MT_C_HIGHBIT;
646 ++mtcap->mtca_all_highbit;
647 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
648 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
649 break;
651 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlnlen, <, F_SIZEOF)) {
652 *f_p++ = (char)c;
653 if (UICMP(z, curlnlen, ==, F_SIZEOF - 1) &&
654 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
655 !memcmp(f_buf, F_, F_SIZEOF)){
656 mtc |= _MT_C_FROM_;
657 if (mtc & _MT_C__1STLINE)
658 mtc |= _MT_C_FROM_1STLINE;
662 if (c == EOF && lastc != '\n')
663 mtc |= _MT_C_NOTERMNL;
665 mtcap->mtca_curlnlen = curlnlen;
666 /*mtcap->mtca_lastc = lastc*/;
667 mtcap->mtca_c = c;
668 mtcap->mtca_mtc = mtc;
669 mtcap->mtca_all_len = alllen;
670 NYD2_LEAVE;
671 return mtc;
672 #undef F_
673 #undef F_SIZEOF
676 static enum mimecontent
677 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp, bool_t deep_inspect)
679 struct str in = {NULL, 0}, outrest, inrest, dec;
680 struct mt_class_arg mtca;
681 bool_t did_inrest;
682 enum mime_type_class mtc;
683 int lc, c;
684 size_t cnt, lsz;
685 FILE *ibuf;
686 off_t start_off;
687 enum mimecontent mc;
688 NYD2_ENTER;
690 assert(mpp->m_mime_enc != MIMEE_BIN);
692 outrest = inrest = dec = in;
693 mc = MIME_UNKNOWN;
694 n_UNINIT(mtc, 0);
695 did_inrest = FAL0;
697 /* TODO v15-compat Note we actually bypass our usual file handling by
698 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
699 * TODO all of this, and until then doing it like this is the only option
700 * TODO to integrate nicely into whoever calls us */
701 start_off = ftell(mb.mb_itf);
702 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
703 jos_leave:
704 fseek(mb.mb_itf, start_off, SEEK_SET);
705 goto jleave;
707 cnt = mpp->m_size;
709 /* Skip part headers */
710 for (lc = '\0'; cnt > 0; lc = c, --cnt)
711 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
712 break;
713 if (cnt == 0 || ferror(ibuf))
714 goto jos_leave;
716 /* So now let's inspect the part content, decoding content-transfer-encoding
717 * along the way TODO this should simply be "mime_factory_create(MPP)"!
718 * TODO In fact m_mime_classifier_(setup|call|call_part|finalize)() and the
719 * TODO state(s) (the _MT_C states) should become reported to the outer
720 * TODO world like that (see MIME boundary TODO around here) */
721 _mt_classify_init(&mtca, (_MT_C_ISTXT |
722 (deep_inspect ? _MT_C_DEEP_INSPECT : _MT_C_NONE)));
724 for (lsz = 0;;) {
725 bool_t dobuf;
727 c = (--cnt == 0) ? EOF : getc(ibuf);
728 if ((dobuf = (c == '\n'))) {
729 /* Ignore empty lines */
730 if (lsz == 0)
731 continue;
732 } else if ((dobuf = (c == EOF))) {
733 if (lsz == 0 && outrest.l == 0)
734 break;
737 if (in.l + 1 >= lsz)
738 in.s = n_realloc(in.s, lsz += LINESIZE);
739 if (c != EOF)
740 in.s[in.l++] = (char)c;
741 if (!dobuf)
742 continue;
744 jdobuf:
745 switch (mpp->m_mime_enc) {
746 case MIMEE_B64:
747 if (!b64_decode_part(&dec, &in, &outrest,
748 (did_inrest ? NULL : &inrest))) {
749 mtca.mtca_mtc = _MT_C_HASNUL;
750 goto jstopit; /* break;break; */
752 break;
753 case MIMEE_QP:
754 /* Drin */
755 if (!qp_decode_part(&dec, &in, &outrest, &inrest)) {
756 mtca.mtca_mtc = _MT_C_HASNUL;
757 goto jstopit; /* break;break; */
759 if (dec.l == 0 && c != EOF) {
760 in.l = 0;
761 continue;
763 break;
764 default:
765 /* Temporarily switch those two buffers.. */
766 dec = in;
767 in.s = NULL;
768 in.l = 0;
769 break;
772 mtca.mtca_buf = dec.s;
773 mtca.mtca_len = (ssize_t)dec.l;
774 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
775 mtc = _MT_C_HASNUL;
776 break;
779 if (c == EOF)
780 break;
781 /* ..and restore switched */
782 if (in.s == NULL) {
783 in = dec;
784 dec.s = NULL;
786 in.l = dec.l = 0;
789 if ((in.l = inrest.l) > 0) {
790 in.s = inrest.s;
791 inrest.s = NULL;
792 did_inrest = TRU1;
793 goto jdobuf;
795 if (outrest.l > 0)
796 goto jdobuf;
797 jstopit:
798 if (in.s != NULL)
799 n_free(in.s);
800 if (dec.s != NULL)
801 n_free(dec.s);
802 if (outrest.s != NULL)
803 n_free(outrest.s);
804 if (inrest.s != NULL)
805 n_free(inrest.s);
807 fseek(mb.mb_itf, start_off, SEEK_SET);
809 if (!(mtc & (_MT_C_HASNUL /*| _MT_C_CTRLCHAR XXX really? */))) {
810 /* In that special relaxed case we may very well wave through
811 * octet-streams full of control characters, as they do no harm
812 * TODO This should be part of m_mime_classifier_finalize() then! */
813 if(deep_inspect &&
814 mtca.mtca_all_len - mtca.mtca_all_bogus < mtca.mtca_all_len >> 2)
815 goto jleave;
817 mc = MIME_TEXT_PLAIN;
818 if (mce & MIMECE_ALL_OVWR)
819 mpp->m_ct_type_plain = "text/plain";
820 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
821 mpp->m_ct_type_usr_ovwr = "text/plain";
823 jleave:
824 NYD2_LEAVE;
825 return mc;
828 static enum mime_handler_flags
829 a_mt_pipe_check(struct mime_handler *mhp){
830 enum mime_handler_flags rv_orig, rv;
831 char const *cp;
832 NYD2_ENTER;
834 rv_orig = rv = mhp->mh_flags;
836 /* Do we have any handler for this part? */
837 if(*(cp = mhp->mh_shell_cmd) == '\0')
838 goto jleave;
839 else if(*cp++ != '@'){
840 rv |= MIME_HDL_CMD;
841 goto jleave;
842 }else if(*cp == '\0'){
843 rv |= MIME_HDL_TEXT;
844 goto jleave;
847 jnextc:
848 switch(*cp){
849 case '*': rv |= MIME_HDL_COPIOUSOUTPUT; ++cp; goto jnextc;
850 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
851 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
852 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
853 case '+':
854 if(rv & MIME_HDL_TMPF)
855 rv |= MIME_HDL_TMPF_UNLINK;
856 rv |= MIME_HDL_TMPF;
857 ++cp;
858 goto jnextc;
859 case '=':
860 rv |= MIME_HDL_TMPF_FILL;
861 ++cp;
862 goto jnextc;
863 case '@':
864 ++cp;
865 /* FALLTHRU */
866 default:
867 break;
869 mhp->mh_shell_cmd = cp;
871 /* Implications */
872 if(rv & MIME_HDL_TMPF_FILL)
873 rv |= MIME_HDL_TMPF;
875 /* Exceptions */
876 if(rv & MIME_HDL_ISQUOTE){
877 if(rv & MIME_HDL_NOQUOTE)
878 goto jerr;
880 /* Cannot fetch data back from asynchronous process */
881 if(rv & MIME_HDL_ASYNC)
882 goto jerr;
884 /* TODO Can't use a "needsterminal" program for quoting */
885 if(rv & MIME_HDL_NEEDSTERM)
886 goto jerr;
889 if(rv & MIME_HDL_NEEDSTERM){
890 if(rv & MIME_HDL_COPIOUSOUTPUT){
891 n_err(_("MIME type handlers: cannot use needsterminal and "
892 "copiousoutput together\n"));
893 goto jerr;
895 if(rv & MIME_HDL_ASYNC){
896 n_err(_("MIME type handlers: cannot use needsterminal and "
897 "x-mailx-async together\n"));
898 goto jerr;
901 /* needsterminal needs a terminal */
902 if(!(n_psonce & n_PSO_INTERACTIVE))
903 goto jerr;
906 if(rv & MIME_HDL_ASYNC){
907 if(rv & MIME_HDL_COPIOUSOUTPUT){
908 n_err(_("MIME type handlers: cannot use x-mailx-async and "
909 "copiousoutput together\n"));
910 goto jerr;
912 if(rv & MIME_HDL_TMPF_UNLINK){
913 n_err(_("MIME type handlers: cannot use x-mailx-async and "
914 "x-mailx-tmpfile-unlink together\n"));
915 goto jerr;
919 /* TODO mailcap-only: TMPF_UNLINK): needs -tmpfile OR -tmpfile-fill */
921 rv |= MIME_HDL_CMD;
922 jleave:
923 mhp->mh_flags = rv;
924 NYD2_LEAVE;
925 return rv;
926 jerr:
927 rv = rv_orig;
928 goto jleave;
931 FL int
932 c_mimetype(void *v){
933 struct n_string s, *sp;
934 struct mtnode *mtnp;
935 char **argv;
936 NYD_ENTER;
938 if(!_mt_is_init)
939 _mt_init();
941 sp = n_string_creat_auto(&s);
943 if(*(argv = v) == NULL){
944 FILE *fp;
945 size_t l;
947 if(_mt_list == NULL){
948 fprintf(n_stdout, _("# `mimetype': no mime.types(5) available\n"));
949 goto jleave;
952 if((fp = Ftmp(NULL, "mimetype", OF_RDWR | OF_UNLINK | OF_REGISTER)
953 ) == NULL){
954 n_perr(_("tmpfile"), 0);
955 v = NULL;
956 goto jleave;
959 sp = n_string_reserve(sp, 63);
961 for(l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next){
962 char const *cp;
964 sp = n_string_trunc(sp, 0);
966 switch(mtnp->mt_flags & a_MT__TM_MARKMASK){
967 case a_MT_TM_PLAIN: cp = "@t "; break;
968 case a_MT_TM_SOUP_h: cp = "@h "; break;
969 case a_MT_TM_SOUP_H: cp = "@H "; break;
970 case a_MT_TM_QUIET: cp = "@q "; break;
971 default: cp = NULL; break;
973 if(cp != NULL)
974 sp = n_string_push_cp(sp, cp);
976 if((mtnp->mt_flags & __MT_TMASK) != _MT_OTHER)
977 sp = n_string_push_cp(sp, _mt_typnames[mtnp->mt_flags &__MT_TMASK]);
979 sp = n_string_push_buf(sp, mtnp->mt_line, mtnp->mt_mtlen);
980 sp = n_string_push_c(sp, ' ');
981 sp = n_string_push_c(sp, ' ');
982 sp = n_string_push_cp(sp, &mtnp->mt_line[mtnp->mt_mtlen]);
984 fprintf(fp, "wysh mimetype %s%s\n", n_string_cp(sp),
985 ((n_poption & n_PO_D_V) == 0 ? n_empty
986 : (mtnp->mt_flags & _MT_USR ? " # user"
987 : (mtnp->mt_flags & _MT_SYS ? " # system"
988 : (mtnp->mt_flags & _MT_FSPEC ? " # f= file"
989 : (mtnp->mt_flags & _MT_CMD ? " # command" : " # built-in"))))));
992 page_or_print(fp, l);
993 Fclose(fp);
994 }else{
995 for(; *argv != NULL; ++argv){
996 if(sp->s_len > 0)
997 sp = n_string_push_c(sp, ' ');
998 sp = n_string_push_cp(sp, *argv);
1001 mtnp = _mt_create(TRU1, _MT_CMD, n_string_cp(sp), sp->s_len);
1002 if(mtnp != NULL){
1003 mtnp->mt_next = _mt_list;
1004 _mt_list = mtnp;
1005 }else
1006 v = NULL;
1008 jleave:
1009 NYD_LEAVE;
1010 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1013 FL int
1014 c_unmimetype(void *v)
1016 char **argv = v;
1017 struct mtnode *lnp, *mtnp;
1018 bool_t match;
1019 NYD_ENTER;
1021 /* Need to load that first as necessary */
1022 if (!_mt_is_init)
1023 _mt_init();
1025 for (; *argv != NULL; ++argv) {
1026 if (!asccasecmp(*argv, "reset")) {
1027 _mt_is_init = FAL0;
1028 goto jdelall;
1031 if (argv[0][0] == '*' && argv[0][1] == '\0') {
1032 jdelall:
1033 while ((mtnp = _mt_list) != NULL) {
1034 _mt_list = mtnp->mt_next;
1035 n_free(mtnp);
1037 continue;
1040 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
1041 char const *typ;
1042 char *val;
1043 size_t i;
1045 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
1046 typ = n_empty;
1047 i = 0;
1048 } else {
1049 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
1050 i = strlen(typ);
1053 val = n_lofi_alloc(i + mtnp->mt_mtlen +1);
1054 memcpy(val, typ, i);
1055 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
1056 val[i += mtnp->mt_mtlen] = '\0';
1057 i = asccasecmp(val, *argv);
1058 n_lofi_free(val);
1060 if (!i) {
1061 struct mtnode *nnp = mtnp->mt_next;
1062 if (lnp == NULL)
1063 _mt_list = nnp;
1064 else
1065 lnp->mt_next = nnp;
1066 n_free(mtnp);
1067 mtnp = nnp;
1068 match = TRU1;
1069 } else
1070 lnp = mtnp, mtnp = mtnp->mt_next;
1072 if (!match) {
1073 if (!(n_pstate & n_PS_ROBOT) || (n_poption & n_PO_D_V))
1074 n_err(_("No such MIME type: %s\n"), *argv);
1075 v = NULL;
1078 NYD_LEAVE;
1079 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1082 FL bool_t
1083 n_mimetype_check_mtname(char const *name)
1085 struct mtlookup mtl;
1086 bool_t rv;
1087 NYD_ENTER;
1089 rv = (_mt_by_mtname(&mtl, name) != NULL);
1090 NYD_LEAVE;
1091 return rv;
1094 FL char *
1095 n_mimetype_classify_filename(char const *name)
1097 struct mtlookup mtl;
1098 NYD_ENTER;
1100 _mt_by_filename(&mtl, name, TRU1);
1101 NYD_LEAVE;
1102 return mtl.mtl_result;
1105 FL enum conversion
1106 n_mimetype_classify_file(FILE *fp, char const **contenttype,
1107 char const **charset, int *do_iconv)
1109 /* TODO classify once only PLEASE PLEASE PLEASE */
1110 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1111 * TODO binary according to RFC 2046, 5.2.1
1112 * TODO The handling of which is a hack */
1113 bool_t rfc822;
1114 enum mime_type_class mtc;
1115 enum mime_enc menc;
1116 off_t fpsz;
1117 enum conversion c;
1118 NYD_ENTER;
1120 assert(ftell(fp) == 0x0l);
1122 *do_iconv = 0;
1124 if (*contenttype == NULL) {
1125 mtc = _MT_C_NCTT;
1126 rfc822 = FAL0;
1127 } else if (!ascncasecmp(*contenttype, "text/", 5)) {
1128 mtc = ok_blook(mime_allow_text_controls)
1129 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1130 rfc822 = FAL0;
1131 } else if (!asccasecmp(*contenttype, "message/rfc822")) {
1132 mtc = _MT_C_ISTXT;
1133 rfc822 = TRU1;
1134 } else {
1135 mtc = _MT_C_CLEAN;
1136 rfc822 = FAL0;
1139 menc = mime_enc_target();
1141 if ((fpsz = fsize(fp)) == 0)
1142 goto j7bit;
1143 else {
1144 char buf[BUFFER_SIZE];
1145 struct mt_class_arg mtca;
1147 _mt_classify_init(&mtca, mtc);
1148 for (;;) {
1149 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1150 mtca.mtca_buf = buf;
1151 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1152 break;
1153 if (mtca.mtca_len == 0)
1154 break;
1156 /* TODO ferror(fp) ! */
1157 rewind(fp);
1160 if (mtc & _MT_C_HASNUL) {
1161 menc = MIMEE_B64;
1162 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1163 * on request; else enforce what file(1)/libmagic(3) would suggest */
1164 if (mtc & _MT_C_ISTXTCOK)
1165 goto jcharset;
1166 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1167 *contenttype = "application/octet-stream";
1168 goto jleave;
1171 if (mtc &
1172 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1173 if (menc != MIMEE_B64)
1174 menc = MIMEE_QP;
1175 goto jstepi;
1177 if (mtc & _MT_C_HIGHBIT) {
1178 jstepi:
1179 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1180 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1181 } else
1182 j7bit:
1183 menc = MIMEE_7B;
1184 if (mtc & _MT_C_NCTT)
1185 *contenttype = "text/plain";
1187 /* Not an attachment with specified charset? */
1188 jcharset:
1189 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1190 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1191 : ok_vlook(charset_7bit);
1192 jleave:
1193 /* TODO mime_type_file_classify() shouldn't return conversion */
1194 if (rfc822) {
1195 if (mtc & _MT_C_FROM_1STLINE) {
1196 n_err(_("Pre-v15 %s cannot handle message/rfc822 that "
1197 "indeed is a RFC 4155 MBOX!\n"
1198 " Forcing a content-type of application/mbox!\n"),
1199 n_uagent);
1200 *contenttype = "application/mbox";
1201 goto jnorfc822;
1203 c = (menc == MIMEE_7B ? CONV_7BIT
1204 : (menc == MIMEE_8B ? CONV_8BIT
1205 /* May have only 7-bit, 8-bit and binary. Try to avoid latter */
1206 : ((mtc & _MT_C_HASNUL) ? CONV_NONE
1207 : ((mtc & _MT_C_HIGHBIT) ? CONV_8BIT : CONV_7BIT))));
1208 } else
1209 jnorfc822:
1210 c = (menc == MIMEE_7B ? CONV_7BIT
1211 : (menc == MIMEE_8B ? CONV_8BIT
1212 : (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1213 NYD_LEAVE;
1214 return c;
1217 FL enum mimecontent
1218 n_mimetype_classify_part(struct mimepart *mpp, bool_t for_user_context){
1219 /* TODO n_mimetype_classify_part() <-> m_mime_classifier_ with life cycle */
1220 struct mtlookup mtl;
1221 enum mimecontent mc;
1222 char const *ct;
1223 union {char const *cp; ui32_t f;} mce;
1224 bool_t is_os;
1225 NYD_ENTER;
1227 mc = MIME_UNKNOWN;
1228 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1229 ct = n_empty;
1231 if((mce.cp = ok_vlook(mime_counter_evidence)) != NULL && *mce.cp != '\0'){
1232 if((n_idec_ui32_cp(&mce.f, mce.cp, 0, NULL
1233 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1234 ) != n_IDEC_STATE_CONSUMED){
1235 n_err(_("Invalid *mime-counter-evidence* value content\n"));
1236 is_os = FAL0;
1237 }else{
1238 mce.f |= MIMECE_SET;
1239 is_os = !asccasecmp(ct, "application/octet-stream");
1241 if(mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))){
1242 if(_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL){
1243 if(is_os)
1244 goto jos_content_check;
1245 }else if(is_os || asccasecmp(ct, mtl.mtl_result)){
1246 if(mce.f & MIMECE_ALL_OVWR)
1247 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1248 if(mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1249 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1253 }else
1254 is_os = FAL0;
1256 if(*ct == '\0' || strchr(ct, '/') == NULL) /* For compat with non-MIME */
1257 mc = MIME_TEXT;
1258 else if(is_asccaseprefix("text/", ct)){
1259 ct += sizeof("text/") -1;
1260 if(!asccasecmp(ct, "plain"))
1261 mc = MIME_TEXT_PLAIN;
1262 else if(!asccasecmp(ct, "html"))
1263 mc = MIME_TEXT_HTML;
1264 else
1265 mc = MIME_TEXT;
1266 }else if(is_asccaseprefix("message/", ct)){
1267 ct += sizeof("message/") -1;
1268 if(!asccasecmp(ct, "rfc822"))
1269 mc = MIME_822;
1270 else
1271 mc = MIME_MESSAGE;
1272 }else if(is_asccaseprefix("multipart/", ct)){
1273 struct multi_types{
1274 char mt_name[12];
1275 enum mimecontent mt_mc;
1276 } const mta[] = {
1277 {"alternative\0", MIME_ALTERNATIVE},
1278 {"related", MIME_RELATED},
1279 {"digest", MIME_DIGEST},
1280 {"signed", MIME_SIGNED},
1281 {"encrypted", MIME_ENCRYPTED}
1282 }, *mtap;
1284 for(ct += sizeof("multipart/") -1, mtap = mta;;)
1285 if(!asccasecmp(ct, mtap->mt_name)){
1286 mc = mtap->mt_mc;
1287 break;
1288 }else if(++mtap == mta + n_NELEM(mta)){
1289 mc = MIME_MULTI;
1290 break;
1292 }else if(is_asccaseprefix("application/", ct)){
1293 if(is_os)
1294 goto jos_content_check;
1295 ct += sizeof("application/") -1;
1296 if(!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1297 mc = MIME_PKCS7;
1299 jleave:
1300 NYD_LEAVE;
1301 return mc;
1303 jos_content_check:
1304 if((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1305 mpp->m_charset != NULL)
1306 mc = _mt_classify_os_part(mce.f, mpp, for_user_context);
1307 goto jleave;
1310 FL enum mime_handler_flags
1311 n_mimetype_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1312 enum sendaction action)
1314 #define __S "pipe-"
1315 #define __L (sizeof(__S) -1)
1316 struct mtlookup mtl;
1317 char *buf, *cp;
1318 enum mime_handler_flags rv, xrv;
1319 char const *es, *cs, *ccp;
1320 size_t el, cl, l;
1321 NYD_ENTER;
1323 memset(mhp, 0, sizeof *mhp);
1324 buf = NULL;
1326 rv = MIME_HDL_NULL;
1327 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1328 rv |= MIME_HDL_ISQUOTE;
1329 else if (action != SEND_TODISP && action != SEND_TODISP_ALL &&
1330 action != SEND_TODISP_PARTS)
1331 goto jleave;
1333 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1334 *++es != '\0') ? strlen(es) : 0;
1335 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1336 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1337 if ((l = n_MAX(el, cl)) == 0) {
1338 /* TODO this should be done during parse time! */
1339 goto jleave;
1342 /* We don't pass the flags around, so ensure carrier is up-to-date */
1343 mhp->mh_flags = rv;
1345 buf = n_lofi_alloc(__L + l +1);
1346 memcpy(buf, __S, __L);
1348 /* File-extension handlers take precedence.
1349 * Yes, we really "fail" here for file extensions which clash MIME types */
1350 if (el > 0) {
1351 memcpy(buf + __L, es, el +1);
1352 for (cp = buf + __L; *cp != '\0'; ++cp)
1353 *cp = lowerconv(*cp);
1355 if ((mhp->mh_shell_cmd = ccp = n_var_vlook(buf, FAL0)) != NULL) {
1356 rv = a_mt_pipe_check(mhp);
1357 goto jleave;
1361 /* Then MIME Content-Type:, if any */
1362 if (cl == 0)
1363 goto jleave;
1365 memcpy(buf + __L, cs, cl +1);
1366 for (cp = buf + __L; *cp != '\0'; ++cp)
1367 *cp = lowerconv(*cp);
1369 if ((mhp->mh_shell_cmd = n_var_vlook(buf, FAL0)) != NULL) {
1370 rv = a_mt_pipe_check(mhp);
1371 goto jleave;
1374 if (_mt_by_mtname(&mtl, cs) != NULL)
1375 switch (mtl.mtl_node->mt_flags & a_MT__TM_MARKMASK) {
1376 #ifndef HAVE_FILTER_HTML_TAGSOUP
1377 case a_MT_TM_SOUP_H:
1378 break;
1379 #endif
1380 case a_MT_TM_SOUP_h:
1381 #ifdef HAVE_FILTER_HTML_TAGSOUP
1382 case a_MT_TM_SOUP_H:
1383 mhp->mh_ptf = &htmlflt_process_main;
1384 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1385 n_UNCONST(_("Built-in HTML tagsoup filter")));
1386 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1387 goto jleave;
1388 #endif
1389 /* FALLTHRU */
1390 case a_MT_TM_PLAIN:
1391 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
1392 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1393 goto jleave;
1394 case a_MT_TM_QUIET:
1395 mhp->mh_msg.l = 0;
1396 mhp->mh_msg.s = n_UNCONST(n_empty);
1397 goto jleave;
1398 default:
1399 break;
1402 jleave:
1403 if(buf != NULL)
1404 n_lofi_free(buf);
1406 xrv = rv;
1407 if((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL){
1408 if(mhp->mh_msg.s == NULL)
1409 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1410 A_("[-- No MIME handler installed, or not applicable --]\n")));
1411 }else if(rv == MIME_HDL_CMD && !(xrv & MIME_HDL_COPIOUSOUTPUT) &&
1412 action != SEND_TODISP_PARTS){
1413 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1414 _("[-- Use the command `mimeview' to display this --]\n")));
1415 xrv &= ~MIME_HDL_TYPE_MASK;
1416 xrv |= (rv = MIME_HDL_MSG);
1418 mhp->mh_flags = xrv;
1420 NYD_LEAVE;
1421 return rv;
1422 #undef __L
1423 #undef __S
1426 /* s-it-mode */