Port to SunOS 5.9 (Aharon Robbins)
[s-mailx.git] / mime-types.c
blobe6d8a774db35af7d6851f034eab2141c9b6ea16c
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 - 2017 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 = 0u,
36 __MT_TMAX = _MT_OTHER,
37 __MT_TMASK = 0x07u,
39 _MT_CMD = 1u<< 8, /* Via `mimetype' (not struct mtbltin) */
40 _MT_USR = 1u<< 9, /* VAL_MIME_TYPES_USR */
41 _MT_SYS = 1u<<10, /* VAL_MIME_TYPES_SYS */
42 _MT_FSPEC = 1u<<11, /* Loaded via f= *mimetypes-load-control* spec. */
44 a_MT_TM_PLAIN = 1u<<16, /* Without pipe handler display as text */
45 a_MT_TM_SOUP_h = 2u<<16, /* Ditto, but HTML tagsoup parser if possible */
46 a_MT_TM_SOUP_H = 3u<<16, /* HTML tagsoup parser, else NOT plain text */
47 a_MT_TM_QUIET = 4u<<16, /* No "no mime handler available" message */
48 a_MT__TM_MARKMASK = 7u<<16
51 enum mime_type_class {
52 _MT_C_CLEAN = 0, /* Plain RFC 5322 message */
53 _MT_C_NCTT = 1<<0, /* *contenttype == NULL */
54 _MT_C_ISTXT = 1<<1, /* *contenttype =~ text\/ */
55 _MT_C_ISTXTCOK = 1<<2, /* _ISTXT + *mime-allow-text-controls* */
56 _MT_C_HIGHBIT = 1<<3, /* Not 7bit clean */
57 _MT_C_LONGLINES = 1<<4, /* MIME_LINELEN_LIMIT exceed. */
58 _MT_C_CTRLCHAR = 1<<5, /* Control characters seen */
59 _MT_C_HASNUL = 1<<6, /* Contains \0 characters */
60 _MT_C_NOTERMNL = 1<<7, /* Lacks a final newline */
61 _MT_C_FROM_ = 1<<8, /* ^From_ seen */
62 _MT_C_FROM_1STLINE = 1<<9, /* From_ line seen */
63 _MT_C_SUGGEST_DONE = 1<<16, /* Inspector suggests to stop further parse */
64 _MT_C__1STLINE = 1<<17 /* .. */
67 struct mtbltin {
68 ui32_t mtb_flags;
69 ui32_t mtb_mtlen;
70 char const *mtb_line;
73 struct mtnode {
74 struct mtnode *mt_next;
75 ui32_t mt_flags;
76 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
77 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
78 * that could already store character data here */
79 char const *mt_line;
82 struct mtlookup {
83 char const *mtl_name;
84 size_t mtl_nlen;
85 struct mtnode const *mtl_node;
86 char *mtl_result; /* If requested, salloc()ed MIME type */
89 struct mt_class_arg {
90 char const *mtca_buf;
91 size_t mtca_len;
92 ssize_t mtca_curlen;
93 /*char mtca_lastc;*/
94 char mtca_c;
95 enum mime_type_class mtca_mtc;
98 static struct mtbltin const _mt_bltin[] = {
99 #include <gen-mime-types.h>
102 static char const _mt_typnames[][16] = {
103 "application/", "audio/", "image/",
104 "message/", "multipart/", "text/",
105 "video/"
107 n_CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
108 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
109 _MT_VIDEO == 6);
111 /* */
112 static bool_t _mt_is_init;
113 static struct mtnode *_mt_list;
115 /* Initialize MIME type list in order */
116 static void _mt_init(void);
117 static bool_t __mt_load_file(ui32_t orflags,
118 char const *file, char **line, size_t *linesize);
120 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
121 * for `mimetype' */
122 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
123 char const *line, size_t len);
125 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
126 * if with_result >mtl_result will be created upon success for the former */
127 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
128 char const *name, bool_t with_result);
129 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
130 char const *mtname);
132 /* In-depth inspection of raw content: call _round() repeatedly, last time with
133 * a 0 length buffer, finally check .mtca_mtc for result.
134 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
135 * as the resulting classification is unambiguous */
136 SINLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
137 enum mime_type_class initval);
138 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
140 /* We need an in-depth inspection of an application/octet-stream part */
141 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp);
143 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
144 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
145 * isn't changed if mhp doesn't apply */
146 static enum mime_handler_flags a_mt_pipe_check(struct mime_handler *mhp);
148 static void
149 _mt_init(void)
151 struct mtnode *tail;
152 char c, *line; /* TODO line pool (below) */
153 size_t linesize;
154 ui32_t i, j;
155 char const *srcs_arr[10], *ccp, **srcs;
156 NYD_ENTER;
158 /*if (_mt_is_init)
159 * goto jleave;*/
161 /* Always load our built-ins */
162 for (tail = NULL, i = 0; i < n_NELEM(_mt_bltin); ++i) {
163 struct mtbltin const *mtbp = _mt_bltin + i;
164 struct mtnode *mtnp = smalloc(sizeof *mtnp);
166 if (tail != NULL)
167 tail->mt_next = mtnp;
168 else
169 _mt_list = mtnp;
170 tail = mtnp;
171 mtnp->mt_next = NULL;
172 mtnp->mt_flags = mtbp->mtb_flags;
173 mtnp->mt_mtlen = mtbp->mtb_mtlen;
174 mtnp->mt_line = mtbp->mtb_line;
177 /* Decide which files sources have to be loaded */
178 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
179 ccp = "US";
180 else if (*ccp == '\0')
181 goto jleave;
183 srcs = srcs_arr + 2;
184 srcs[-1] = srcs[-2] = NULL;
186 if (strchr(ccp, '=') != NULL) {
187 line = savestr(ccp);
189 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
190 switch ((c = *ccp)) {
191 case 'S': case 's':
192 srcs_arr[1] = VAL_MIME_TYPES_SYS;
193 if (0) {
194 /* FALLTHRU */
195 case 'U': case 'u':
196 srcs_arr[0] = VAL_MIME_TYPES_USR;
198 if (ccp[1] != '\0')
199 goto jecontent;
200 break;
201 case 'F': case 'f':
202 if (*++ccp == '=' && *++ccp != '\0') {
203 if (PTR2SIZE(srcs - srcs_arr) < n_NELEM(srcs_arr))
204 *srcs++ = ccp;
205 else
206 n_err(_("*mimetypes-load-control*: too many sources, "
207 "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
208 continue;
210 /* FALLTHRU */
211 default:
212 goto jecontent;
215 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
216 switch (c) {
217 case 'S': case 's': srcs_arr[1] = VAL_MIME_TYPES_SYS; break;
218 case 'U': case 'u': srcs_arr[0] = VAL_MIME_TYPES_USR; break;
219 default:
220 jecontent:
221 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp);
222 goto jleave;
225 /* Load all file-based sources in the desired order */
226 line = NULL;
227 linesize = 0;
228 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
229 i > 0; ++j, ++srcs, --i)
230 if (*srcs == NULL)
231 continue;
232 else if (!__mt_load_file((j == 0 ? _MT_USR
233 : (j == 1 ? _MT_SYS : _MT_FSPEC)), *srcs, &line, &linesize)) {
234 if ((n_poption & n_PO_D_V) || j > 1)
235 n_err(_("*mimetypes-load-control*: cannot open or load %s\n"),
236 n_shexp_quote_cp(*srcs, FAL0));
238 if (line != NULL)
239 free(line);
240 jleave:
241 _mt_is_init = TRU1;
242 NYD_LEAVE;
245 static bool_t
246 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
248 char const *cp;
249 FILE *fp;
250 struct mtnode *head, *tail, *mtnp;
251 size_t len;
252 NYD_ENTER;
254 if ((cp = fexpand(file, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
255 (fp = Fopen(cp, "r")) == NULL) {
256 cp = NULL;
257 goto jleave;
260 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
261 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
262 if (head == NULL)
263 head = tail = mtnp;
264 else
265 tail->mt_next = mtnp;
266 tail = mtnp;
268 if (head != NULL) {
269 tail->mt_next = _mt_list;
270 _mt_list = head;
273 Fclose(fp);
274 jleave:
275 NYD_LEAVE;
276 return (cp != NULL);
279 static struct mtnode *
280 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
282 struct mtnode *mtnp;
283 char const *typ, *subtyp;
284 size_t tlen, i;
285 NYD_ENTER;
287 mtnp = NULL;
289 /* Drop anything after a comment first TODO v15: only when read from file */
290 if ((typ = memchr(line, '#', len)) != NULL)
291 len = PTR2SIZE(typ - line);
293 /* Then trim any trailing whitespace from line (including NL/CR) */
294 /* C99 */{
295 struct str work;
297 work.s = n_UNCONST(line);
298 work.l = len;
299 line = n_str_trim(&work, n_STR_TRIM_BOTH)->s;
300 len = work.l;
302 typ = line;
304 /* (But wait - is there a type marker?) */
305 tlen = len;
306 if(!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@'){
307 if(len < 2)
308 goto jeinval;
309 if(typ[1] == ' '){
310 orflags |= a_MT_TM_PLAIN;
311 typ += 2;
312 len -= 2;
313 line += 2;
314 }else if(len > 3){
315 if(typ[2] == ' ')
316 i = 3;
317 else if(len > 4 && typ[2] == '@' && typ[3] == ' '){
318 n_OBSOLETE("`mimetype': the trailing \"@\" in \"type-marker\" "
319 "is redundant");
320 i = 4;
321 }else
322 goto jeinval;
324 switch(typ[1]){
325 default: goto jeinval;
326 case 't': orflags |= a_MT_TM_PLAIN; break;
327 case 'h': orflags |= a_MT_TM_SOUP_h; break;
328 case 'H': orflags |= a_MT_TM_SOUP_H; break;
329 case 'q': orflags |= a_MT_TM_QUIET; break;
331 typ += i;
332 len -= i;
333 line += i;
334 }else
335 goto jeinval;
338 while (len > 0 && !blankchar(*line))
339 ++line, --len;
340 /* Ignore empty lines and even incomplete specifications (only MIME type)
341 * because this is quite common in mime.types(5) files */
342 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
343 if (cmdcalled || (orflags & _MT_FSPEC)) {
344 if(len == 0){
345 line = _("(no value)");
346 len = strlen(line);
348 n_err(_("Empty MIME type or no extensions given: %.*s\n"),
349 (int)len, line);
351 goto jleave;
354 if ((subtyp = memchr(typ, '/', tlen)) == NULL || subtyp[1] == '\0' ||
355 spacechar(subtyp[1])) {
356 jeinval:
357 if(cmdcalled || (orflags & _MT_FSPEC) || (n_poption & n_PO_D_V))
358 n_err(_("%s MIME type: %.*s\n"),
359 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")),
360 (int)tlen, typ);
361 goto jleave;
363 ++subtyp;
365 /* Map to mime_type */
366 tlen = PTR2SIZE(subtyp - typ);
367 for (i = __MT_TMIN;;) {
368 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
369 orflags |= i;
370 tlen = PTR2SIZE(line - subtyp);
371 typ = subtyp;
372 break;
374 if (++i == __MT_TMAX) {
375 orflags |= _MT_OTHER;
376 tlen = PTR2SIZE(line - typ);
377 break;
381 /* Strip leading whitespace from the list of extensions;
382 * trailing WS has already been trimmed away above.
383 * Be silent on slots which define a mimetype without any value */
384 while (len > 0 && blankchar(*line))
385 ++line, --len;
386 if (len == 0)
387 goto jleave;
389 /* */
390 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
391 mtnp->mt_next = NULL;
392 mtnp->mt_flags = orflags;
393 mtnp->mt_mtlen = (ui32_t)tlen;
394 { char *l = (char*)(mtnp + 1);
395 mtnp->mt_line = l;
396 memcpy(l, typ, tlen);
397 memcpy(l + tlen, line, len);
398 tlen += len;
399 l[tlen] = '\0';
402 jleave:
403 NYD_LEAVE;
404 return mtnp;
407 static struct mtlookup *
408 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
410 struct mtnode *mtnp;
411 size_t nlen, i, j;
412 char const *ext, *cp;
413 NYD2_ENTER;
415 memset(mtlp, 0, sizeof *mtlp);
417 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
418 goto jnull_leave;
419 /* We need a period TODO we should support names like README etc. */
420 for (i = nlen; name[--i] != '.';)
421 if (i == 0 || name[i] == '/') /* XXX no magics */
422 goto jnull_leave;
423 /* While here, basename() it */
424 while (i > 0 && name[i - 1] != '/')
425 --i;
426 name += i;
427 nlen -= i;
428 mtlp->mtl_name = name;
429 mtlp->mtl_nlen = nlen;
431 if (!_mt_is_init)
432 _mt_init();
434 /* ..all the MIME types */
435 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
436 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
437 cp = ext;
438 while (whitechar(*cp))
439 ++cp;
440 ext = cp;
441 while (!whitechar(*cp) && *cp != '\0')
442 ++cp;
444 if ((i = PTR2SIZE(cp - ext)) == 0)
445 break;
446 /* Don't allow neither of ".txt" or "txt" to match "txt" */
447 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
448 ascncasecmp(name + j, ext, i))
449 continue;
451 /* Found it */
452 mtlp->mtl_node = mtnp;
454 if (!with_result)
455 goto jleave;
457 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
458 name = n_empty;
459 j = 0;
460 } else {
461 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
462 j = strlen(name);
464 i = mtnp->mt_mtlen;
465 mtlp->mtl_result = salloc(i + j +1);
466 if (j > 0)
467 memcpy(mtlp->mtl_result, name, j);
468 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
469 mtlp->mtl_result[j += i] = '\0';
470 goto jleave;
472 jnull_leave:
473 mtlp = NULL;
474 jleave:
475 NYD2_LEAVE;
476 return mtlp;
479 static struct mtlookup *
480 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
482 struct mtnode *mtnp;
483 size_t nlen, i, j;
484 char const *cp;
485 NYD2_ENTER;
487 memset(mtlp, 0, sizeof *mtlp);
489 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
490 goto jnull_leave;
492 if (!_mt_is_init)
493 _mt_init();
495 /* ..all the MIME types */
496 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
497 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
498 cp = n_empty;
499 j = 0;
500 } else {
501 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
502 j = strlen(cp);
504 i = mtnp->mt_mtlen;
506 if (i + j == mtlp->mtl_nlen) {
507 char *xmt = ac_alloc(i + j +1);
508 if (j > 0)
509 memcpy(xmt, cp, j);
510 memcpy(xmt + j, mtnp->mt_line, i);
511 xmt[j += i] = '\0';
512 i = asccasecmp(mtname, xmt);
513 ac_free(xmt);
515 if (!i) {
516 /* Found it */
517 mtlp->mtl_node = mtnp;
518 goto jleave;
522 jnull_leave:
523 mtlp = NULL;
524 jleave:
525 NYD2_LEAVE;
526 return mtlp;
529 SINLINE struct mt_class_arg *
530 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
532 NYD2_ENTER;
533 memset(mtcap, 0, sizeof *mtcap);
534 /*mtcap->mtca_lastc =*/ mtcap->mtca_c = EOF;
535 mtcap->mtca_mtc = initval | _MT_C__1STLINE;
536 NYD2_LEAVE;
537 return mtcap;
540 static enum mime_type_class
541 _mt_classify_round(struct mt_class_arg *mtcap) /* TODO dig UTF-8 for !text/!! */
543 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
544 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
545 * TODO and report that state to the outer world */
546 #define F_ "From "
547 #define F_SIZEOF (sizeof(F_) -1)
548 char f_buf[F_SIZEOF], *f_p = f_buf;
549 char const *buf;
550 size_t blen;
551 ssize_t curlen;
552 int c, lastc;
553 enum mime_type_class mtc;
554 NYD2_ENTER;
556 buf = mtcap->mtca_buf;
557 blen = mtcap->mtca_len;
558 curlen = mtcap->mtca_curlen;
559 c = mtcap->mtca_c;
560 /*lastc = mtcap->mtca_lastc;*/
561 mtc = mtcap->mtca_mtc;
563 for (;; ++curlen) {
564 if(blen == 0){
565 /* Real EOF, or only current buffer end? */
566 if(mtcap->mtca_len == 0){
567 lastc = c;
568 c = EOF;
569 }else{
570 lastc = EOF;
571 break;
573 }else{
574 lastc = c;
575 c = (uc_i)*buf++;
577 --blen;
579 if (c == '\0') {
580 mtc |= _MT_C_HASNUL;
581 if (!(mtc & _MT_C_ISTXTCOK)) {
582 mtc |= _MT_C_SUGGEST_DONE;
583 break;
585 continue;
587 if (c == '\n' || c == EOF) {
588 mtc &= ~_MT_C__1STLINE;
589 if (curlen >= MIME_LINELEN_LIMIT)
590 mtc |= _MT_C_LONGLINES;
591 if (c == EOF) {
592 break;
594 f_p = f_buf;
595 curlen = -1;
596 continue;
598 /* A bit hairy is handling of \r=\x0D=CR.
599 * RFC 2045, 6.7:
600 * Control characters other than TAB, or CR and LF as parts of CRLF
601 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
602 * we cannot peek the next character. Thus right here, inspect the last
603 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
604 /*else*/ if (lastc == '\r')
605 mtc |= _MT_C_CTRLCHAR;
607 /* Control character? XXX this is all ASCII here */
608 if (c < 0x20 || c == 0x7F) {
609 /* RFC 2045, 6.7, as above ... */
610 if (c != '\t' && c != '\r')
611 mtc |= _MT_C_CTRLCHAR;
612 /* If there is a escape sequence in reverse solidus notation defined
613 * for this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
614 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
615 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
616 * \e=\x1B=ESC */
617 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
618 continue;
619 mtc |= _MT_C_HASNUL; /* Force base64 */
620 if (!(mtc & _MT_C_ISTXTCOK)) {
621 mtc |= _MT_C_SUGGEST_DONE;
622 break;
624 } else if ((ui8_t)c & 0x80) {
625 mtc |= _MT_C_HIGHBIT;
626 /* TODO count chars with HIGHBIT? libmagic?
627 * TODO try encode part - base64 if bails? */
628 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
629 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
630 break;
632 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlen, <, F_SIZEOF)) {
633 *f_p++ = (char)c;
634 if (UICMP(z, curlen, ==, F_SIZEOF - 1) &&
635 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
636 !memcmp(f_buf, F_, F_SIZEOF)){
637 mtc |= _MT_C_FROM_;
638 if (mtc & _MT_C__1STLINE)
639 mtc |= _MT_C_FROM_1STLINE;
643 if (c == EOF && lastc != '\n')
644 mtc |= _MT_C_NOTERMNL;
646 mtcap->mtca_curlen = curlen;
647 /*mtcap->mtca_lastc = lastc*/;
648 mtcap->mtca_c = c;
649 mtcap->mtca_mtc = mtc;
650 NYD2_LEAVE;
651 return mtc;
652 #undef F_
653 #undef F_SIZEOF
656 static enum mimecontent
657 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp)
659 struct str in = {NULL, 0}, outrest, inrest, dec;
660 struct mt_class_arg mtca;
661 bool_t did_inrest;
662 enum mime_type_class mtc;
663 int lc, c;
664 size_t cnt, lsz;
665 FILE *ibuf;
666 off_t start_off;
667 enum mimecontent mc;
668 NYD2_ENTER;
670 assert(mpp->m_mime_enc != MIMEE_BIN);
672 outrest = inrest = dec = in;
673 mc = MIME_UNKNOWN;
674 n_UNINIT(mtc, 0);
675 did_inrest = FAL0;
677 /* TODO v15-compat Note we actually bypass our usual file handling by
678 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
679 * TODO all of this, and until then doing it like this is the only option
680 * TODO to integrate nicely into whoever calls us */
681 start_off = ftell(mb.mb_itf);
682 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
683 jos_leave:
684 fseek(mb.mb_itf, start_off, SEEK_SET);
685 goto jleave;
687 cnt = mpp->m_size;
689 /* Skip part headers */
690 for (lc = '\0'; cnt > 0; lc = c, --cnt)
691 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
692 break;
693 if (cnt == 0 || ferror(ibuf))
694 goto jos_leave;
696 /* So now let's inspect the part content, decoding content-transfer-encoding
697 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
698 _mt_classify_init(&mtca, _MT_C_ISTXT);
700 for (lsz = 0;;) {
701 bool_t dobuf;
703 c = (--cnt == 0) ? EOF : getc(ibuf);
704 if ((dobuf = (c == '\n'))) {
705 /* Ignore empty lines */
706 if (lsz == 0)
707 continue;
708 } else if ((dobuf = (c == EOF))) {
709 if (lsz == 0 && outrest.l == 0)
710 break;
713 if (in.l + 1 >= lsz)
714 in.s = srealloc(in.s, lsz += LINESIZE);
715 if (c != EOF)
716 in.s[in.l++] = (char)c;
717 if (!dobuf)
718 continue;
720 jdobuf:
721 switch (mpp->m_mime_enc) {
722 case MIMEE_B64:
723 if (!b64_decode_part(&dec, &in, &outrest,
724 (did_inrest ? NULL : &inrest))) {
725 mtca.mtca_mtc = _MT_C_HASNUL;
726 goto jstopit; /* break;break; */
728 break;
729 case MIMEE_QP:
730 /* Drin */
731 if (!qp_decode_part(&dec, &in, &outrest, &inrest)) {
732 mtca.mtca_mtc = _MT_C_HASNUL;
733 goto jstopit; /* break;break; */
735 if (dec.l == 0 && c != EOF) {
736 in.l = 0;
737 continue;
739 break;
740 default:
741 /* Temporarily switch those two buffers.. */
742 dec = in;
743 in.s = NULL;
744 in.l = 0;
745 break;
748 mtca.mtca_buf = dec.s;
749 mtca.mtca_len = (ssize_t)dec.l;
750 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
751 mtc = _MT_C_HASNUL;
752 break;
755 if (c == EOF)
756 break;
757 /* ..and restore switched */
758 if (in.s == NULL) {
759 in = dec;
760 dec.s = NULL;
762 in.l = dec.l = 0;
765 if ((in.l = inrest.l) > 0) {
766 in.s = inrest.s;
767 did_inrest = TRU1;
768 goto jdobuf;
770 if (outrest.l > 0)
771 goto jdobuf;
772 jstopit:
773 if (in.s != NULL)
774 free(in.s);
775 if (dec.s != NULL)
776 free(dec.s);
777 if (outrest.s != NULL)
778 free(outrest.s);
779 if (inrest.s != NULL)
780 free(inrest.s);
782 fseek(mb.mb_itf, start_off, SEEK_SET);
784 if (!(mtc & (_MT_C_HASNUL /*| _MT_C_CTRLCHAR XXX really? */))) {
785 mc = MIME_TEXT_PLAIN;
786 if (mce & MIMECE_ALL_OVWR)
787 mpp->m_ct_type_plain = "text/plain";
788 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
789 mpp->m_ct_type_usr_ovwr = "text/plain";
791 jleave:
792 NYD2_LEAVE;
793 return mc;
796 static enum mime_handler_flags
797 a_mt_pipe_check(struct mime_handler *mhp){
798 enum mime_handler_flags rv_orig, rv;
799 char const *cp;
800 NYD2_ENTER;
802 rv_orig = rv = mhp->mh_flags;
804 /* Do we have any handler for this part? */
805 if(*(cp = mhp->mh_shell_cmd) == '\0')
806 goto jleave;
807 else if(*cp++ != '@'){
808 rv |= MIME_HDL_CMD;
809 goto jleave;
810 }else if(*cp == '\0'){
811 rv |= MIME_HDL_TEXT;
812 goto jleave;
815 jnextc:
816 switch(*cp){
817 case '*': rv |= MIME_HDL_COPIOUSOUTPUT; ++cp; goto jnextc;
818 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
819 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
820 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
821 case '+':
822 if(rv & MIME_HDL_TMPF)
823 rv |= MIME_HDL_TMPF_UNLINK;
824 rv |= MIME_HDL_TMPF;
825 ++cp;
826 goto jnextc;
827 case '=':
828 rv |= MIME_HDL_TMPF_FILL;
829 ++cp;
830 goto jnextc;
831 case '@':
832 ++cp;
833 /* FALLTHRU */
834 default:
835 break;
837 mhp->mh_shell_cmd = cp;
839 /* Implications */
840 if(rv & MIME_HDL_TMPF_FILL)
841 rv |= MIME_HDL_TMPF;
843 /* Exceptions */
844 if(rv & MIME_HDL_ISQUOTE){
845 if(rv & MIME_HDL_NOQUOTE)
846 goto jerr;
848 /* Cannot fetch data back from asynchronous process */
849 if(rv & MIME_HDL_ASYNC)
850 goto jerr;
852 /* TODO Can't use a "needsterminal" program for quoting */
853 if(rv & MIME_HDL_NEEDSTERM)
854 goto jerr;
857 if(rv & MIME_HDL_NEEDSTERM){
858 if(rv & MIME_HDL_COPIOUSOUTPUT){
859 n_err(_("MIME type handlers: cannot use needsterminal and "
860 "copiousoutput together\n"));
861 goto jerr;
863 if(rv & MIME_HDL_ASYNC){
864 n_err(_("MIME type handlers: cannot use needsterminal and "
865 "x-mailx-async together\n"));
866 goto jerr;
869 /* needsterminal needs a terminal */
870 if(!(n_psonce & n_PSO_INTERACTIVE))
871 goto jerr;
874 if(rv & MIME_HDL_ASYNC){
875 if(rv & MIME_HDL_COPIOUSOUTPUT){
876 n_err(_("MIME type handlers: cannot use x-mailx-async and "
877 "copiousoutput together\n"));
878 goto jerr;
880 if(rv & MIME_HDL_TMPF_UNLINK){
881 n_err(_("MIME type handlers: cannot use x-mailx-async and "
882 "x-mailx-tmpfile-unlink together\n"));
883 goto jerr;
887 /* TODO mailcap-only: TMPF_UNLINK): needs -tmpfile OR -tmpfile-fill */
889 rv |= MIME_HDL_CMD;
890 jleave:
891 mhp->mh_flags = rv;
892 NYD2_LEAVE;
893 return rv;
894 jerr:
895 rv = rv_orig;
896 goto jleave;
899 FL int
900 c_mimetype(void *v){
901 struct n_string s, *sp;
902 struct mtnode *mtnp;
903 char **argv;
904 NYD_ENTER;
906 if(!_mt_is_init)
907 _mt_init();
909 sp = n_string_creat_auto(&s);
911 if(*(argv = v) == NULL){
912 FILE *fp;
913 size_t l;
915 if(_mt_list == NULL){
916 fprintf(n_stdout, _("# `mimetype': no mime.types(5) available\n"));
917 goto jleave;
920 if((fp = Ftmp(NULL, "mimetype", OF_RDWR | OF_UNLINK | OF_REGISTER)
921 ) == NULL){
922 n_perr(_("tmpfile"), 0);
923 v = NULL;
924 goto jleave;
927 sp = n_string_reserve(sp, 63);
929 for(l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next){
930 char const *cp;
932 sp = n_string_trunc(sp, 0);
934 switch(mtnp->mt_flags & a_MT__TM_MARKMASK){
935 case a_MT_TM_PLAIN: cp = "@t "; break;
936 case a_MT_TM_SOUP_h: cp = "@h "; break;
937 case a_MT_TM_SOUP_H: cp = "@H "; break;
938 case a_MT_TM_QUIET: cp = "@q "; break;
939 default: cp = NULL; break;
941 if(cp != NULL)
942 sp = n_string_push_cp(sp, cp);
944 if((mtnp->mt_flags & __MT_TMASK) != _MT_OTHER)
945 sp = n_string_push_cp(sp, _mt_typnames[mtnp->mt_flags &__MT_TMASK]);
947 sp = n_string_push_buf(sp, mtnp->mt_line, mtnp->mt_mtlen);
948 sp = n_string_push_c(sp, ' ');
949 sp = n_string_push_c(sp, ' ');
950 sp = n_string_push_cp(sp, &mtnp->mt_line[mtnp->mt_mtlen]);
952 fprintf(fp, "wysh mimetype %s%s\n", n_string_cp(sp),
953 ((n_poption & n_PO_D_V) == 0 ? n_empty
954 : (mtnp->mt_flags & _MT_USR ? " # user"
955 : (mtnp->mt_flags & _MT_SYS ? " # system"
956 : (mtnp->mt_flags & _MT_FSPEC ? " # f= file"
957 : (mtnp->mt_flags & _MT_CMD ? " # command" : " # built-in"))))));
960 page_or_print(fp, l);
961 Fclose(fp);
962 }else{
963 for(; *argv != NULL; ++argv){
964 if(sp->s_len > 0)
965 sp = n_string_push_c(sp, ' ');
966 sp = n_string_push_cp(sp, *argv);
969 mtnp = _mt_create(TRU1, _MT_CMD, n_string_cp(sp), sp->s_len);
970 if(mtnp != NULL){
971 mtnp->mt_next = _mt_list;
972 _mt_list = mtnp;
973 }else
974 v = NULL;
976 jleave:
977 NYD_LEAVE;
978 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
981 FL int
982 c_unmimetype(void *v)
984 char **argv = v;
985 struct mtnode *lnp, *mtnp;
986 bool_t match;
987 NYD_ENTER;
989 /* Need to load that first as necessary */
990 if (!_mt_is_init)
991 _mt_init();
993 for (; *argv != NULL; ++argv) {
994 if (!asccasecmp(*argv, "reset")) {
995 _mt_is_init = FAL0;
996 goto jdelall;
999 if (argv[0][0] == '*' && argv[0][1] == '\0') {
1000 jdelall:
1001 while ((mtnp = _mt_list) != NULL) {
1002 _mt_list = mtnp->mt_next;
1003 free(mtnp);
1005 continue;
1008 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
1009 char const *typ;
1010 char *val;
1011 size_t i;
1013 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
1014 typ = n_empty;
1015 i = 0;
1016 } else {
1017 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
1018 i = strlen(typ);
1021 val = ac_alloc(i + mtnp->mt_mtlen +1);
1022 memcpy(val, typ, i);
1023 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
1024 val[i += mtnp->mt_mtlen] = '\0';
1025 i = asccasecmp(val, *argv);
1026 ac_free(val);
1028 if (!i) {
1029 struct mtnode *nnp = mtnp->mt_next;
1030 if (lnp == NULL)
1031 _mt_list = nnp;
1032 else
1033 lnp->mt_next = nnp;
1034 free(mtnp);
1035 mtnp = nnp;
1036 match = TRU1;
1037 } else
1038 lnp = mtnp, mtnp = mtnp->mt_next;
1040 if (!match) {
1041 if (!(n_pstate & n_PS_ROBOT) || (n_poption & n_PO_D_V))
1042 n_err(_("No such MIME type: %s\n"), *argv);
1043 v = NULL;
1046 NYD_LEAVE;
1047 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1050 FL bool_t
1051 n_mimetype_check_mtname(char const *name)
1053 struct mtlookup mtl;
1054 bool_t rv;
1055 NYD_ENTER;
1057 rv = (_mt_by_mtname(&mtl, name) != NULL);
1058 NYD_LEAVE;
1059 return rv;
1062 FL char *
1063 n_mimetype_classify_filename(char const *name)
1065 struct mtlookup mtl;
1066 NYD_ENTER;
1068 _mt_by_filename(&mtl, name, TRU1);
1069 NYD_LEAVE;
1070 return mtl.mtl_result;
1073 FL enum conversion
1074 n_mimetype_classify_file(FILE *fp, char const **contenttype,
1075 char const **charset, int *do_iconv)
1077 /* TODO classify once only PLEASE PLEASE PLEASE */
1078 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1079 * TODO binary according to RFC 2046, 5.2.1
1080 * TODO The handling of which is a hack */
1081 bool_t rfc822;
1082 enum mime_type_class mtc;
1083 enum mime_enc menc;
1084 off_t fpsz;
1085 enum conversion c;
1086 NYD_ENTER;
1088 assert(ftell(fp) == 0x0l);
1090 *do_iconv = 0;
1092 if (*contenttype == NULL) {
1093 mtc = _MT_C_NCTT;
1094 rfc822 = FAL0;
1095 } else if (!ascncasecmp(*contenttype, "text/", 5)) {
1096 mtc = ok_blook(mime_allow_text_controls)
1097 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1098 rfc822 = FAL0;
1099 } else if (!asccasecmp(*contenttype, "message/rfc822")) {
1100 mtc = _MT_C_ISTXT;
1101 rfc822 = TRU1;
1102 } else {
1103 mtc = _MT_C_CLEAN;
1104 rfc822 = FAL0;
1107 menc = mime_enc_target();
1109 if ((fpsz = fsize(fp)) == 0)
1110 goto j7bit;
1111 else {
1112 char buf[BUFFER_SIZE];
1113 struct mt_class_arg mtca;
1115 _mt_classify_init(&mtca, mtc);
1116 for (;;) {
1117 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1118 mtca.mtca_buf = buf;
1119 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1120 break;
1121 if (mtca.mtca_len == 0)
1122 break;
1124 /* TODO ferror(fp) ! */
1125 rewind(fp);
1128 if (mtc & _MT_C_HASNUL) {
1129 menc = MIMEE_B64;
1130 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1131 * on request; else enforce what file(1)/libmagic(3) would suggest */
1132 if (mtc & _MT_C_ISTXTCOK)
1133 goto jcharset;
1134 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1135 *contenttype = "application/octet-stream";
1136 /* if (*charset == NULL)
1137 * *charset = "binary";*/
1138 goto jleave;
1141 if (mtc &
1142 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1143 if (menc != MIMEE_B64)
1144 menc = MIMEE_QP;
1145 goto jstepi;
1147 if (mtc & _MT_C_HIGHBIT) {
1148 jstepi:
1149 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1150 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1151 } else
1152 j7bit:
1153 menc = MIMEE_7B;
1154 if (mtc & _MT_C_NCTT)
1155 *contenttype = "text/plain";
1157 /* Not an attachment with specified charset? */
1158 jcharset:
1159 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1160 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1161 : ok_vlook(charset_7bit);
1162 jleave:
1163 /* TODO mime_type_file_classify() shouldn't return conversion */
1164 if (rfc822) {
1165 if (mtc & _MT_C_FROM_1STLINE) {
1166 n_err(_("Pre-v15 %s cannot handle message/rfc822 that "
1167 "indeed is a RFC 4155 MBOX!\n"
1168 " Forcing a content-type of application/mbox!\n"),
1169 n_uagent);
1170 *contenttype = "application/mbox";
1171 goto jnorfc822;
1173 c = (menc == MIMEE_7B ? CONV_7BIT
1174 : (menc == MIMEE_8B ? CONV_8BIT
1175 /* May have only 7-bit, 8-bit and binary. Try to avoid latter */
1176 : ((mtc & _MT_C_HASNUL) ? CONV_NONE
1177 : ((mtc & _MT_C_HIGHBIT) ? CONV_8BIT : CONV_7BIT))));
1178 } else
1179 jnorfc822:
1180 c = (menc == MIMEE_7B ? CONV_7BIT
1181 : (menc == MIMEE_8B ? CONV_8BIT
1182 : (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1183 NYD_LEAVE;
1184 return c;
1187 FL enum mimecontent
1188 n_mimetype_classify_part(struct mimepart *mpp) /* FIXME charset=binary ??? */
1190 struct mtlookup mtl;
1191 enum mimecontent mc;
1192 char const *ct;
1193 union {char const *cp; ui32_t f;} mce;
1194 bool_t is_os;
1195 NYD_ENTER;
1197 mc = MIME_UNKNOWN;
1198 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1199 ct = n_empty;
1201 if((mce.cp = ok_vlook(mime_counter_evidence)) != NULL && *mce.cp != '\0'){
1202 if((n_idec_ui32_cp(&mce.f, mce.cp, 0, NULL
1203 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1204 ) != n_IDEC_STATE_CONSUMED){
1205 n_err(_("Invalid *mime-counter-evidence* value content\n"));
1206 is_os = FAL0;
1207 }else{
1208 mce.f |= MIMECE_SET;
1209 is_os = !asccasecmp(ct, "application/octet-stream");
1211 if(mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))){
1212 if(_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL){
1213 if(is_os)
1214 goto jos_content_check;
1215 }else if(is_os || asccasecmp(ct, mtl.mtl_result)){
1216 if(mce.f & MIMECE_ALL_OVWR)
1217 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1218 if(mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1219 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1223 }else
1224 is_os = FAL0;
1226 if(*ct == '\0' || strchr(ct, '/') == NULL) /* For compat with non-MIME */
1227 mc = MIME_TEXT;
1228 else if(is_asccaseprefix("text/", ct)){
1229 ct += sizeof("text/") -1;
1230 if(!asccasecmp(ct, "plain"))
1231 mc = MIME_TEXT_PLAIN;
1232 else if(!asccasecmp(ct, "html"))
1233 mc = MIME_TEXT_HTML;
1234 else
1235 mc = MIME_TEXT;
1236 }else if(is_asccaseprefix("message/", ct)){
1237 ct += sizeof("message/") -1;
1238 if(!asccasecmp(ct, "rfc822"))
1239 mc = MIME_822;
1240 else
1241 mc = MIME_MESSAGE;
1242 }else if(is_asccaseprefix("multipart/", ct)){
1243 struct multi_types{
1244 char mt_name[12];
1245 enum mimecontent mt_mc;
1246 } const mta[] = {
1247 {"alternative\0", MIME_ALTERNATIVE},
1248 {"related", MIME_RELATED},
1249 {"digest", MIME_DIGEST},
1250 {"signed", MIME_SIGNED},
1251 {"encrypted", MIME_ENCRYPTED}
1252 }, *mtap;
1254 for(ct += sizeof("multipart/") -1, mtap = mta;;)
1255 if(!asccasecmp(ct, mtap->mt_name)){
1256 mc = mtap->mt_mc;
1257 break;
1258 }else if(++mtap == mta + n_NELEM(mta)){
1259 mc = MIME_MULTI;
1260 break;
1262 }else if(is_asccaseprefix("application/", ct)){
1263 if(is_os)
1264 goto jos_content_check;
1265 ct += sizeof("application/") -1;
1266 if(!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1267 mc = MIME_PKCS7;
1269 jleave:
1270 NYD_LEAVE;
1271 return mc;
1273 jos_content_check:
1274 if((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1275 mpp->m_charset != NULL && asccasecmp(mpp->m_charset, "binary"))
1276 mc = _mt_classify_os_part(mce.f, mpp);
1277 goto jleave;
1280 FL enum mime_handler_flags
1281 n_mimetype_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1282 enum sendaction action)
1284 #define __S "pipe-"
1285 #define __L (sizeof(__S) -1)
1286 struct mtlookup mtl;
1287 char *buf, *cp;
1288 enum mime_handler_flags rv, xrv;
1289 char const *es, *cs, *ccp;
1290 size_t el, cl, l;
1291 NYD_ENTER;
1293 memset(mhp, 0, sizeof *mhp);
1294 buf = NULL;
1296 rv = MIME_HDL_NULL;
1297 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1298 rv |= MIME_HDL_ISQUOTE;
1299 else if (action != SEND_TODISP && action != SEND_TODISP_ALL &&
1300 action != SEND_TODISP_PARTS)
1301 goto jleave;
1303 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1304 *++es != '\0') ? strlen(es) : 0;
1305 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1306 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1307 if ((l = n_MAX(el, cl)) == 0) {
1308 /* TODO this should be done during parse time! */
1309 goto jleave;
1312 /* We don't pass the flags around, so ensure carrier is up-to-date */
1313 mhp->mh_flags = rv;
1315 buf = n_lofi_alloc(__L + l +1);
1316 memcpy(buf, __S, __L);
1318 /* File-extension handlers take precedence.
1319 * Yes, we really "fail" here for file extensions which clash MIME types */
1320 if (el > 0) {
1321 memcpy(buf + __L, es, el +1);
1322 for (cp = buf + __L; *cp != '\0'; ++cp)
1323 *cp = lowerconv(*cp);
1325 if ((mhp->mh_shell_cmd = ccp = n_var_vlook(buf, FAL0)) != NULL) {
1326 rv = a_mt_pipe_check(mhp);
1327 goto jleave;
1331 /* Then MIME Content-Type:, if any */
1332 if (cl == 0)
1333 goto jleave;
1335 memcpy(buf + __L, cs, cl +1);
1336 for (cp = buf + __L; *cp != '\0'; ++cp)
1337 *cp = lowerconv(*cp);
1339 if ((mhp->mh_shell_cmd = n_var_vlook(buf, FAL0)) != NULL) {
1340 rv = a_mt_pipe_check(mhp);
1341 goto jleave;
1344 if (_mt_by_mtname(&mtl, cs) != NULL)
1345 switch (mtl.mtl_node->mt_flags & a_MT__TM_MARKMASK) {
1346 #ifndef HAVE_FILTER_HTML_TAGSOUP
1347 case a_MT_TM_SOUP_H:
1348 break;
1349 #endif
1350 case a_MT_TM_SOUP_h:
1351 #ifdef HAVE_FILTER_HTML_TAGSOUP
1352 case a_MT_TM_SOUP_H:
1353 mhp->mh_ptf = &htmlflt_process_main;
1354 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1355 n_UNCONST(_("Built-in HTML tagsoup filter")));
1356 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1357 goto jleave;
1358 #endif
1359 /* FALLTHRU */
1360 case a_MT_TM_PLAIN:
1361 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
1362 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1363 goto jleave;
1364 case a_MT_TM_QUIET:
1365 mhp->mh_msg.l = 0;
1366 mhp->mh_msg.s = n_UNCONST(n_empty);
1367 goto jleave;
1368 default:
1369 break;
1372 jleave:
1373 if(buf != NULL)
1374 n_lofi_free(buf);
1376 xrv = rv;
1377 if((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL){
1378 if(mhp->mh_msg.s == NULL)
1379 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1380 _("[-- No MIME handler installed, or not applicable --]\n")));
1381 }else if(rv == MIME_HDL_CMD && !(xrv & MIME_HDL_COPIOUSOUTPUT) &&
1382 action != SEND_TODISP_PARTS){
1383 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1384 _("[-- Use the command `mimeview' to display this --]\n")));
1385 xrv &= ~MIME_HDL_TYPE_MASK;
1386 xrv |= (rv = MIME_HDL_MSG);
1388 mhp->mh_flags = xrv;
1390 NYD_LEAVE;
1391 return rv;
1392 #undef __L
1393 #undef __S
1396 /* s-it-mode */