1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?mimetype' and other mime.types(5) related facilities.
3 *@ "Keep in sync with" ./mime.types.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
7 * Permission to use, copy, modify, and/or distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 #define n_FILE mime_types
22 #ifndef HAVE_AMALGAMATION
36 __MT_TMAX
= _MT_OTHER
,
39 _MT_LOADED
= 1<< 8, /* Not struct mtbltin */
40 _MT_USR
= 1<< 9, /* MIME_TYPES_USR */
41 _MT_SYS
= 1<<10, /* MIME_TYPES_SYS */
43 _MT_PLAIN
= 1<<16, /* Without pipe handler display as text */
44 _MT_SOUP_h
= 2<<16, /* Ditto, but HTML tagsoup parser if possible */
45 _MT_SOUP_H
= 3<<16, /* HTML tagsoup parser, else NOT plain text */
46 __MT_MARKMASK
= _MT_SOUP_H
49 enum mime_type_class
{
50 _MT_C_CLEAN
= 0, /* Plain RFC 5322 message */
51 _MT_C_NCTT
= 1<<0, /* *contenttype == NULL */
52 _MT_C_ISTXT
= 1<<1, /* *contenttype =~ text\/ */
53 _MT_C_ISTXTCOK
= 1<<2, /* _ISTXT + *mime-allow-text-controls* */
54 _MT_C_HIGHBIT
= 1<<3, /* Not 7bit clean */
55 _MT_C_LONGLINES
= 1<<4, /* MIME_LINELEN_LIMIT exceed. */
56 _MT_C_CTRLCHAR
= 1<<5, /* Control characters seen */
57 _MT_C_HASNUL
= 1<<6, /* Contains \0 characters */
58 _MT_C_NOTERMNL
= 1<<7, /* Lacks a final newline */
59 _MT_C_FROM_
= 1<<8, /* ^From_ seen */
60 _MT_C_SUGGEST_DONE
= 1<<16 /* Inspector suggests to stop further parse */
70 struct mtnode
*mt_next
;
72 ui32_t mt_mtlen
; /* Length of MIME type string, rest thereafter */
73 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
74 * that could already store character data here */
81 struct mtnode
const *mtl_node
;
82 char *mtl_result
; /* If requested, salloc()ed MIME type */
91 enum mime_type_class mtca_mtc
;
94 static struct mtbltin
const _mt_bltin
[] = {
95 #include "mime_types.h"
98 static char const _mt_typnames
[][16] = {
99 "application/", "audio/", "image/",
100 "message/", "multipart/", "text/",
103 CTA(_MT_APPLICATION
== 0 && _MT_AUDIO
== 1 && _MT_IMAGE
== 2 &&
104 _MT_MESSAGE
== 3 && _MT_MULTIPART
== 4 && _MT_TEXT
== 5 &&
108 static bool_t _mt_is_init
;
109 static struct mtnode
*_mt_list
;
111 /* Initialize MIME type list in order */
112 static void _mt_init(void);
113 static bool_t
__mt_load_file(ui32_t orflags
,
114 char const *file
, char **line
, size_t *linesize
);
116 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
118 static struct mtnode
* _mt_create(bool_t cmdcalled
, ui32_t orflags
,
119 char const *line
, size_t len
);
121 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
122 * if with_result >mtl_result will be created upon success for the former */
123 static struct mtlookup
* _mt_by_filename(struct mtlookup
*mtlp
,
124 char const *name
, bool_t with_result
);
125 static struct mtlookup
* _mt_by_mtname(struct mtlookup
*mtlp
,
128 /* In-depth inspection of raw content: call _round() repeatedly, last time with
129 * a 0 length buffer, finally check .mtca_mtc for result.
130 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
131 * as the resulting classification is unambiguous */
132 SINLINE
struct mt_class_arg
* _mt_classify_init(struct mt_class_arg
*mtcap
,
133 enum mime_type_class initval
);
134 static enum mime_type_class
_mt_classify_round(struct mt_class_arg
*mtcap
);
136 /* We need an in-depth inspection of an application/octet-stream part */
137 static enum mimecontent
_mt_classify_os_part(ui32_t mce
, struct mimepart
*mpp
);
139 /* Check wether a *pipe-XY* handler is applicable, and adjust flags according
140 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
141 * isn't changed if mhp doesn't apply */
142 static enum mime_handler_flags
_mt_pipe_check(struct mime_handler
*mhp
);
148 char c
, *line
; /* TODO line pool (below) */
151 char const *srcs_arr
[10], *ccp
, **srcs
;
157 /* Always load our builtins */
158 for (tail
= NULL
, i
= 0; i
< NELEM(_mt_bltin
); ++i
) {
159 struct mtbltin
const *mtbp
= _mt_bltin
+ i
;
160 struct mtnode
*mtnp
= smalloc(sizeof *mtnp
);
163 tail
->mt_next
= mtnp
;
167 mtnp
->mt_next
= NULL
;
168 mtnp
->mt_flags
= mtbp
->mtb_flags
;
169 mtnp
->mt_mtlen
= mtbp
->mtb_mtlen
;
170 mtnp
->mt_line
= mtbp
->mtb_line
;
173 /* Decide which files sources have to be loaded */
174 if ((ccp
= ok_vlook(mimetypes_load_control
)) == NULL
)
176 else if (*ccp
== '\0')
180 srcs
[-1] = srcs
[-2] = NULL
;
182 if (strchr(ccp
, '=') != NULL
) {
185 while ((ccp
= n_strsep(&line
, ',', TRU1
)) != NULL
) {
186 switch ((c
= *ccp
)) {
188 srcs_arr
[1] = MIME_TYPES_SYS
;
192 srcs_arr
[0] = MIME_TYPES_USR
;
198 if (*++ccp
== '=' && *++ccp
!= '\0') {
199 if (PTR2SIZE(srcs
- srcs_arr
) < NELEM(srcs_arr
))
202 n_err(_("*mimetypes-load-control*: too many sources, "
203 "skipping \"%s\"\n"), ccp
);
211 } else for (i
= 0; (c
= ccp
[i
]) != '\0'; ++i
)
213 case 'S': case 's': srcs_arr
[1] = MIME_TYPES_SYS
; break;
214 case 'U': case 'u': srcs_arr
[0] = MIME_TYPES_USR
; break;
217 n_err(_("*mimetypes-load-control*: unsupported content: \"%s\"\n"),
222 /* Load all file-based sources in the desired order */
225 for (j
= 0, i
= (ui32_t
)PTR2SIZE(srcs
- srcs_arr
), srcs
= srcs_arr
;
226 i
> 0; ++j
, ++srcs
, --i
)
229 else if (!__mt_load_file((j
== 0 ? _MT_USR
: (j
== 1 ? _MT_SYS
: 0)),
230 *srcs
, &line
, &linesize
)) {
231 if ((options
& OPT_D_V
) || j
> 1)
232 n_err(_("*mimetypes-load-control*: can't open or load \"%s\"\n"),
243 __mt_load_file(ui32_t orflags
, char const *file
, char **line
, size_t *linesize
)
247 struct mtnode
*head
, *tail
, *mtnp
;
251 if ((cp
= file_expand(file
)) == NULL
|| (fp
= Fopen(cp
, "r")) == NULL
) {
256 for (head
= tail
= NULL
; fgetline(line
, linesize
, NULL
, &len
, fp
, 0) != 0;)
257 if ((mtnp
= _mt_create(FAL0
, orflags
, *line
, len
)) != NULL
) {
261 tail
->mt_next
= mtnp
;
265 tail
->mt_next
= _mt_list
;
275 static struct mtnode
*
276 _mt_create(bool_t cmdcalled
, ui32_t orflags
, char const *line
, size_t len
)
278 struct mtnode
*mtnp
= NULL
;
279 char const *typ
, *subtyp
;
283 /* Drop anything after a comment first */
284 if ((typ
= memchr(line
, '#', len
)) != NULL
)
285 len
= PTR2SIZE(typ
- line
);
287 /* Then trim any trailing whitespace from line (including NL/CR) */
288 while (len
> 0 && spacechar(line
[len
- 1]))
291 /* Isolate MIME type, trim any whitespace from it */
292 while (len
> 0 && blankchar(*line
))
296 /* (But wait - is there a type marker?) */
297 if (!(orflags
& (_MT_USR
| _MT_SYS
)) && *typ
== '@') {
301 orflags
|= _MT_PLAIN
;
305 } else if (len
> 4 && typ
[2] == '@' && typ
[3] == ' ') {
307 case 't': orflags
|= _MT_PLAIN
; goto jexttypmar
;
308 case 'h': orflags
|= _MT_SOUP_h
; goto jexttypmar
;
309 case 'H': orflags
|= _MT_SOUP_H
;
322 while (len
> 0 && !blankchar(*line
))
324 /* Ignore empty lines and even incomplete specifications (only MIME type)
325 * because this is quite common in mime.types(5) files */
326 if (len
== 0 || (tlen
= PTR2SIZE(line
- typ
)) == 0) {
328 n_err(_("Empty MIME type or no extensions given: \"%s\"\n"),
329 (len
== 0 ? _("(no value)") : line
));
333 if ((subtyp
= memchr(typ
, '/', tlen
)) == NULL
) {
335 if (cmdcalled
|| (options
& OPT_D_V
))
336 n_err(_("%s MIME type: \"%s\"\n"),
337 (cmdcalled
? _("Invalid") : _("mime.types(5): invalid")), typ
);
342 /* Map to mime_type */
343 tlen
= PTR2SIZE(subtyp
- typ
);
344 for (i
= __MT_TMIN
;;) {
345 if (!ascncasecmp(_mt_typnames
[i
], typ
, tlen
)) {
347 tlen
= PTR2SIZE(line
- subtyp
);
351 if (++i
== __MT_TMAX
) {
352 orflags
|= _MT_OTHER
;
353 tlen
= PTR2SIZE(line
- typ
);
358 /* Strip leading whitespace from the list of extensions;
359 * trailing WS has already been trimmed away above.
360 * Be silent on slots which define a mimetype without any value */
361 while (len
> 0 && blankchar(*line
))
367 mtnp
= smalloc(sizeof(*mtnp
) + tlen
+ len
+1);
368 mtnp
->mt_next
= NULL
;
369 mtnp
->mt_flags
= (orflags
|= _MT_LOADED
);
370 mtnp
->mt_mtlen
= (ui32_t
)tlen
;
371 { char *l
= (char*)(mtnp
+ 1);
373 memcpy(l
, typ
, tlen
);
374 memcpy(l
+ tlen
, line
, len
);
384 static struct mtlookup
*
385 _mt_by_filename(struct mtlookup
*mtlp
, char const *name
, bool_t with_result
)
389 char const *ext
, *cp
;
392 memset(mtlp
, 0, sizeof *mtlp
);
394 if ((mtlp
->mtl_nlen
= nlen
= strlen(mtlp
->mtl_name
= name
)) == 0 ||
395 memchr(name
, '.', nlen
) == NULL
)
401 /* ..all the MIME types */
402 for (mtnp
= _mt_list
; mtnp
!= NULL
; mtnp
= mtnp
->mt_next
)
403 for (ext
= mtnp
->mt_line
+ mtnp
->mt_mtlen
;; ext
= cp
) {
405 while (whitechar(*cp
))
408 while (!whitechar(*cp
) && *cp
!= '\0')
411 if ((i
= PTR2SIZE(cp
- ext
)) == 0)
413 /* Don't allow neither of ".txt" or "txt" to match "txt" */
414 else if (i
+ 1 >= nlen
|| name
[(j
= nlen
- i
) - 1] != '.' ||
415 ascncasecmp(name
+ j
, ext
, i
))
419 mtlp
->mtl_node
= mtnp
;
424 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
428 name
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
432 mtlp
->mtl_result
= salloc(i
+ j
+1);
434 memcpy(mtlp
->mtl_result
, name
, j
);
435 memcpy(mtlp
->mtl_result
+ j
, mtnp
->mt_line
, i
);
436 mtlp
->mtl_result
[j
+= i
] = '\0';
446 static struct mtlookup
*
447 _mt_by_mtname(struct mtlookup
*mtlp
, char const *mtname
)
454 memset(mtlp
, 0, sizeof *mtlp
);
456 if ((mtlp
->mtl_nlen
= nlen
= strlen(mtlp
->mtl_name
= mtname
)) == 0)
462 /* ..all the MIME types */
463 for (mtnp
= _mt_list
; mtnp
!= NULL
; mtnp
= mtnp
->mt_next
) {
464 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
468 cp
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
473 if (i
+ j
== mtlp
->mtl_nlen
) {
474 char *xmt
= ac_alloc(i
+ j
+1);
477 memcpy(xmt
+ j
, mtnp
->mt_line
, i
);
479 i
= asccasecmp(mtname
, xmt
);
484 mtlp
->mtl_node
= mtnp
;
496 SINLINE
struct mt_class_arg
*
497 _mt_classify_init(struct mt_class_arg
* mtcap
, enum mime_type_class initval
)
500 memset(mtcap
, 0, sizeof *mtcap
);
501 mtcap
->mtca_lastc
= mtcap
->mtca_c
= EOF
;
502 mtcap
->mtca_mtc
= initval
;
507 static enum mime_type_class
508 _mt_classify_round(struct mt_class_arg
*mtcap
)
510 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
511 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
512 * TODO and report that state to the outer world */
514 #define F_SIZEOF (sizeof(F_) -1)
515 char f_buf
[F_SIZEOF
], *f_p
= f_buf
;
520 enum mime_type_class mtc
;
523 buf
= mtcap
->mtca_buf
;
524 blen
= mtcap
->mtca_len
;
525 curlen
= mtcap
->mtca_curlen
;
527 lastc
= mtcap
->mtca_lastc
;
528 mtc
= mtcap
->mtca_mtc
;
533 /* Real EOF, or only current buffer end? */
534 if (mtcap
->mtca_len
== 0)
544 if (!(mtc
& _MT_C_ISTXTCOK
)) {
545 mtc
|= _MT_C_SUGGEST_DONE
;
550 if (c
== '\n' || c
== EOF
) {
551 if (curlen
>= MIME_LINELEN_LIMIT
)
552 mtc
|= _MT_C_LONGLINES
;
560 /* A bit hairy is handling of \r=\x0D=CR.
562 * Control characters other than TAB, or CR and LF as parts of CRLF
563 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
564 * we cannot peek the next character. Thus right here, inspect the last
565 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
566 /*else*/ if (lastc
== '\r')
567 mtc
|= _MT_C_CTRLCHAR
;
569 /* Control character? XXX this is all ASCII here */
570 if (c
< 0x20 || c
== 0x7F) {
571 /* RFC 2045, 6.7, as above ... */
572 if (c
!= '\t' && c
!= '\r')
573 mtc
|= _MT_C_CTRLCHAR
;
574 /* If there is a escape sequence in backslash notation defined for
575 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
576 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
577 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
579 if ((c
>= '\x07' && c
<= '\x0D') || c
== '\x1B')
581 mtc
|= _MT_C_HASNUL
; /* Force base64 */
582 if (!(mtc
& _MT_C_ISTXTCOK
)) {
583 mtc
|= _MT_C_SUGGEST_DONE
;
586 } else if ((ui8_t
)c
& 0x80) {
587 mtc
|= _MT_C_HIGHBIT
;
588 /* TODO count chars with HIGHBIT? libmagic?
589 * TODO try encode part - base64 if bails? */
590 if (!(mtc
& (_MT_C_NCTT
| _MT_C_ISTXT
))) { /* TODO _NCTT?? */
591 mtc
|= _MT_C_HASNUL
/* Force base64 */ | _MT_C_SUGGEST_DONE
;
594 } else if (!(mtc
& _MT_C_FROM_
) && UICMP(z
, curlen
, <, F_SIZEOF
)) {
596 if (UICMP(z
, curlen
, ==, F_SIZEOF
- 1) &&
597 PTR2SIZE(f_p
- f_buf
) == F_SIZEOF
&&
598 !memcmp(f_buf
, F_
, F_SIZEOF
))
602 if (c
== EOF
&& lastc
!= '\n')
603 mtc
|= _MT_C_NOTERMNL
;
605 mtcap
->mtca_curlen
= curlen
;
606 mtcap
->mtca_lastc
= lastc
;
608 mtcap
->mtca_mtc
= mtc
;
615 static enum mimecontent
616 _mt_classify_os_part(ui32_t mce
, struct mimepart
*mpp
)
618 struct str in
= {NULL
, 0}, rest
= {NULL
, 0}, dec
= {NULL
, 0};
619 struct mt_class_arg mtca
;
620 enum mime_type_class mtc
;
628 assert(mpp
->m_mime_enc
!= MIMEE_BIN
);
633 /* TODO v15-compat Note we actually bypass our usual file handling by
634 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
635 * TODO all of this, and until then doing it like this is the only option
636 * TODO to integrate nicely into whoever calls us */
637 start_off
= ftell(mb
.mb_itf
);
638 if ((ibuf
= setinput(&mb
, (struct message
*)mpp
, NEED_BODY
)) == NULL
) {
640 fseek(mb
.mb_itf
, start_off
, SEEK_SET
);
645 /* Skip part headers */
646 for (lc
= '\0'; cnt
> 0; lc
= c
, --cnt
)
647 if ((c
= getc(ibuf
)) == EOF
|| (c
== '\n' && lc
== '\n'))
649 if (cnt
== 0 || ferror(ibuf
))
652 /* So now let's inspect the part content, decoding content-transfer-encoding
653 * along the way TODO this should simply be "mime_factory_create(MPP)"! */
654 _mt_classify_init(&mtca
, _MT_C_ISTXT
);
659 c
= (--cnt
== 0) ? EOF
: getc(ibuf
);
660 if ((dobuf
= (c
== '\n'))) {
661 /* Ignore empty lines */
664 } else if ((dobuf
= (c
== EOF
))) {
665 if (lsz
== 0 && rest
.l
== 0)
670 in
.s
= srealloc(in
.s
, lsz
+= LINESIZE
);
672 in
.s
[in
.l
++] = (char)c
;
677 switch (mpp
->m_mime_enc
) {
679 if (b64_decode(&dec
, &in
, &rest
) == STOP
) {
680 mtca
.mtca_mtc
= _MT_C_HASNUL
;
681 goto jstopit
; /* break;break; */
686 if (qp_decode(&dec
, &in
, &rest
) == STOP
) {
687 mtca
.mtca_mtc
= _MT_C_HASNUL
;
688 goto jstopit
; /* break;break; */
690 if (dec
.l
== 0 && c
!= EOF
) {
696 /* Temporarily switch those two buffers.. */
703 mtca
.mtca_buf
= dec
.s
;
704 mtca
.mtca_len
= (ssize_t
)dec
.l
;
705 if ((mtc
= _mt_classify_round(&mtca
)) & _MT_C_SUGGEST_DONE
) {
712 /* ..and restore switched */
731 fseek(mb
.mb_itf
, start_off
, SEEK_SET
);
733 if (!(mtc
& (_MT_C_HASNUL
| _MT_C_CTRLCHAR
))) {
734 mc
= MIME_TEXT_PLAIN
;
735 if (mce
& MIMECE_ALL_OVWR
)
736 mpp
->m_ct_type_plain
= "text/plain";
737 if (mce
& (MIMECE_BIN_OVWR
| MIMECE_ALL_OVWR
))
738 mpp
->m_ct_type_usr_ovwr
= "text/plain";
745 static enum mime_handler_flags
746 _mt_pipe_check(struct mime_handler
*mhp
)
748 enum mime_handler_flags rv_orig
, rv
;
752 rv_orig
= rv
= mhp
->mh_flags
;
754 /* Do we have any handler for this part? */
755 if (*(cp
= mhp
->mh_shell_cmd
) == '\0')
757 else if (*cp
++ != '@') {
760 } else if (*cp
== '\0') {
767 case '*': rv
|= MIME_HDL_ALWAYS
; ++cp
; goto jnextc
;
768 case '#': rv
|= MIME_HDL_NOQUOTE
; ++cp
; goto jnextc
;
769 case '&': rv
|= MIME_HDL_ASYNC
; ++cp
; goto jnextc
;
770 case '!': rv
|= MIME_HDL_NEEDSTERM
; ++cp
; goto jnextc
;
772 if (rv
& MIME_HDL_TMPF
)
773 rv
|= MIME_HDL_TMPF_UNLINK
;
778 rv
|= MIME_HDL_TMPF_FILL
;
787 mhp
->mh_shell_cmd
= cp
;
790 if (rv
& MIME_HDL_TMPF_FILL
)
794 if (rv
& MIME_HDL_ISQUOTE
) {
795 if (rv
& MIME_HDL_NOQUOTE
)
798 /* Cannot fetch data back from asynchronous process */
799 if (rv
& MIME_HDL_ASYNC
)
802 /* TODO Can't use a "needsterminal" program for quoting */
803 if (rv
& MIME_HDL_NEEDSTERM
)
807 if (rv
& MIME_HDL_NEEDSTERM
) {
808 if (rv
& MIME_HDL_ASYNC
) {
809 n_err(_("MIME type handlers: can't use \"needsterminal\" and "
810 "\"x-nail-async\" together\n"));
814 /* needsterminal needs a terminal */
815 if (!(options
& OPT_INTERACTIVE
))
819 if (!(rv
& MIME_HDL_ALWAYS
) && !(pstate
& PS_MSGLIST_DIRECT
)) {
820 /* Viewing multiple messages in one go, don't block system */
821 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
= UNCONST(
822 _("[-- Directly address message only for display --]\n")));
851 if (_mt_list
== NULL
) {
852 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
856 if ((fp
= Ftmp(NULL
, "mimelist", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) ==
858 n_perr(_("tmpfile"), 0);
863 for (l
= 0, mtnp
= _mt_list
; mtnp
!= NULL
; ++l
, mtnp
= mtnp
->mt_next
) {
864 char const *tmark
, *typ
;
866 switch (mtnp
->mt_flags
& __MT_MARKMASK
) {
867 case _MT_PLAIN
: tmark
= "/t"; break;
868 case _MT_SOUP_h
: tmark
= "/h"; break;
869 case _MT_SOUP_H
: tmark
= "/H"; break;
870 default: tmark
= " "; break;
872 typ
= ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
)
873 ? "" : _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
875 fprintf(fp
, "%c%s %s%.*s <%s>\n",
876 (mtnp
->mt_flags
& _MT_USR
? 'U'
877 : (mtnp
->mt_flags
& _MT_SYS
? 'S'
878 : (mtnp
->mt_flags
& _MT_LOADED
? 'F' : 'B'))),
879 tmark
, typ
, (int)mtnp
->mt_mtlen
, mtnp
->mt_line
,
880 mtnp
->mt_line
+ mtnp
->mt_mtlen
);
883 page_or_print(fp
, l
);
886 for (; *argv
!= NULL
; ++argv
) {
887 mtnp
= _mt_create(TRU1
, _MT_LOADED
, *argv
, strlen(*argv
));
889 mtnp
->mt_next
= _mt_list
;
897 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
901 c_unmimetype(void *v
)
904 struct mtnode
*lnp
, *mtnp
;
908 /* Need to load that first as necessary */
912 for (; *argv
!= NULL
; ++argv
) {
913 if (!asccasecmp(*argv
, "reset")) {
918 if (argv
[0][0] == '*' && argv
[0][1] == '\0') {
920 while ((mtnp
= _mt_list
) != NULL
) {
921 _mt_list
= mtnp
->mt_next
;
927 for (match
= FAL0
, lnp
= NULL
, mtnp
= _mt_list
; mtnp
!= NULL
;) {
932 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
936 typ
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
940 val
= ac_alloc(i
+ mtnp
->mt_mtlen
+1);
942 memcpy(val
+ i
, mtnp
->mt_line
, mtnp
->mt_mtlen
);
943 val
[i
+= mtnp
->mt_mtlen
] = '\0';
944 i
= asccasecmp(val
, *argv
);
948 struct mtnode
*nnp
= mtnp
->mt_next
;
957 lnp
= mtnp
, mtnp
= mtnp
->mt_next
;
960 if (!(pstate
& PS_ROBOT
) || (options
& OPT_D_V
))
961 n_err(_("No such MIME type: \"%s\"\n"), *argv
);
966 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
970 mime_type_classify_filename(char const *name
)
975 _mt_by_filename(&mtl
, name
, TRU1
);
977 return mtl
.mtl_result
;
981 mime_type_classify_file(FILE *fp
, char const **contenttype
,
982 char const **charset
, int *do_iconv
)
984 /* TODO classify once only PLEASE PLEASE PLEASE */
985 enum mime_type_class mtc
;
990 assert(ftell(fp
) == 0x0l
);
994 if (*contenttype
== NULL
)
996 else if (!ascncasecmp(*contenttype
, "text/", 5))
997 mtc
= ok_blook(mime_allow_text_controls
)
998 ? _MT_C_ISTXT
| _MT_C_ISTXTCOK
: _MT_C_ISTXT
;
1002 menc
= mime_enc_target();
1004 if ((fpsz
= fsize(fp
)) == 0)
1007 char buf
[BUFFER_SIZE
];
1008 struct mt_class_arg mtca
;
1010 _mt_classify_init(&mtca
, mtc
);
1012 mtca
.mtca_len
= fread(buf
, sizeof(buf
[0]), NELEM(buf
), fp
);
1013 mtca
.mtca_buf
= buf
;
1014 if ((mtc
= _mt_classify_round(&mtca
)) & _MT_C_SUGGEST_DONE
)
1016 if (mtca
.mtca_len
== 0)
1019 /* TODO ferror(fp) ! */
1023 if (mtc
& _MT_C_HASNUL
) {
1025 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1026 * on request; else enforce what file(1)/libmagic(3) would suggest */
1027 if (mtc
& _MT_C_ISTXTCOK
)
1029 if (mtc
& (_MT_C_NCTT
| _MT_C_ISTXT
))
1030 *contenttype
= "application/octet-stream";
1031 if (*charset
== NULL
)
1032 *charset
= "binary";
1037 (_MT_C_LONGLINES
| _MT_C_CTRLCHAR
| _MT_C_NOTERMNL
| _MT_C_FROM_
)) {
1038 if (menc
!= MIMEE_B64
)
1042 if (mtc
& _MT_C_HIGHBIT
) {
1044 if (mtc
& (_MT_C_NCTT
| _MT_C_ISTXT
))
1045 *do_iconv
= ((mtc
& _MT_C_HIGHBIT
) != 0);
1049 if (mtc
& _MT_C_NCTT
)
1050 *contenttype
= "text/plain";
1052 /* Not an attachment with specified charset? */
1054 if (*charset
== NULL
) /* TODO MIME/send: iter active? iter! else */
1055 *charset
= (mtc
& _MT_C_HIGHBIT
) ? charset_iter_or_fallback()
1056 : charset_get_7bit();
1059 /* TODO mime_type_file_classify() shouldn't return conversion */
1060 return (menc
== MIMEE_7B
? CONV_7BIT
:
1061 (menc
== MIMEE_8B
? CONV_8BIT
:
1062 (menc
== MIMEE_QP
? CONV_TOQP
: CONV_TOB64
)));
1066 mime_type_classify_part(struct mimepart
*mpp
) /* FIXME charset=binary ??? */
1068 struct mtlookup mtl
;
1069 enum mimecontent mc
;
1071 union {char const *cp
; ui32_t f
;} mce
;
1076 if ((ct
= mpp
->m_ct_type_plain
) == NULL
) /* TODO may not */
1079 if ((mce
.cp
= ok_vlook(mime_counter_evidence
)) != NULL
) {
1083 l
= strtol(mce
.cp
, &eptr
, 0); /* XXX strtol */
1084 if (*mce
.cp
== '\0')
1086 else if (*eptr
!= '\0' || l
< 0 || (ui64_t
)(ul_i
)l
>= UI32_MAX
) {
1087 n_err(_("Can't parse *mime-counter-evidence* value \"%s\"\n"), mce
.cp
);
1090 mce
.f
= (ui32_t
)l
| MIMECE_SET
;
1091 is_os
= !asccasecmp(ct
, "application/octet-stream");
1093 if (mpp
->m_filename
!= NULL
&& (is_os
|| (mce
.f
& MIMECE_ALL_OVWR
))) {
1094 if (_mt_by_filename(&mtl
, mpp
->m_filename
, TRU1
) == NULL
) {
1096 goto jos_content_check
;
1097 } else if (is_os
|| asccasecmp(ct
, mtl
.mtl_result
)) {
1098 if (mce
.f
& MIMECE_ALL_OVWR
)
1099 mpp
->m_ct_type_plain
= ct
= mtl
.mtl_result
;
1100 if (mce
.f
& (MIMECE_BIN_OVWR
| MIMECE_ALL_OVWR
))
1101 mpp
->m_ct_type_usr_ovwr
= ct
= mtl
.mtl_result
;
1108 if (strchr(ct
, '/') == NULL
) /* For compatibility with non-MIME */
1110 else if (is_asccaseprefix(ct
, "text/")) {
1111 ct
+= sizeof("text/") -1;
1112 if (!asccasecmp(ct
, "plain"))
1113 mc
= MIME_TEXT_PLAIN
;
1114 else if (!asccasecmp(ct
, "html"))
1115 mc
= MIME_TEXT_HTML
;
1118 } else if (is_asccaseprefix(ct
, "message/")) {
1119 ct
+= sizeof("message/") -1;
1120 if (!asccasecmp(ct
, "rfc822"))
1124 } else if (!ascncasecmp(ct
, "multipart/", 10)) {
1125 ct
+= sizeof("multipart/") -1;
1126 if (!asccasecmp(ct
, "alternative"))
1127 mc
= MIME_ALTERNATIVE
;
1128 else if (!asccasecmp(ct
, "related"))
1130 else if (!asccasecmp(ct
, "digest"))
1134 } else if (is_asccaseprefix(ct
, "application/")) {
1136 goto jos_content_check
;
1137 ct
+= sizeof("application/") -1;
1138 if (!asccasecmp(ct
, "pkcs7-mime") || !asccasecmp(ct
, "x-pkcs7-mime"))
1146 if ((mce
.f
& MIMECE_BIN_PARSE
) && mpp
->m_mime_enc
!= MIMEE_BIN
&&
1147 mpp
->m_charset
!= NULL
&& asccasecmp(mpp
->m_charset
, "binary"))
1148 mc
= _mt_classify_os_part(mce
.f
, mpp
);
1152 FL
enum mime_handler_flags
1153 mime_type_handler(struct mime_handler
*mhp
, struct mimepart
const *mpp
,
1154 enum sendaction action
)
1157 #define __L (sizeof(__S) -1)
1158 struct mtlookup mtl
;
1160 enum mime_handler_flags rv
;
1161 char const *es
, *cs
, *ccp
;
1165 memset(mhp
, 0, sizeof *mhp
);
1169 if (action
== SEND_QUOTE
|| action
== SEND_QUOTE_ALL
)
1170 rv
|= MIME_HDL_ISQUOTE
;
1171 else if (action
!= SEND_TODISP
&& action
!= SEND_TODISP_ALL
)
1174 el
= ((es
= mpp
->m_filename
) != NULL
&& (es
= strrchr(es
, '.')) != NULL
&&
1175 *++es
!= '\0') ? strlen(es
) : 0;
1176 cl
= ((cs
= mpp
->m_ct_type_usr_ovwr
) != NULL
||
1177 (cs
= mpp
->m_ct_type_plain
) != NULL
) ? strlen(cs
) : 0;
1178 if ((l
= MAX(el
, cl
)) == 0) {
1179 /* TODO this should be done during parse time! */
1183 /* We don't pass the flags around, so ensure carrier is up-to-date */
1186 buf
= ac_alloc(__L
+ l
+1);
1187 memcpy(buf
, __S
, __L
);
1189 /* File-extension handlers take precedence.
1190 * Yes, we really "fail" here for file extensions which clash MIME types */
1192 memcpy(buf
+ __L
, es
, el
+1);
1193 for (cp
= buf
+ __L
; *cp
!= '\0'; ++cp
)
1194 *cp
= lowerconv(*cp
);
1196 if ((mhp
->mh_shell_cmd
= ccp
= vok_vlook(buf
)) != NULL
) {
1197 rv
= _mt_pipe_check(mhp
);
1202 /* Then MIME Content-Type:, if any */
1206 memcpy(buf
+ __L
, cs
, cl
+1);
1207 for (cp
= buf
+ __L
; *cp
!= '\0'; ++cp
)
1208 *cp
= lowerconv(*cp
);
1210 if ((mhp
->mh_shell_cmd
= vok_vlook(buf
)) != NULL
) {
1211 rv
= _mt_pipe_check(mhp
);
1215 if (_mt_by_mtname(&mtl
, cs
) != NULL
)
1216 switch (mtl
.mtl_node
->mt_flags
& __MT_MARKMASK
) {
1217 #ifndef HAVE_FILTER_HTML_TAGSOUP
1222 #ifdef HAVE_FILTER_HTML_TAGSOUP
1224 mhp
->mh_ptf
= &htmlflt_process_main
;
1225 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
=
1226 UNCONST(_("Builtin HTML tagsoup filter")));
1227 rv
^= MIME_HDL_NULL
| MIME_HDL_PTF
;
1232 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
= UNCONST(_("Plain text view")));
1233 rv
^= MIME_HDL_NULL
| MIME_HDL_TEXT
;
1244 if ((rv
&= MIME_HDL_TYPE_MASK
) == MIME_HDL_NULL
)
1245 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
= UNCONST(
1246 _("[-- No MIME handler installed or none applicable --]")));