`vexpr': fix error message for shift operators
[s-mailx.git] / mime-types.c
blob845ed821f67119f05b2aa79345e129d225a234f1
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_NONE,
53 _MT_C_CLEAN = _MT_C_NONE, /* Plain RFC 5322 message */
54 _MT_C_DEEP_INSPECT = 1u<<0, /* Always test all the file */
55 _MT_C_NCTT = 1u<<1, /* *contenttype == NULL */
56 _MT_C_ISTXT = 1u<<2, /* *contenttype =~ text\/ */
57 _MT_C_ISTXTCOK = 1u<<3, /* _ISTXT + *mime-allow-text-controls* */
58 _MT_C_HIGHBIT = 1u<<4, /* Not 7bit clean */
59 _MT_C_LONGLINES = 1u<<5, /* MIME_LINELEN_LIMIT exceed. */
60 _MT_C_CTRLCHAR = 1u<<6, /* Control characters seen */
61 _MT_C_HASNUL = 1u<<7, /* Contains \0 characters */
62 _MT_C_NOTERMNL = 1u<<8, /* Lacks a final newline */
63 _MT_C_FROM_ = 1u<<9, /* ^From_ seen */
64 _MT_C_FROM_1STLINE = 1u<<10, /* From_ line seen */
65 _MT_C_SUGGEST_DONE = 1u<<16, /* Inspector suggests to stop further parse */
66 _MT_C__1STLINE = 1u<<17 /* .. */
69 struct mtbltin {
70 ui32_t mtb_flags;
71 ui32_t mtb_mtlen;
72 char const *mtb_line;
75 struct mtnode {
76 struct mtnode *mt_next;
77 ui32_t mt_flags;
78 ui32_t mt_mtlen; /* Length of MIME type string, rest thereafter */
79 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
80 * that could already store character data here */
81 char const *mt_line;
84 struct mtlookup {
85 char const *mtl_name;
86 size_t mtl_nlen;
87 struct mtnode const *mtl_node;
88 char *mtl_result; /* If requested, salloc()ed MIME type */
91 struct mt_class_arg {
92 char const *mtca_buf;
93 size_t mtca_len;
94 ssize_t mtca_curlnlen;
95 /*char mtca_lastc;*/
96 char mtca_c;
97 ui8_t mtca__dummy[3];
98 enum mime_type_class mtca_mtc;
99 ui64_t mtca_all_len;
100 ui64_t mtca_all_highbit; /* TODO not yet interpreted */
101 ui64_t mtca_all_bogus;
104 static struct mtbltin const _mt_bltin[] = {
105 #include <gen-mime-types.h>
108 static char const _mt_typnames[][16] = {
109 "application/", "audio/", "image/",
110 "message/", "multipart/", "text/",
111 "video/"
113 n_CTAV(_MT_APPLICATION == 0 && _MT_AUDIO == 1 && _MT_IMAGE == 2 &&
114 _MT_MESSAGE == 3 && _MT_MULTIPART == 4 && _MT_TEXT == 5 &&
115 _MT_VIDEO == 6);
117 /* */
118 static bool_t _mt_is_init;
119 static struct mtnode *_mt_list;
121 /* Initialize MIME type list in order */
122 static void _mt_init(void);
123 static bool_t __mt_load_file(ui32_t orflags,
124 char const *file, char **line, size_t *linesize);
126 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
127 * for `mimetype' */
128 static struct mtnode * _mt_create(bool_t cmdcalled, ui32_t orflags,
129 char const *line, size_t len);
131 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
132 * if with_result >mtl_result will be created upon success for the former */
133 static struct mtlookup * _mt_by_filename(struct mtlookup *mtlp,
134 char const *name, bool_t with_result);
135 static struct mtlookup * _mt_by_mtname(struct mtlookup *mtlp,
136 char const *mtname);
138 /* In-depth inspection of raw content: call _round() repeatedly, last time with
139 * a 0 length buffer, finally check .mtca_mtc for result.
140 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
141 * as the resulting classification is unambiguous */
142 n_INLINE struct mt_class_arg * _mt_classify_init(struct mt_class_arg *mtcap,
143 enum mime_type_class initval);
144 static enum mime_type_class _mt_classify_round(struct mt_class_arg *mtcap);
146 /* We need an in-depth inspection of an application/octet-stream part */
147 static enum mimecontent _mt_classify_os_part(ui32_t mce, struct mimepart *mpp,
148 bool_t deep_inspect);
150 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
151 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
152 * isn't changed if mhp doesn't apply */
153 static enum mime_handler_flags a_mt_pipe_check(struct mime_handler *mhp);
155 static void
156 _mt_init(void)
158 struct mtnode *tail;
159 char c, *line; /* TODO line pool (below) */
160 size_t linesize;
161 ui32_t i, j;
162 char const *srcs_arr[10], *ccp, **srcs;
163 NYD_ENTER;
165 /*if (_mt_is_init)
166 * goto jleave;*/
168 /* Always load our built-ins */
169 for (tail = NULL, i = 0; i < n_NELEM(_mt_bltin); ++i) {
170 struct mtbltin const *mtbp = _mt_bltin + i;
171 struct mtnode *mtnp = smalloc(sizeof *mtnp);
173 if (tail != NULL)
174 tail->mt_next = mtnp;
175 else
176 _mt_list = mtnp;
177 tail = mtnp;
178 mtnp->mt_next = NULL;
179 mtnp->mt_flags = mtbp->mtb_flags;
180 mtnp->mt_mtlen = mtbp->mtb_mtlen;
181 mtnp->mt_line = mtbp->mtb_line;
184 /* Decide which files sources have to be loaded */
185 if ((ccp = ok_vlook(mimetypes_load_control)) == NULL)
186 ccp = "US";
187 else if (*ccp == '\0')
188 goto jleave;
190 srcs = srcs_arr + 2;
191 srcs[-1] = srcs[-2] = NULL;
193 if (strchr(ccp, '=') != NULL) {
194 line = savestr(ccp);
196 while ((ccp = n_strsep(&line, ',', TRU1)) != NULL) {
197 switch ((c = *ccp)) {
198 case 'S': case 's':
199 srcs_arr[1] = VAL_MIME_TYPES_SYS;
200 if (0) {
201 /* FALLTHRU */
202 case 'U': case 'u':
203 srcs_arr[0] = VAL_MIME_TYPES_USR;
205 if (ccp[1] != '\0')
206 goto jecontent;
207 break;
208 case 'F': case 'f':
209 if (*++ccp == '=' && *++ccp != '\0') {
210 if (PTR2SIZE(srcs - srcs_arr) < n_NELEM(srcs_arr))
211 *srcs++ = ccp;
212 else
213 n_err(_("*mimetypes-load-control*: too many sources, "
214 "skipping %s\n"), n_shexp_quote_cp(ccp, FAL0));
215 continue;
217 /* FALLTHRU */
218 default:
219 goto jecontent;
222 } else for (i = 0; (c = ccp[i]) != '\0'; ++i)
223 switch (c) {
224 case 'S': case 's': srcs_arr[1] = VAL_MIME_TYPES_SYS; break;
225 case 'U': case 'u': srcs_arr[0] = VAL_MIME_TYPES_USR; break;
226 default:
227 jecontent:
228 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp);
229 goto jleave;
232 /* Load all file-based sources in the desired order */
233 line = NULL;
234 linesize = 0;
235 for (j = 0, i = (ui32_t)PTR2SIZE(srcs - srcs_arr), srcs = srcs_arr;
236 i > 0; ++j, ++srcs, --i)
237 if (*srcs == NULL)
238 continue;
239 else if (!__mt_load_file((j == 0 ? _MT_USR
240 : (j == 1 ? _MT_SYS : _MT_FSPEC)), *srcs, &line, &linesize)) {
241 if ((n_poption & n_PO_D_V) || j > 1)
242 n_err(_("*mimetypes-load-control*: cannot open or load %s\n"),
243 n_shexp_quote_cp(*srcs, FAL0));
245 if (line != NULL)
246 free(line);
247 jleave:
248 _mt_is_init = TRU1;
249 NYD_LEAVE;
252 static bool_t
253 __mt_load_file(ui32_t orflags, char const *file, char **line, size_t *linesize)
255 char const *cp;
256 FILE *fp;
257 struct mtnode *head, *tail, *mtnp;
258 size_t len;
259 NYD_ENTER;
261 if ((cp = fexpand(file, FEXP_LOCAL | FEXP_NOPROTO)) == NULL ||
262 (fp = Fopen(cp, "r")) == NULL) {
263 cp = NULL;
264 goto jleave;
267 for (head = tail = NULL; fgetline(line, linesize, NULL, &len, fp, 0) != 0;)
268 if ((mtnp = _mt_create(FAL0, orflags, *line, len)) != NULL) {
269 if (head == NULL)
270 head = tail = mtnp;
271 else
272 tail->mt_next = mtnp;
273 tail = mtnp;
275 if (head != NULL) {
276 tail->mt_next = _mt_list;
277 _mt_list = head;
280 Fclose(fp);
281 jleave:
282 NYD_LEAVE;
283 return (cp != NULL);
286 static struct mtnode *
287 _mt_create(bool_t cmdcalled, ui32_t orflags, char const *line, size_t len)
289 struct mtnode *mtnp;
290 char const *typ, *subtyp;
291 size_t tlen, i;
292 NYD_ENTER;
294 mtnp = NULL;
296 /* Drop anything after a comment first TODO v15: only when read from file */
297 if ((typ = memchr(line, '#', len)) != NULL)
298 len = PTR2SIZE(typ - line);
300 /* Then trim any trailing whitespace from line (including NL/CR) */
301 /* C99 */{
302 struct str work;
304 work.s = n_UNCONST(line);
305 work.l = len;
306 line = n_str_trim(&work, n_STR_TRIM_BOTH)->s;
307 len = work.l;
309 typ = line;
311 /* (But wait - is there a type marker?) */
312 tlen = len;
313 if(!(orflags & (_MT_USR | _MT_SYS)) && *typ == '@'){
314 if(len < 2)
315 goto jeinval;
316 if(typ[1] == ' '){
317 orflags |= a_MT_TM_PLAIN;
318 typ += 2;
319 len -= 2;
320 line += 2;
321 }else if(len > 3){
322 if(typ[2] == ' ')
323 i = 3;
324 else if(len > 4 && typ[2] == '@' && typ[3] == ' '){
325 n_OBSOLETE("`mimetype': the trailing \"@\" in \"type-marker\" "
326 "is redundant");
327 i = 4;
328 }else
329 goto jeinval;
331 switch(typ[1]){
332 default: goto jeinval;
333 case 't': orflags |= a_MT_TM_PLAIN; break;
334 case 'h': orflags |= a_MT_TM_SOUP_h; break;
335 case 'H': orflags |= a_MT_TM_SOUP_H; break;
336 case 'q': orflags |= a_MT_TM_QUIET; break;
338 typ += i;
339 len -= i;
340 line += i;
341 }else
342 goto jeinval;
345 while (len > 0 && !blankchar(*line))
346 ++line, --len;
347 /* Ignore empty lines and even incomplete specifications (only MIME type)
348 * because this is quite common in mime.types(5) files */
349 if (len == 0 || (tlen = PTR2SIZE(line - typ)) == 0) {
350 if (cmdcalled || (orflags & _MT_FSPEC)) {
351 if(len == 0){
352 line = _("(no value)");
353 len = strlen(line);
355 n_err(_("Empty MIME type or no extensions given: %.*s\n"),
356 (int)len, line);
358 goto jleave;
361 if ((subtyp = memchr(typ, '/', tlen)) == NULL || subtyp[1] == '\0' ||
362 spacechar(subtyp[1])) {
363 jeinval:
364 if(cmdcalled || (orflags & _MT_FSPEC) || (n_poption & n_PO_D_V))
365 n_err(_("%s MIME type: %.*s\n"),
366 (cmdcalled ? _("Invalid") : _("mime.types(5): invalid")),
367 (int)tlen, typ);
368 goto jleave;
370 ++subtyp;
372 /* Map to mime_type */
373 tlen = PTR2SIZE(subtyp - typ);
374 for (i = __MT_TMIN;;) {
375 if (!ascncasecmp(_mt_typnames[i], typ, tlen)) {
376 orflags |= i;
377 tlen = PTR2SIZE(line - subtyp);
378 typ = subtyp;
379 break;
381 if (++i == __MT_TMAX) {
382 orflags |= _MT_OTHER;
383 tlen = PTR2SIZE(line - typ);
384 break;
388 /* Strip leading whitespace from the list of extensions;
389 * trailing WS has already been trimmed away above.
390 * Be silent on slots which define a mimetype without any value */
391 while (len > 0 && blankchar(*line))
392 ++line, --len;
393 if (len == 0)
394 goto jleave;
396 /* */
397 mtnp = smalloc(sizeof(*mtnp) + tlen + len +1);
398 mtnp->mt_next = NULL;
399 mtnp->mt_flags = orflags;
400 mtnp->mt_mtlen = (ui32_t)tlen;
401 { char *l = (char*)(mtnp + 1);
402 mtnp->mt_line = l;
403 memcpy(l, typ, tlen);
404 memcpy(l + tlen, line, len);
405 tlen += len;
406 l[tlen] = '\0';
409 jleave:
410 NYD_LEAVE;
411 return mtnp;
414 static struct mtlookup *
415 _mt_by_filename(struct mtlookup *mtlp, char const *name, bool_t with_result)
417 struct mtnode *mtnp;
418 size_t nlen, i, j;
419 char const *ext, *cp;
420 NYD2_ENTER;
422 memset(mtlp, 0, sizeof *mtlp);
424 if ((nlen = strlen(name)) == 0) /* TODO name should be a URI */
425 goto jnull_leave;
426 /* We need a period TODO we should support names like README etc. */
427 for (i = nlen; name[--i] != '.';)
428 if (i == 0 || name[i] == '/') /* XXX no magics */
429 goto jnull_leave;
430 /* While here, basename() it */
431 while (i > 0 && name[i - 1] != '/')
432 --i;
433 name += i;
434 nlen -= i;
435 mtlp->mtl_name = name;
436 mtlp->mtl_nlen = nlen;
438 if (!_mt_is_init)
439 _mt_init();
441 /* ..all the MIME types */
442 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next)
443 for (ext = mtnp->mt_line + mtnp->mt_mtlen;; ext = cp) {
444 cp = ext;
445 while (whitechar(*cp))
446 ++cp;
447 ext = cp;
448 while (!whitechar(*cp) && *cp != '\0')
449 ++cp;
451 if ((i = PTR2SIZE(cp - ext)) == 0)
452 break;
453 /* Don't allow neither of ".txt" or "txt" to match "txt" */
454 else if (i + 1 >= nlen || name[(j = nlen - i) - 1] != '.' ||
455 ascncasecmp(name + j, ext, i))
456 continue;
458 /* Found it */
459 mtlp->mtl_node = mtnp;
461 if (!with_result)
462 goto jleave;
464 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
465 name = n_empty;
466 j = 0;
467 } else {
468 name = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
469 j = strlen(name);
471 i = mtnp->mt_mtlen;
472 mtlp->mtl_result = salloc(i + j +1);
473 if (j > 0)
474 memcpy(mtlp->mtl_result, name, j);
475 memcpy(mtlp->mtl_result + j, mtnp->mt_line, i);
476 mtlp->mtl_result[j += i] = '\0';
477 goto jleave;
479 jnull_leave:
480 mtlp = NULL;
481 jleave:
482 NYD2_LEAVE;
483 return mtlp;
486 static struct mtlookup *
487 _mt_by_mtname(struct mtlookup *mtlp, char const *mtname)
489 struct mtnode *mtnp;
490 size_t nlen, i, j;
491 char const *cp;
492 NYD2_ENTER;
494 memset(mtlp, 0, sizeof *mtlp);
496 if ((mtlp->mtl_nlen = nlen = strlen(mtlp->mtl_name = mtname)) == 0)
497 goto jnull_leave;
499 if (!_mt_is_init)
500 _mt_init();
502 /* ..all the MIME types */
503 for (mtnp = _mt_list; mtnp != NULL; mtnp = mtnp->mt_next) {
504 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
505 cp = n_empty;
506 j = 0;
507 } else {
508 cp = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
509 j = strlen(cp);
511 i = mtnp->mt_mtlen;
513 if (i + j == mtlp->mtl_nlen) {
514 char *xmt = ac_alloc(i + j +1);
515 if (j > 0)
516 memcpy(xmt, cp, j);
517 memcpy(xmt + j, mtnp->mt_line, i);
518 xmt[j += i] = '\0';
519 i = asccasecmp(mtname, xmt);
520 ac_free(xmt);
522 if (!i) {
523 /* Found it */
524 mtlp->mtl_node = mtnp;
525 goto jleave;
529 jnull_leave:
530 mtlp = NULL;
531 jleave:
532 NYD2_LEAVE;
533 return mtlp;
536 n_INLINE struct mt_class_arg *
537 _mt_classify_init(struct mt_class_arg * mtcap, enum mime_type_class initval)
539 NYD2_ENTER;
540 memset(mtcap, 0, sizeof *mtcap);
541 /*mtcap->mtca_lastc =*/ mtcap->mtca_c = EOF;
542 mtcap->mtca_mtc = initval | _MT_C__1STLINE;
543 NYD2_LEAVE;
544 return mtcap;
547 static enum mime_type_class
548 _mt_classify_round(struct mt_class_arg *mtcap) /* TODO dig UTF-8 for !text/!! */
550 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
551 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
552 * TODO and report that state to the outer world */
553 #define F_ "From "
554 #define F_SIZEOF (sizeof(F_) -1)
555 char f_buf[F_SIZEOF], *f_p = f_buf;
556 char const *buf;
557 size_t blen;
558 ssize_t curlnlen;
559 si64_t alllen;
560 int c, lastc;
561 enum mime_type_class mtc;
562 NYD2_ENTER;
564 buf = mtcap->mtca_buf;
565 blen = mtcap->mtca_len;
566 curlnlen = mtcap->mtca_curlnlen;
567 alllen = mtcap->mtca_all_len;
568 c = mtcap->mtca_c;
569 /*lastc = mtcap->mtca_lastc;*/
570 mtc = mtcap->mtca_mtc;
572 for (;; ++curlnlen) {
573 if(blen == 0){
574 /* Real EOF, or only current buffer end? */
575 if(mtcap->mtca_len == 0){
576 lastc = c;
577 c = EOF;
578 }else{
579 lastc = EOF;
580 break;
582 }else{
583 ++alllen;
584 lastc = c;
585 c = (uc_i)*buf++;
587 --blen;
589 if (c == '\0') {
590 mtc |= _MT_C_HASNUL;
591 if (!(mtc & _MT_C_ISTXTCOK)) {
592 mtc |= _MT_C_SUGGEST_DONE;
593 break;
595 continue;
597 if (c == '\n' || c == EOF) {
598 mtc &= ~_MT_C__1STLINE;
599 if (curlnlen >= MIME_LINELEN_LIMIT)
600 mtc |= _MT_C_LONGLINES;
601 if (c == EOF)
602 break;
603 f_p = f_buf;
604 curlnlen = -1;
605 continue;
607 /* A bit hairy is handling of \r=\x0D=CR.
608 * RFC 2045, 6.7:
609 * Control characters other than TAB, or CR and LF as parts of CRLF
610 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
611 * we cannot peek the next character. Thus right here, inspect the last
612 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
613 /*else*/ if (lastc == '\r')
614 mtc |= _MT_C_CTRLCHAR;
616 /* Control character? XXX this is all ASCII here */
617 if (c < 0x20 || c == 0x7F) {
618 /* RFC 2045, 6.7, as above ... */
619 if (c != '\t' && c != '\r')
620 mtc |= _MT_C_CTRLCHAR;
622 /* If there is a escape sequence in reverse solidus notation defined
623 * for this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
624 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
625 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
626 * \e=\x1B=ESC */
627 if ((c >= '\x07' && c <= '\x0D') || c == '\x1B')
628 continue;
630 /* As a special case, if we are going for displaying data to the user
631 * or quoting a message then simply continue this, in the end, in case
632 * we get there, we will decide upon the all_len/all_bogus ratio
633 * whether this is usable plain text or not */
634 ++mtcap->mtca_all_bogus;
635 if(mtc & _MT_C_DEEP_INSPECT)
636 continue;
638 mtc |= _MT_C_HASNUL; /* Force base64 */
639 if (!(mtc & _MT_C_ISTXTCOK)) {
640 mtc |= _MT_C_SUGGEST_DONE;
641 break;
643 } else if ((ui8_t)c & 0x80) {
644 mtc |= _MT_C_HIGHBIT;
645 ++mtcap->mtca_all_highbit;
646 if (!(mtc & (_MT_C_NCTT | _MT_C_ISTXT))) { /* TODO _NCTT?? */
647 mtc |= _MT_C_HASNUL /* Force base64 */ | _MT_C_SUGGEST_DONE;
648 break;
650 } else if (!(mtc & _MT_C_FROM_) && UICMP(z, curlnlen, <, F_SIZEOF)) {
651 *f_p++ = (char)c;
652 if (UICMP(z, curlnlen, ==, F_SIZEOF - 1) &&
653 PTR2SIZE(f_p - f_buf) == F_SIZEOF &&
654 !memcmp(f_buf, F_, F_SIZEOF)){
655 mtc |= _MT_C_FROM_;
656 if (mtc & _MT_C__1STLINE)
657 mtc |= _MT_C_FROM_1STLINE;
661 if (c == EOF && lastc != '\n')
662 mtc |= _MT_C_NOTERMNL;
664 mtcap->mtca_curlnlen = curlnlen;
665 /*mtcap->mtca_lastc = lastc*/;
666 mtcap->mtca_c = c;
667 mtcap->mtca_mtc = mtc;
668 mtcap->mtca_all_len = alllen;
669 NYD2_LEAVE;
670 return mtc;
671 #undef F_
672 #undef F_SIZEOF
675 static enum mimecontent
676 _mt_classify_os_part(ui32_t mce, struct mimepart *mpp, bool_t deep_inspect)
678 struct str in = {NULL, 0}, outrest, inrest, dec;
679 struct mt_class_arg mtca;
680 bool_t did_inrest;
681 enum mime_type_class mtc;
682 int lc, c;
683 size_t cnt, lsz;
684 FILE *ibuf;
685 off_t start_off;
686 enum mimecontent mc;
687 NYD2_ENTER;
689 assert(mpp->m_mime_enc != MIMEE_BIN);
691 outrest = inrest = dec = in;
692 mc = MIME_UNKNOWN;
693 n_UNINIT(mtc, 0);
694 did_inrest = FAL0;
696 /* TODO v15-compat Note we actually bypass our usual file handling by
697 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
698 * TODO all of this, and until then doing it like this is the only option
699 * TODO to integrate nicely into whoever calls us */
700 start_off = ftell(mb.mb_itf);
701 if ((ibuf = setinput(&mb, (struct message*)mpp, NEED_BODY)) == NULL) {
702 jos_leave:
703 fseek(mb.mb_itf, start_off, SEEK_SET);
704 goto jleave;
706 cnt = mpp->m_size;
708 /* Skip part headers */
709 for (lc = '\0'; cnt > 0; lc = c, --cnt)
710 if ((c = getc(ibuf)) == EOF || (c == '\n' && lc == '\n'))
711 break;
712 if (cnt == 0 || ferror(ibuf))
713 goto jos_leave;
715 /* So now let's inspect the part content, decoding content-transfer-encoding
716 * along the way TODO this should simply be "mime_factory_create(MPP)"!
717 * TODO In fact m_mime_classifier_(setup|call|call_part|finalize)() and the
718 * TODO state(s) (the _MT_C states) should become reported to the outer
719 * TODO world like that (see MIME boundary TODO around here) */
720 _mt_classify_init(&mtca, (_MT_C_ISTXT |
721 (deep_inspect ? _MT_C_DEEP_INSPECT : _MT_C_NONE)));
723 for (lsz = 0;;) {
724 bool_t dobuf;
726 c = (--cnt == 0) ? EOF : getc(ibuf);
727 if ((dobuf = (c == '\n'))) {
728 /* Ignore empty lines */
729 if (lsz == 0)
730 continue;
731 } else if ((dobuf = (c == EOF))) {
732 if (lsz == 0 && outrest.l == 0)
733 break;
736 if (in.l + 1 >= lsz)
737 in.s = srealloc(in.s, lsz += LINESIZE);
738 if (c != EOF)
739 in.s[in.l++] = (char)c;
740 if (!dobuf)
741 continue;
743 jdobuf:
744 switch (mpp->m_mime_enc) {
745 case MIMEE_B64:
746 if (!b64_decode_part(&dec, &in, &outrest,
747 (did_inrest ? NULL : &inrest))) {
748 mtca.mtca_mtc = _MT_C_HASNUL;
749 goto jstopit; /* break;break; */
751 break;
752 case MIMEE_QP:
753 /* Drin */
754 if (!qp_decode_part(&dec, &in, &outrest, &inrest)) {
755 mtca.mtca_mtc = _MT_C_HASNUL;
756 goto jstopit; /* break;break; */
758 if (dec.l == 0 && c != EOF) {
759 in.l = 0;
760 continue;
762 break;
763 default:
764 /* Temporarily switch those two buffers.. */
765 dec = in;
766 in.s = NULL;
767 in.l = 0;
768 break;
771 mtca.mtca_buf = dec.s;
772 mtca.mtca_len = (ssize_t)dec.l;
773 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE) {
774 mtc = _MT_C_HASNUL;
775 break;
778 if (c == EOF)
779 break;
780 /* ..and restore switched */
781 if (in.s == NULL) {
782 in = dec;
783 dec.s = NULL;
785 in.l = dec.l = 0;
788 if ((in.l = inrest.l) > 0) {
789 in.s = inrest.s;
790 did_inrest = TRU1;
791 goto jdobuf;
793 if (outrest.l > 0)
794 goto jdobuf;
795 jstopit:
796 if (in.s != NULL)
797 free(in.s);
798 if (dec.s != NULL)
799 free(dec.s);
800 if (outrest.s != NULL)
801 free(outrest.s);
802 if (inrest.s != NULL)
803 free(inrest.s);
805 fseek(mb.mb_itf, start_off, SEEK_SET);
807 if (!(mtc & (_MT_C_HASNUL /*| _MT_C_CTRLCHAR XXX really? */))) {
808 /* In that special relaxed case we may very well wave through
809 * octet-streams full of control characters, as they do no harm
810 * TODO This should be part of m_mime_classifier_finalize() then! */
811 if(deep_inspect &&
812 mtca.mtca_all_len - mtca.mtca_all_bogus < mtca.mtca_all_len >> 2)
813 goto jleave;
815 mc = MIME_TEXT_PLAIN;
816 if (mce & MIMECE_ALL_OVWR)
817 mpp->m_ct_type_plain = "text/plain";
818 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
819 mpp->m_ct_type_usr_ovwr = "text/plain";
821 jleave:
822 NYD2_LEAVE;
823 return mc;
826 static enum mime_handler_flags
827 a_mt_pipe_check(struct mime_handler *mhp){
828 enum mime_handler_flags rv_orig, rv;
829 char const *cp;
830 NYD2_ENTER;
832 rv_orig = rv = mhp->mh_flags;
834 /* Do we have any handler for this part? */
835 if(*(cp = mhp->mh_shell_cmd) == '\0')
836 goto jleave;
837 else if(*cp++ != '@'){
838 rv |= MIME_HDL_CMD;
839 goto jleave;
840 }else if(*cp == '\0'){
841 rv |= MIME_HDL_TEXT;
842 goto jleave;
845 jnextc:
846 switch(*cp){
847 case '*': rv |= MIME_HDL_COPIOUSOUTPUT; ++cp; goto jnextc;
848 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
849 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
850 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
851 case '+':
852 if(rv & MIME_HDL_TMPF)
853 rv |= MIME_HDL_TMPF_UNLINK;
854 rv |= MIME_HDL_TMPF;
855 ++cp;
856 goto jnextc;
857 case '=':
858 rv |= MIME_HDL_TMPF_FILL;
859 ++cp;
860 goto jnextc;
861 case '@':
862 ++cp;
863 /* FALLTHRU */
864 default:
865 break;
867 mhp->mh_shell_cmd = cp;
869 /* Implications */
870 if(rv & MIME_HDL_TMPF_FILL)
871 rv |= MIME_HDL_TMPF;
873 /* Exceptions */
874 if(rv & MIME_HDL_ISQUOTE){
875 if(rv & MIME_HDL_NOQUOTE)
876 goto jerr;
878 /* Cannot fetch data back from asynchronous process */
879 if(rv & MIME_HDL_ASYNC)
880 goto jerr;
882 /* TODO Can't use a "needsterminal" program for quoting */
883 if(rv & MIME_HDL_NEEDSTERM)
884 goto jerr;
887 if(rv & MIME_HDL_NEEDSTERM){
888 if(rv & MIME_HDL_COPIOUSOUTPUT){
889 n_err(_("MIME type handlers: cannot use needsterminal and "
890 "copiousoutput together\n"));
891 goto jerr;
893 if(rv & MIME_HDL_ASYNC){
894 n_err(_("MIME type handlers: cannot use needsterminal and "
895 "x-mailx-async together\n"));
896 goto jerr;
899 /* needsterminal needs a terminal */
900 if(!(n_psonce & n_PSO_INTERACTIVE))
901 goto jerr;
904 if(rv & MIME_HDL_ASYNC){
905 if(rv & MIME_HDL_COPIOUSOUTPUT){
906 n_err(_("MIME type handlers: cannot use x-mailx-async and "
907 "copiousoutput together\n"));
908 goto jerr;
910 if(rv & MIME_HDL_TMPF_UNLINK){
911 n_err(_("MIME type handlers: cannot use x-mailx-async and "
912 "x-mailx-tmpfile-unlink together\n"));
913 goto jerr;
917 /* TODO mailcap-only: TMPF_UNLINK): needs -tmpfile OR -tmpfile-fill */
919 rv |= MIME_HDL_CMD;
920 jleave:
921 mhp->mh_flags = rv;
922 NYD2_LEAVE;
923 return rv;
924 jerr:
925 rv = rv_orig;
926 goto jleave;
929 FL int
930 c_mimetype(void *v){
931 struct n_string s, *sp;
932 struct mtnode *mtnp;
933 char **argv;
934 NYD_ENTER;
936 if(!_mt_is_init)
937 _mt_init();
939 sp = n_string_creat_auto(&s);
941 if(*(argv = v) == NULL){
942 FILE *fp;
943 size_t l;
945 if(_mt_list == NULL){
946 fprintf(n_stdout, _("# `mimetype': no mime.types(5) available\n"));
947 goto jleave;
950 if((fp = Ftmp(NULL, "mimetype", OF_RDWR | OF_UNLINK | OF_REGISTER)
951 ) == NULL){
952 n_perr(_("tmpfile"), 0);
953 v = NULL;
954 goto jleave;
957 sp = n_string_reserve(sp, 63);
959 for(l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next){
960 char const *cp;
962 sp = n_string_trunc(sp, 0);
964 switch(mtnp->mt_flags & a_MT__TM_MARKMASK){
965 case a_MT_TM_PLAIN: cp = "@t "; break;
966 case a_MT_TM_SOUP_h: cp = "@h "; break;
967 case a_MT_TM_SOUP_H: cp = "@H "; break;
968 case a_MT_TM_QUIET: cp = "@q "; break;
969 default: cp = NULL; break;
971 if(cp != NULL)
972 sp = n_string_push_cp(sp, cp);
974 if((mtnp->mt_flags & __MT_TMASK) != _MT_OTHER)
975 sp = n_string_push_cp(sp, _mt_typnames[mtnp->mt_flags &__MT_TMASK]);
977 sp = n_string_push_buf(sp, mtnp->mt_line, mtnp->mt_mtlen);
978 sp = n_string_push_c(sp, ' ');
979 sp = n_string_push_c(sp, ' ');
980 sp = n_string_push_cp(sp, &mtnp->mt_line[mtnp->mt_mtlen]);
982 fprintf(fp, "wysh mimetype %s%s\n", n_string_cp(sp),
983 ((n_poption & n_PO_D_V) == 0 ? n_empty
984 : (mtnp->mt_flags & _MT_USR ? " # user"
985 : (mtnp->mt_flags & _MT_SYS ? " # system"
986 : (mtnp->mt_flags & _MT_FSPEC ? " # f= file"
987 : (mtnp->mt_flags & _MT_CMD ? " # command" : " # built-in"))))));
990 page_or_print(fp, l);
991 Fclose(fp);
992 }else{
993 for(; *argv != NULL; ++argv){
994 if(sp->s_len > 0)
995 sp = n_string_push_c(sp, ' ');
996 sp = n_string_push_cp(sp, *argv);
999 mtnp = _mt_create(TRU1, _MT_CMD, n_string_cp(sp), sp->s_len);
1000 if(mtnp != NULL){
1001 mtnp->mt_next = _mt_list;
1002 _mt_list = mtnp;
1003 }else
1004 v = NULL;
1006 jleave:
1007 NYD_LEAVE;
1008 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1011 FL int
1012 c_unmimetype(void *v)
1014 char **argv = v;
1015 struct mtnode *lnp, *mtnp;
1016 bool_t match;
1017 NYD_ENTER;
1019 /* Need to load that first as necessary */
1020 if (!_mt_is_init)
1021 _mt_init();
1023 for (; *argv != NULL; ++argv) {
1024 if (!asccasecmp(*argv, "reset")) {
1025 _mt_is_init = FAL0;
1026 goto jdelall;
1029 if (argv[0][0] == '*' && argv[0][1] == '\0') {
1030 jdelall:
1031 while ((mtnp = _mt_list) != NULL) {
1032 _mt_list = mtnp->mt_next;
1033 free(mtnp);
1035 continue;
1038 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
1039 char const *typ;
1040 char *val;
1041 size_t i;
1043 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
1044 typ = n_empty;
1045 i = 0;
1046 } else {
1047 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
1048 i = strlen(typ);
1051 val = ac_alloc(i + mtnp->mt_mtlen +1);
1052 memcpy(val, typ, i);
1053 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
1054 val[i += mtnp->mt_mtlen] = '\0';
1055 i = asccasecmp(val, *argv);
1056 ac_free(val);
1058 if (!i) {
1059 struct mtnode *nnp = mtnp->mt_next;
1060 if (lnp == NULL)
1061 _mt_list = nnp;
1062 else
1063 lnp->mt_next = nnp;
1064 free(mtnp);
1065 mtnp = nnp;
1066 match = TRU1;
1067 } else
1068 lnp = mtnp, mtnp = mtnp->mt_next;
1070 if (!match) {
1071 if (!(n_pstate & n_PS_ROBOT) || (n_poption & n_PO_D_V))
1072 n_err(_("No such MIME type: %s\n"), *argv);
1073 v = NULL;
1076 NYD_LEAVE;
1077 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1080 FL bool_t
1081 n_mimetype_check_mtname(char const *name)
1083 struct mtlookup mtl;
1084 bool_t rv;
1085 NYD_ENTER;
1087 rv = (_mt_by_mtname(&mtl, name) != NULL);
1088 NYD_LEAVE;
1089 return rv;
1092 FL char *
1093 n_mimetype_classify_filename(char const *name)
1095 struct mtlookup mtl;
1096 NYD_ENTER;
1098 _mt_by_filename(&mtl, name, TRU1);
1099 NYD_LEAVE;
1100 return mtl.mtl_result;
1103 FL enum conversion
1104 n_mimetype_classify_file(FILE *fp, char const **contenttype,
1105 char const **charset, int *do_iconv)
1107 /* TODO classify once only PLEASE PLEASE PLEASE */
1108 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1109 * TODO binary according to RFC 2046, 5.2.1
1110 * TODO The handling of which is a hack */
1111 bool_t rfc822;
1112 enum mime_type_class mtc;
1113 enum mime_enc menc;
1114 off_t fpsz;
1115 enum conversion c;
1116 NYD_ENTER;
1118 assert(ftell(fp) == 0x0l);
1120 *do_iconv = 0;
1122 if (*contenttype == NULL) {
1123 mtc = _MT_C_NCTT;
1124 rfc822 = FAL0;
1125 } else if (!ascncasecmp(*contenttype, "text/", 5)) {
1126 mtc = ok_blook(mime_allow_text_controls)
1127 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1128 rfc822 = FAL0;
1129 } else if (!asccasecmp(*contenttype, "message/rfc822")) {
1130 mtc = _MT_C_ISTXT;
1131 rfc822 = TRU1;
1132 } else {
1133 mtc = _MT_C_CLEAN;
1134 rfc822 = FAL0;
1137 menc = mime_enc_target();
1139 if ((fpsz = fsize(fp)) == 0)
1140 goto j7bit;
1141 else {
1142 char buf[BUFFER_SIZE];
1143 struct mt_class_arg mtca;
1145 _mt_classify_init(&mtca, mtc);
1146 for (;;) {
1147 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1148 mtca.mtca_buf = buf;
1149 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1150 break;
1151 if (mtca.mtca_len == 0)
1152 break;
1154 /* TODO ferror(fp) ! */
1155 rewind(fp);
1158 if (mtc & _MT_C_HASNUL) {
1159 menc = MIMEE_B64;
1160 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1161 * on request; else enforce what file(1)/libmagic(3) would suggest */
1162 if (mtc & _MT_C_ISTXTCOK)
1163 goto jcharset;
1164 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1165 *contenttype = "application/octet-stream";
1166 /* if (*charset == NULL)
1167 * *charset = "binary";*/
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 _("[-- 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 */