quoteflt_dummy() usage: FIX usage without preceeding _reset() (Jürgen Bruckner)
[s-mailx.git] / mime-types.c
blob60b7526d5c0847017a4b89c4e83919235f9281a2
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>.
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 inrest.s = NULL;
791 did_inrest = TRU1;
792 goto jdobuf;
794 if (outrest.l > 0)
795 goto jdobuf;
796 jstopit:
797 if (in.s != NULL)
798 free(in.s);
799 if (dec.s != NULL)
800 free(dec.s);
801 if (outrest.s != NULL)
802 free(outrest.s);
803 if (inrest.s != NULL)
804 free(inrest.s);
806 fseek(mb.mb_itf, start_off, SEEK_SET);
808 if (!(mtc & (_MT_C_HASNUL /*| _MT_C_CTRLCHAR XXX really? */))) {
809 /* In that special relaxed case we may very well wave through
810 * octet-streams full of control characters, as they do no harm
811 * TODO This should be part of m_mime_classifier_finalize() then! */
812 if(deep_inspect &&
813 mtca.mtca_all_len - mtca.mtca_all_bogus < mtca.mtca_all_len >> 2)
814 goto jleave;
816 mc = MIME_TEXT_PLAIN;
817 if (mce & MIMECE_ALL_OVWR)
818 mpp->m_ct_type_plain = "text/plain";
819 if (mce & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
820 mpp->m_ct_type_usr_ovwr = "text/plain";
822 jleave:
823 NYD2_LEAVE;
824 return mc;
827 static enum mime_handler_flags
828 a_mt_pipe_check(struct mime_handler *mhp){
829 enum mime_handler_flags rv_orig, rv;
830 char const *cp;
831 NYD2_ENTER;
833 rv_orig = rv = mhp->mh_flags;
835 /* Do we have any handler for this part? */
836 if(*(cp = mhp->mh_shell_cmd) == '\0')
837 goto jleave;
838 else if(*cp++ != '@'){
839 rv |= MIME_HDL_CMD;
840 goto jleave;
841 }else if(*cp == '\0'){
842 rv |= MIME_HDL_TEXT;
843 goto jleave;
846 jnextc:
847 switch(*cp){
848 case '*': rv |= MIME_HDL_COPIOUSOUTPUT; ++cp; goto jnextc;
849 case '#': rv |= MIME_HDL_NOQUOTE; ++cp; goto jnextc;
850 case '&': rv |= MIME_HDL_ASYNC; ++cp; goto jnextc;
851 case '!': rv |= MIME_HDL_NEEDSTERM; ++cp; goto jnextc;
852 case '+':
853 if(rv & MIME_HDL_TMPF)
854 rv |= MIME_HDL_TMPF_UNLINK;
855 rv |= MIME_HDL_TMPF;
856 ++cp;
857 goto jnextc;
858 case '=':
859 rv |= MIME_HDL_TMPF_FILL;
860 ++cp;
861 goto jnextc;
862 case '@':
863 ++cp;
864 /* FALLTHRU */
865 default:
866 break;
868 mhp->mh_shell_cmd = cp;
870 /* Implications */
871 if(rv & MIME_HDL_TMPF_FILL)
872 rv |= MIME_HDL_TMPF;
874 /* Exceptions */
875 if(rv & MIME_HDL_ISQUOTE){
876 if(rv & MIME_HDL_NOQUOTE)
877 goto jerr;
879 /* Cannot fetch data back from asynchronous process */
880 if(rv & MIME_HDL_ASYNC)
881 goto jerr;
883 /* TODO Can't use a "needsterminal" program for quoting */
884 if(rv & MIME_HDL_NEEDSTERM)
885 goto jerr;
888 if(rv & MIME_HDL_NEEDSTERM){
889 if(rv & MIME_HDL_COPIOUSOUTPUT){
890 n_err(_("MIME type handlers: cannot use needsterminal and "
891 "copiousoutput together\n"));
892 goto jerr;
894 if(rv & MIME_HDL_ASYNC){
895 n_err(_("MIME type handlers: cannot use needsterminal and "
896 "x-mailx-async together\n"));
897 goto jerr;
900 /* needsterminal needs a terminal */
901 if(!(n_psonce & n_PSO_INTERACTIVE))
902 goto jerr;
905 if(rv & MIME_HDL_ASYNC){
906 if(rv & MIME_HDL_COPIOUSOUTPUT){
907 n_err(_("MIME type handlers: cannot use x-mailx-async and "
908 "copiousoutput together\n"));
909 goto jerr;
911 if(rv & MIME_HDL_TMPF_UNLINK){
912 n_err(_("MIME type handlers: cannot use x-mailx-async and "
913 "x-mailx-tmpfile-unlink together\n"));
914 goto jerr;
918 /* TODO mailcap-only: TMPF_UNLINK): needs -tmpfile OR -tmpfile-fill */
920 rv |= MIME_HDL_CMD;
921 jleave:
922 mhp->mh_flags = rv;
923 NYD2_LEAVE;
924 return rv;
925 jerr:
926 rv = rv_orig;
927 goto jleave;
930 FL int
931 c_mimetype(void *v){
932 struct n_string s, *sp;
933 struct mtnode *mtnp;
934 char **argv;
935 NYD_ENTER;
937 if(!_mt_is_init)
938 _mt_init();
940 sp = n_string_creat_auto(&s);
942 if(*(argv = v) == NULL){
943 FILE *fp;
944 size_t l;
946 if(_mt_list == NULL){
947 fprintf(n_stdout, _("# `mimetype': no mime.types(5) available\n"));
948 goto jleave;
951 if((fp = Ftmp(NULL, "mimetype", OF_RDWR | OF_UNLINK | OF_REGISTER)
952 ) == NULL){
953 n_perr(_("tmpfile"), 0);
954 v = NULL;
955 goto jleave;
958 sp = n_string_reserve(sp, 63);
960 for(l = 0, mtnp = _mt_list; mtnp != NULL; ++l, mtnp = mtnp->mt_next){
961 char const *cp;
963 sp = n_string_trunc(sp, 0);
965 switch(mtnp->mt_flags & a_MT__TM_MARKMASK){
966 case a_MT_TM_PLAIN: cp = "@t "; break;
967 case a_MT_TM_SOUP_h: cp = "@h "; break;
968 case a_MT_TM_SOUP_H: cp = "@H "; break;
969 case a_MT_TM_QUIET: cp = "@q "; break;
970 default: cp = NULL; break;
972 if(cp != NULL)
973 sp = n_string_push_cp(sp, cp);
975 if((mtnp->mt_flags & __MT_TMASK) != _MT_OTHER)
976 sp = n_string_push_cp(sp, _mt_typnames[mtnp->mt_flags &__MT_TMASK]);
978 sp = n_string_push_buf(sp, mtnp->mt_line, mtnp->mt_mtlen);
979 sp = n_string_push_c(sp, ' ');
980 sp = n_string_push_c(sp, ' ');
981 sp = n_string_push_cp(sp, &mtnp->mt_line[mtnp->mt_mtlen]);
983 fprintf(fp, "wysh mimetype %s%s\n", n_string_cp(sp),
984 ((n_poption & n_PO_D_V) == 0 ? n_empty
985 : (mtnp->mt_flags & _MT_USR ? " # user"
986 : (mtnp->mt_flags & _MT_SYS ? " # system"
987 : (mtnp->mt_flags & _MT_FSPEC ? " # f= file"
988 : (mtnp->mt_flags & _MT_CMD ? " # command" : " # built-in"))))));
991 page_or_print(fp, l);
992 Fclose(fp);
993 }else{
994 for(; *argv != NULL; ++argv){
995 if(sp->s_len > 0)
996 sp = n_string_push_c(sp, ' ');
997 sp = n_string_push_cp(sp, *argv);
1000 mtnp = _mt_create(TRU1, _MT_CMD, n_string_cp(sp), sp->s_len);
1001 if(mtnp != NULL){
1002 mtnp->mt_next = _mt_list;
1003 _mt_list = mtnp;
1004 }else
1005 v = NULL;
1007 jleave:
1008 NYD_LEAVE;
1009 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1012 FL int
1013 c_unmimetype(void *v)
1015 char **argv = v;
1016 struct mtnode *lnp, *mtnp;
1017 bool_t match;
1018 NYD_ENTER;
1020 /* Need to load that first as necessary */
1021 if (!_mt_is_init)
1022 _mt_init();
1024 for (; *argv != NULL; ++argv) {
1025 if (!asccasecmp(*argv, "reset")) {
1026 _mt_is_init = FAL0;
1027 goto jdelall;
1030 if (argv[0][0] == '*' && argv[0][1] == '\0') {
1031 jdelall:
1032 while ((mtnp = _mt_list) != NULL) {
1033 _mt_list = mtnp->mt_next;
1034 free(mtnp);
1036 continue;
1039 for (match = FAL0, lnp = NULL, mtnp = _mt_list; mtnp != NULL;) {
1040 char const *typ;
1041 char *val;
1042 size_t i;
1044 if ((mtnp->mt_flags & __MT_TMASK) == _MT_OTHER) {
1045 typ = n_empty;
1046 i = 0;
1047 } else {
1048 typ = _mt_typnames[mtnp->mt_flags & __MT_TMASK];
1049 i = strlen(typ);
1052 val = ac_alloc(i + mtnp->mt_mtlen +1);
1053 memcpy(val, typ, i);
1054 memcpy(val + i, mtnp->mt_line, mtnp->mt_mtlen);
1055 val[i += mtnp->mt_mtlen] = '\0';
1056 i = asccasecmp(val, *argv);
1057 ac_free(val);
1059 if (!i) {
1060 struct mtnode *nnp = mtnp->mt_next;
1061 if (lnp == NULL)
1062 _mt_list = nnp;
1063 else
1064 lnp->mt_next = nnp;
1065 free(mtnp);
1066 mtnp = nnp;
1067 match = TRU1;
1068 } else
1069 lnp = mtnp, mtnp = mtnp->mt_next;
1071 if (!match) {
1072 if (!(n_pstate & n_PS_ROBOT) || (n_poption & n_PO_D_V))
1073 n_err(_("No such MIME type: %s\n"), *argv);
1074 v = NULL;
1077 NYD_LEAVE;
1078 return (v == NULL ? !STOP : !OKAY); /* xxx 1:bad 0:good -- do some */
1081 FL bool_t
1082 n_mimetype_check_mtname(char const *name)
1084 struct mtlookup mtl;
1085 bool_t rv;
1086 NYD_ENTER;
1088 rv = (_mt_by_mtname(&mtl, name) != NULL);
1089 NYD_LEAVE;
1090 return rv;
1093 FL char *
1094 n_mimetype_classify_filename(char const *name)
1096 struct mtlookup mtl;
1097 NYD_ENTER;
1099 _mt_by_filename(&mtl, name, TRU1);
1100 NYD_LEAVE;
1101 return mtl.mtl_result;
1104 FL enum conversion
1105 n_mimetype_classify_file(FILE *fp, char const **contenttype,
1106 char const **charset, int *do_iconv)
1108 /* TODO classify once only PLEASE PLEASE PLEASE */
1109 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1110 * TODO binary according to RFC 2046, 5.2.1
1111 * TODO The handling of which is a hack */
1112 bool_t rfc822;
1113 enum mime_type_class mtc;
1114 enum mime_enc menc;
1115 off_t fpsz;
1116 enum conversion c;
1117 NYD_ENTER;
1119 assert(ftell(fp) == 0x0l);
1121 *do_iconv = 0;
1123 if (*contenttype == NULL) {
1124 mtc = _MT_C_NCTT;
1125 rfc822 = FAL0;
1126 } else if (!ascncasecmp(*contenttype, "text/", 5)) {
1127 mtc = ok_blook(mime_allow_text_controls)
1128 ? _MT_C_ISTXT | _MT_C_ISTXTCOK : _MT_C_ISTXT;
1129 rfc822 = FAL0;
1130 } else if (!asccasecmp(*contenttype, "message/rfc822")) {
1131 mtc = _MT_C_ISTXT;
1132 rfc822 = TRU1;
1133 } else {
1134 mtc = _MT_C_CLEAN;
1135 rfc822 = FAL0;
1138 menc = mime_enc_target();
1140 if ((fpsz = fsize(fp)) == 0)
1141 goto j7bit;
1142 else {
1143 char buf[BUFFER_SIZE];
1144 struct mt_class_arg mtca;
1146 _mt_classify_init(&mtca, mtc);
1147 for (;;) {
1148 mtca.mtca_len = fread(buf, sizeof(buf[0]), n_NELEM(buf), fp);
1149 mtca.mtca_buf = buf;
1150 if ((mtc = _mt_classify_round(&mtca)) & _MT_C_SUGGEST_DONE)
1151 break;
1152 if (mtca.mtca_len == 0)
1153 break;
1155 /* TODO ferror(fp) ! */
1156 rewind(fp);
1159 if (mtc & _MT_C_HASNUL) {
1160 menc = MIMEE_B64;
1161 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1162 * on request; else enforce what file(1)/libmagic(3) would suggest */
1163 if (mtc & _MT_C_ISTXTCOK)
1164 goto jcharset;
1165 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1166 *contenttype = "application/octet-stream";
1167 goto jleave;
1170 if (mtc &
1171 (_MT_C_LONGLINES | _MT_C_CTRLCHAR | _MT_C_NOTERMNL | _MT_C_FROM_)) {
1172 if (menc != MIMEE_B64)
1173 menc = MIMEE_QP;
1174 goto jstepi;
1176 if (mtc & _MT_C_HIGHBIT) {
1177 jstepi:
1178 if (mtc & (_MT_C_NCTT | _MT_C_ISTXT))
1179 *do_iconv = ((mtc & _MT_C_HIGHBIT) != 0);
1180 } else
1181 j7bit:
1182 menc = MIMEE_7B;
1183 if (mtc & _MT_C_NCTT)
1184 *contenttype = "text/plain";
1186 /* Not an attachment with specified charset? */
1187 jcharset:
1188 if (*charset == NULL) /* TODO MIME/send: iter active? iter! else */
1189 *charset = (mtc & _MT_C_HIGHBIT) ? charset_iter_or_fallback()
1190 : ok_vlook(charset_7bit);
1191 jleave:
1192 /* TODO mime_type_file_classify() shouldn't return conversion */
1193 if (rfc822) {
1194 if (mtc & _MT_C_FROM_1STLINE) {
1195 n_err(_("Pre-v15 %s cannot handle message/rfc822 that "
1196 "indeed is a RFC 4155 MBOX!\n"
1197 " Forcing a content-type of application/mbox!\n"),
1198 n_uagent);
1199 *contenttype = "application/mbox";
1200 goto jnorfc822;
1202 c = (menc == MIMEE_7B ? CONV_7BIT
1203 : (menc == MIMEE_8B ? CONV_8BIT
1204 /* May have only 7-bit, 8-bit and binary. Try to avoid latter */
1205 : ((mtc & _MT_C_HASNUL) ? CONV_NONE
1206 : ((mtc & _MT_C_HIGHBIT) ? CONV_8BIT : CONV_7BIT))));
1207 } else
1208 jnorfc822:
1209 c = (menc == MIMEE_7B ? CONV_7BIT
1210 : (menc == MIMEE_8B ? CONV_8BIT
1211 : (menc == MIMEE_QP ? CONV_TOQP : CONV_TOB64)));
1212 NYD_LEAVE;
1213 return c;
1216 FL enum mimecontent
1217 n_mimetype_classify_part(struct mimepart *mpp, bool_t for_user_context){
1218 /* TODO n_mimetype_classify_part() <-> m_mime_classifier_ with life cycle */
1219 struct mtlookup mtl;
1220 enum mimecontent mc;
1221 char const *ct;
1222 union {char const *cp; ui32_t f;} mce;
1223 bool_t is_os;
1224 NYD_ENTER;
1226 mc = MIME_UNKNOWN;
1227 if ((ct = mpp->m_ct_type_plain) == NULL) /* TODO may not */
1228 ct = n_empty;
1230 if((mce.cp = ok_vlook(mime_counter_evidence)) != NULL && *mce.cp != '\0'){
1231 if((n_idec_ui32_cp(&mce.f, mce.cp, 0, NULL
1232 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1233 ) != n_IDEC_STATE_CONSUMED){
1234 n_err(_("Invalid *mime-counter-evidence* value content\n"));
1235 is_os = FAL0;
1236 }else{
1237 mce.f |= MIMECE_SET;
1238 is_os = !asccasecmp(ct, "application/octet-stream");
1240 if(mpp->m_filename != NULL && (is_os || (mce.f & MIMECE_ALL_OVWR))){
1241 if(_mt_by_filename(&mtl, mpp->m_filename, TRU1) == NULL){
1242 if(is_os)
1243 goto jos_content_check;
1244 }else if(is_os || asccasecmp(ct, mtl.mtl_result)){
1245 if(mce.f & MIMECE_ALL_OVWR)
1246 mpp->m_ct_type_plain = ct = mtl.mtl_result;
1247 if(mce.f & (MIMECE_BIN_OVWR | MIMECE_ALL_OVWR))
1248 mpp->m_ct_type_usr_ovwr = ct = mtl.mtl_result;
1252 }else
1253 is_os = FAL0;
1255 if(*ct == '\0' || strchr(ct, '/') == NULL) /* For compat with non-MIME */
1256 mc = MIME_TEXT;
1257 else if(is_asccaseprefix("text/", ct)){
1258 ct += sizeof("text/") -1;
1259 if(!asccasecmp(ct, "plain"))
1260 mc = MIME_TEXT_PLAIN;
1261 else if(!asccasecmp(ct, "html"))
1262 mc = MIME_TEXT_HTML;
1263 else
1264 mc = MIME_TEXT;
1265 }else if(is_asccaseprefix("message/", ct)){
1266 ct += sizeof("message/") -1;
1267 if(!asccasecmp(ct, "rfc822"))
1268 mc = MIME_822;
1269 else
1270 mc = MIME_MESSAGE;
1271 }else if(is_asccaseprefix("multipart/", ct)){
1272 struct multi_types{
1273 char mt_name[12];
1274 enum mimecontent mt_mc;
1275 } const mta[] = {
1276 {"alternative\0", MIME_ALTERNATIVE},
1277 {"related", MIME_RELATED},
1278 {"digest", MIME_DIGEST},
1279 {"signed", MIME_SIGNED},
1280 {"encrypted", MIME_ENCRYPTED}
1281 }, *mtap;
1283 for(ct += sizeof("multipart/") -1, mtap = mta;;)
1284 if(!asccasecmp(ct, mtap->mt_name)){
1285 mc = mtap->mt_mc;
1286 break;
1287 }else if(++mtap == mta + n_NELEM(mta)){
1288 mc = MIME_MULTI;
1289 break;
1291 }else if(is_asccaseprefix("application/", ct)){
1292 if(is_os)
1293 goto jos_content_check;
1294 ct += sizeof("application/") -1;
1295 if(!asccasecmp(ct, "pkcs7-mime") || !asccasecmp(ct, "x-pkcs7-mime"))
1296 mc = MIME_PKCS7;
1298 jleave:
1299 NYD_LEAVE;
1300 return mc;
1302 jos_content_check:
1303 if((mce.f & MIMECE_BIN_PARSE) && mpp->m_mime_enc != MIMEE_BIN &&
1304 mpp->m_charset != NULL)
1305 mc = _mt_classify_os_part(mce.f, mpp, for_user_context);
1306 goto jleave;
1309 FL enum mime_handler_flags
1310 n_mimetype_handler(struct mime_handler *mhp, struct mimepart const *mpp,
1311 enum sendaction action)
1313 #define __S "pipe-"
1314 #define __L (sizeof(__S) -1)
1315 struct mtlookup mtl;
1316 char *buf, *cp;
1317 enum mime_handler_flags rv, xrv;
1318 char const *es, *cs, *ccp;
1319 size_t el, cl, l;
1320 NYD_ENTER;
1322 memset(mhp, 0, sizeof *mhp);
1323 buf = NULL;
1325 rv = MIME_HDL_NULL;
1326 if (action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1327 rv |= MIME_HDL_ISQUOTE;
1328 else if (action != SEND_TODISP && action != SEND_TODISP_ALL &&
1329 action != SEND_TODISP_PARTS)
1330 goto jleave;
1332 el = ((es = mpp->m_filename) != NULL && (es = strrchr(es, '.')) != NULL &&
1333 *++es != '\0') ? strlen(es) : 0;
1334 cl = ((cs = mpp->m_ct_type_usr_ovwr) != NULL ||
1335 (cs = mpp->m_ct_type_plain) != NULL) ? strlen(cs) : 0;
1336 if ((l = n_MAX(el, cl)) == 0) {
1337 /* TODO this should be done during parse time! */
1338 goto jleave;
1341 /* We don't pass the flags around, so ensure carrier is up-to-date */
1342 mhp->mh_flags = rv;
1344 buf = n_lofi_alloc(__L + l +1);
1345 memcpy(buf, __S, __L);
1347 /* File-extension handlers take precedence.
1348 * Yes, we really "fail" here for file extensions which clash MIME types */
1349 if (el > 0) {
1350 memcpy(buf + __L, es, el +1);
1351 for (cp = buf + __L; *cp != '\0'; ++cp)
1352 *cp = lowerconv(*cp);
1354 if ((mhp->mh_shell_cmd = ccp = n_var_vlook(buf, FAL0)) != NULL) {
1355 rv = a_mt_pipe_check(mhp);
1356 goto jleave;
1360 /* Then MIME Content-Type:, if any */
1361 if (cl == 0)
1362 goto jleave;
1364 memcpy(buf + __L, cs, cl +1);
1365 for (cp = buf + __L; *cp != '\0'; ++cp)
1366 *cp = lowerconv(*cp);
1368 if ((mhp->mh_shell_cmd = n_var_vlook(buf, FAL0)) != NULL) {
1369 rv = a_mt_pipe_check(mhp);
1370 goto jleave;
1373 if (_mt_by_mtname(&mtl, cs) != NULL)
1374 switch (mtl.mtl_node->mt_flags & a_MT__TM_MARKMASK) {
1375 #ifndef HAVE_FILTER_HTML_TAGSOUP
1376 case a_MT_TM_SOUP_H:
1377 break;
1378 #endif
1379 case a_MT_TM_SOUP_h:
1380 #ifdef HAVE_FILTER_HTML_TAGSOUP
1381 case a_MT_TM_SOUP_H:
1382 mhp->mh_ptf = &htmlflt_process_main;
1383 mhp->mh_msg.l = strlen(mhp->mh_msg.s =
1384 n_UNCONST(_("Built-in HTML tagsoup filter")));
1385 rv ^= MIME_HDL_NULL | MIME_HDL_PTF;
1386 goto jleave;
1387 #endif
1388 /* FALLTHRU */
1389 case a_MT_TM_PLAIN:
1390 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(_("Plain text")));
1391 rv ^= MIME_HDL_NULL | MIME_HDL_TEXT;
1392 goto jleave;
1393 case a_MT_TM_QUIET:
1394 mhp->mh_msg.l = 0;
1395 mhp->mh_msg.s = n_UNCONST(n_empty);
1396 goto jleave;
1397 default:
1398 break;
1401 jleave:
1402 if(buf != NULL)
1403 n_lofi_free(buf);
1405 xrv = rv;
1406 if((rv &= MIME_HDL_TYPE_MASK) == MIME_HDL_NULL){
1407 if(mhp->mh_msg.s == NULL)
1408 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1409 A_("[-- No MIME handler installed, or not applicable --]\n")));
1410 }else if(rv == MIME_HDL_CMD && !(xrv & MIME_HDL_COPIOUSOUTPUT) &&
1411 action != SEND_TODISP_PARTS){
1412 mhp->mh_msg.l = strlen(mhp->mh_msg.s = n_UNCONST(
1413 _("[-- Use the command `mimeview' to display this --]\n")));
1414 xrv &= ~MIME_HDL_TYPE_MASK;
1415 xrv |= (rv = MIME_HDL_MSG);
1417 mhp->mh_flags = xrv;
1419 NYD_LEAVE;
1420 return rv;
1421 #undef __L
1422 #undef __S
1425 /* s-it-mode */