1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ `(un)?mimetype' and other mime.types(5) related facilities.
3 *@ "Keep in sync with" ./mime.types.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: ISC
8 * Permission to use, copy, modify, and/or distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 #define n_FILE mime_types
23 #ifndef HAVE_AMALGAMATION
37 __MT_TMAX
= _MT_OTHER
,
40 _MT_CMD
= 1u<< 8, /* Via `mimetype' (not struct mtbltin) */
41 _MT_USR
= 1u<< 9, /* VAL_MIME_TYPES_USR */
42 _MT_SYS
= 1u<<10, /* VAL_MIME_TYPES_SYS */
43 _MT_FSPEC
= 1u<<11, /* Loaded via f= *mimetypes-load-control* spec. */
45 a_MT_TM_PLAIN
= 1u<<16, /* Without pipe handler display as text */
46 a_MT_TM_SOUP_h
= 2u<<16, /* Ditto, but HTML tagsoup parser if possible */
47 a_MT_TM_SOUP_H
= 3u<<16, /* HTML tagsoup parser, else NOT plain text */
48 a_MT_TM_QUIET
= 4u<<16, /* No "no mime handler available" message */
49 a_MT__TM_MARKMASK
= 7u<<16
52 enum mime_type_class
{
54 _MT_C_CLEAN
= _MT_C_NONE
, /* Plain RFC 5322 message */
55 _MT_C_DEEP_INSPECT
= 1u<<0, /* Always test all the file */
56 _MT_C_NCTT
= 1u<<1, /* *contenttype == NULL */
57 _MT_C_ISTXT
= 1u<<2, /* *contenttype =~ text\/ */
58 _MT_C_ISTXTCOK
= 1u<<3, /* _ISTXT + *mime-allow-text-controls* */
59 _MT_C_HIGHBIT
= 1u<<4, /* Not 7bit clean */
60 _MT_C_LONGLINES
= 1u<<5, /* MIME_LINELEN_LIMIT exceed. */
61 _MT_C_CTRLCHAR
= 1u<<6, /* Control characters seen */
62 _MT_C_HASNUL
= 1u<<7, /* Contains \0 characters */
63 _MT_C_NOTERMNL
= 1u<<8, /* Lacks a final newline */
64 _MT_C_FROM_
= 1u<<9, /* ^From_ seen */
65 _MT_C_FROM_1STLINE
= 1u<<10, /* From_ line seen */
66 _MT_C_SUGGEST_DONE
= 1u<<16, /* Inspector suggests to stop further parse */
67 _MT_C__1STLINE
= 1u<<17 /* .. */
77 struct mtnode
*mt_next
;
79 ui32_t mt_mtlen
; /* Length of MIME type string, rest thereafter */
80 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
81 * that could already store character data here */
88 struct mtnode
const *mtl_node
;
89 char *mtl_result
; /* If requested, salloc()ed MIME type */
95 ssize_t mtca_curlnlen
;
99 enum mime_type_class mtca_mtc
;
101 ui64_t mtca_all_highbit
; /* TODO not yet interpreted */
102 ui64_t mtca_all_bogus
;
105 static struct mtbltin
const _mt_bltin
[] = {
106 #include <gen-mime-types.h>
109 static char const _mt_typnames
[][16] = {
110 "application/", "audio/", "image/",
111 "message/", "multipart/", "text/",
114 n_CTAV(_MT_APPLICATION
== 0 && _MT_AUDIO
== 1 && _MT_IMAGE
== 2 &&
115 _MT_MESSAGE
== 3 && _MT_MULTIPART
== 4 && _MT_TEXT
== 5 &&
119 static bool_t _mt_is_init
;
120 static struct mtnode
*_mt_list
;
122 /* Initialize MIME type list in order */
123 static void _mt_init(void);
124 static bool_t
__mt_load_file(ui32_t orflags
,
125 char const *file
, char **line
, size_t *linesize
);
127 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
129 static struct mtnode
* _mt_create(bool_t cmdcalled
, ui32_t orflags
,
130 char const *line
, size_t len
);
132 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
133 * if with_result >mtl_result will be created upon success for the former */
134 static struct mtlookup
* _mt_by_filename(struct mtlookup
*mtlp
,
135 char const *name
, bool_t with_result
);
136 static struct mtlookup
* _mt_by_mtname(struct mtlookup
*mtlp
,
139 /* In-depth inspection of raw content: call _round() repeatedly, last time with
140 * a 0 length buffer, finally check .mtca_mtc for result.
141 * No further call is needed if _round() return includes _MT_C_SUGGEST_DONE,
142 * as the resulting classification is unambiguous */
143 n_INLINE
struct mt_class_arg
* _mt_classify_init(struct mt_class_arg
*mtcap
,
144 enum mime_type_class initval
);
145 static enum mime_type_class
_mt_classify_round(struct mt_class_arg
*mtcap
);
147 /* We need an in-depth inspection of an application/octet-stream part */
148 static enum mimecontent
_mt_classify_os_part(ui32_t mce
, struct mimepart
*mpp
,
149 bool_t deep_inspect
);
151 /* Check whether a *pipe-XY* handler is applicable, and adjust flags according
152 * to the defined trigger characters; upon entry MIME_HDL_NULL is set, and that
153 * isn't changed if mhp doesn't apply */
154 static enum mime_handler_flags
a_mt_pipe_check(struct mime_handler
*mhp
);
160 char c
, *line
; /* TODO line pool (below) */
163 char const *srcs_arr
[10], *ccp
, **srcs
;
169 /* Always load our built-ins */
170 for (tail
= NULL
, i
= 0; i
< n_NELEM(_mt_bltin
); ++i
) {
171 struct mtbltin
const *mtbp
= _mt_bltin
+ i
;
172 struct mtnode
*mtnp
= n_alloc(sizeof *mtnp
);
175 tail
->mt_next
= mtnp
;
179 mtnp
->mt_next
= NULL
;
180 mtnp
->mt_flags
= mtbp
->mtb_flags
;
181 mtnp
->mt_mtlen
= mtbp
->mtb_mtlen
;
182 mtnp
->mt_line
= mtbp
->mtb_line
;
185 /* Decide which files sources have to be loaded */
186 if ((ccp
= ok_vlook(mimetypes_load_control
)) == NULL
)
188 else if (*ccp
== '\0')
192 srcs
[-1] = srcs
[-2] = NULL
;
194 if (strchr(ccp
, '=') != NULL
) {
197 while ((ccp
= n_strsep(&line
, ',', TRU1
)) != NULL
) {
198 switch ((c
= *ccp
)) {
200 srcs_arr
[1] = VAL_MIME_TYPES_SYS
;
204 srcs_arr
[0] = VAL_MIME_TYPES_USR
;
210 if (*++ccp
== '=' && *++ccp
!= '\0') {
211 if (PTR2SIZE(srcs
- srcs_arr
) < n_NELEM(srcs_arr
))
214 n_err(_("*mimetypes-load-control*: too many sources, "
215 "skipping %s\n"), n_shexp_quote_cp(ccp
, FAL0
));
223 } else for (i
= 0; (c
= ccp
[i
]) != '\0'; ++i
)
225 case 'S': case 's': srcs_arr
[1] = VAL_MIME_TYPES_SYS
; break;
226 case 'U': case 'u': srcs_arr
[0] = VAL_MIME_TYPES_USR
; break;
229 n_err(_("*mimetypes-load-control*: unsupported content: %s\n"), ccp
);
233 /* Load all file-based sources in the desired order */
236 for (j
= 0, i
= (ui32_t
)PTR2SIZE(srcs
- srcs_arr
), srcs
= srcs_arr
;
237 i
> 0; ++j
, ++srcs
, --i
)
240 else if (!__mt_load_file((j
== 0 ? _MT_USR
241 : (j
== 1 ? _MT_SYS
: _MT_FSPEC
)), *srcs
, &line
, &linesize
)) {
242 if ((n_poption
& n_PO_D_V
) || j
> 1)
243 n_err(_("*mimetypes-load-control*: cannot open or load %s\n"),
244 n_shexp_quote_cp(*srcs
, FAL0
));
254 __mt_load_file(ui32_t orflags
, char const *file
, char **line
, size_t *linesize
)
258 struct mtnode
*head
, *tail
, *mtnp
;
262 if ((cp
= fexpand(file
, FEXP_LOCAL
| FEXP_NOPROTO
)) == NULL
||
263 (fp
= Fopen(cp
, "r")) == NULL
) {
268 for (head
= tail
= NULL
; fgetline(line
, linesize
, NULL
, &len
, fp
, 0) != 0;)
269 if ((mtnp
= _mt_create(FAL0
, orflags
, *line
, len
)) != NULL
) {
273 tail
->mt_next
= mtnp
;
277 tail
->mt_next
= _mt_list
;
287 static struct mtnode
*
288 _mt_create(bool_t cmdcalled
, ui32_t orflags
, char const *line
, size_t len
)
291 char const *typ
, *subtyp
;
297 /* Drop anything after a comment first TODO v15: only when read from file */
298 if ((typ
= memchr(line
, '#', len
)) != NULL
)
299 len
= PTR2SIZE(typ
- line
);
301 /* Then trim any trailing whitespace from line (including NL/CR) */
305 work
.s
= n_UNCONST(line
);
307 line
= n_str_trim(&work
, n_STR_TRIM_BOTH
)->s
;
312 /* (But wait - is there a type marker?) */
314 if(!(orflags
& (_MT_USR
| _MT_SYS
)) && *typ
== '@'){
318 orflags
|= a_MT_TM_PLAIN
;
325 else if(len
> 4 && typ
[2] == '@' && typ
[3] == ' '){
326 n_OBSOLETE("`mimetype': the trailing \"@\" in \"type-marker\" "
333 default: goto jeinval
;
334 case 't': orflags
|= a_MT_TM_PLAIN
; break;
335 case 'h': orflags
|= a_MT_TM_SOUP_h
; break;
336 case 'H': orflags
|= a_MT_TM_SOUP_H
; break;
337 case 'q': orflags
|= a_MT_TM_QUIET
; break;
346 while (len
> 0 && !blankchar(*line
))
348 /* Ignore empty lines and even incomplete specifications (only MIME type)
349 * because this is quite common in mime.types(5) files */
350 if (len
== 0 || (tlen
= PTR2SIZE(line
- typ
)) == 0) {
351 if (cmdcalled
|| (orflags
& _MT_FSPEC
)) {
353 line
= _("(no value)");
356 n_err(_("Empty MIME type or no extensions given: %.*s\n"),
362 if ((subtyp
= memchr(typ
, '/', tlen
)) == NULL
|| subtyp
[1] == '\0' ||
363 spacechar(subtyp
[1])) {
365 if(cmdcalled
|| (orflags
& _MT_FSPEC
) || (n_poption
& n_PO_D_V
))
366 n_err(_("%s MIME type: %.*s\n"),
367 (cmdcalled
? _("Invalid") : _("mime.types(5): invalid")),
373 /* Map to mime_type */
374 tlen
= PTR2SIZE(subtyp
- typ
);
375 for (i
= __MT_TMIN
;;) {
376 if (!ascncasecmp(_mt_typnames
[i
], typ
, tlen
)) {
378 tlen
= PTR2SIZE(line
- subtyp
);
382 if (++i
== __MT_TMAX
) {
383 orflags
|= _MT_OTHER
;
384 tlen
= PTR2SIZE(line
- typ
);
389 /* Strip leading whitespace from the list of extensions;
390 * trailing WS has already been trimmed away above.
391 * Be silent on slots which define a mimetype without any value */
392 while (len
> 0 && blankchar(*line
))
398 mtnp
= n_alloc(sizeof(*mtnp
) + tlen
+ len
+1);
399 mtnp
->mt_next
= NULL
;
400 mtnp
->mt_flags
= orflags
;
401 mtnp
->mt_mtlen
= (ui32_t
)tlen
;
402 { char *l
= (char*)(mtnp
+ 1);
404 memcpy(l
, typ
, tlen
);
405 memcpy(l
+ tlen
, line
, len
);
415 static struct mtlookup
*
416 _mt_by_filename(struct mtlookup
*mtlp
, char const *name
, bool_t with_result
)
420 char const *ext
, *cp
;
423 memset(mtlp
, 0, sizeof *mtlp
);
425 if ((nlen
= strlen(name
)) == 0) /* TODO name should be a URI */
427 /* We need a period TODO we should support names like README etc. */
428 for (i
= nlen
; name
[--i
] != '.';)
429 if (i
== 0 || name
[i
] == '/') /* XXX no magics */
431 /* While here, basename() it */
432 while (i
> 0 && name
[i
- 1] != '/')
436 mtlp
->mtl_name
= name
;
437 mtlp
->mtl_nlen
= nlen
;
442 /* ..all the MIME types */
443 for (mtnp
= _mt_list
; mtnp
!= NULL
; mtnp
= mtnp
->mt_next
)
444 for (ext
= mtnp
->mt_line
+ mtnp
->mt_mtlen
;; ext
= cp
) {
446 while (whitechar(*cp
))
449 while (!whitechar(*cp
) && *cp
!= '\0')
452 if ((i
= PTR2SIZE(cp
- ext
)) == 0)
454 /* Don't allow neither of ".txt" or "txt" to match "txt" */
455 else if (i
+ 1 >= nlen
|| name
[(j
= nlen
- i
) - 1] != '.' ||
456 ascncasecmp(name
+ j
, ext
, i
))
460 mtlp
->mtl_node
= mtnp
;
465 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
469 name
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
473 mtlp
->mtl_result
= n_autorec_alloc(i
+ j
+1);
475 memcpy(mtlp
->mtl_result
, name
, j
);
476 memcpy(mtlp
->mtl_result
+ j
, mtnp
->mt_line
, i
);
477 mtlp
->mtl_result
[j
+= i
] = '\0';
487 static struct mtlookup
*
488 _mt_by_mtname(struct mtlookup
*mtlp
, char const *mtname
)
495 memset(mtlp
, 0, sizeof *mtlp
);
497 if ((mtlp
->mtl_nlen
= nlen
= strlen(mtlp
->mtl_name
= mtname
)) == 0)
503 /* ..all the MIME types */
504 for (mtnp
= _mt_list
; mtnp
!= NULL
; mtnp
= mtnp
->mt_next
) {
505 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
509 cp
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
514 if (i
+ j
== mtlp
->mtl_nlen
) {
515 char *xmt
= n_lofi_alloc(i
+ j
+1);
518 memcpy(xmt
+ j
, mtnp
->mt_line
, i
);
520 i
= asccasecmp(mtname
, xmt
);
525 mtlp
->mtl_node
= mtnp
;
537 n_INLINE
struct mt_class_arg
*
538 _mt_classify_init(struct mt_class_arg
* mtcap
, enum mime_type_class initval
)
541 memset(mtcap
, 0, sizeof *mtcap
);
542 /*mtcap->mtca_lastc =*/ mtcap
->mtca_c
= EOF
;
543 mtcap
->mtca_mtc
= initval
| _MT_C__1STLINE
;
548 static enum mime_type_class
549 _mt_classify_round(struct mt_class_arg
*mtcap
) /* TODO dig UTF-8 for !text/!! */
551 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
552 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
553 * TODO and report that state to the outer world */
555 #define F_SIZEOF (sizeof(F_) -1)
556 char f_buf
[F_SIZEOF
], *f_p
= f_buf
;
562 enum mime_type_class mtc
;
565 buf
= mtcap
->mtca_buf
;
566 blen
= mtcap
->mtca_len
;
567 curlnlen
= mtcap
->mtca_curlnlen
;
568 alllen
= mtcap
->mtca_all_len
;
570 /*lastc = mtcap->mtca_lastc;*/
571 mtc
= mtcap
->mtca_mtc
;
573 for (;; ++curlnlen
) {
575 /* Real EOF, or only current buffer end? */
576 if(mtcap
->mtca_len
== 0){
592 if (!(mtc
& _MT_C_ISTXTCOK
)) {
593 mtc
|= _MT_C_SUGGEST_DONE
;
598 if (c
== '\n' || c
== EOF
) {
599 mtc
&= ~_MT_C__1STLINE
;
600 if (curlnlen
>= MIME_LINELEN_LIMIT
)
601 mtc
|= _MT_C_LONGLINES
;
608 /* A bit hairy is handling of \r=\x0D=CR.
610 * Control characters other than TAB, or CR and LF as parts of CRLF
611 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
612 * we cannot peek the next character. Thus right here, inspect the last
613 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
614 /*else*/ if (lastc
== '\r')
615 mtc
|= _MT_C_CTRLCHAR
;
617 /* Control character? XXX this is all ASCII here */
618 if (c
< 0x20 || c
== 0x7F) {
619 /* RFC 2045, 6.7, as above ... */
620 if (c
!= '\t' && c
!= '\r')
621 mtc
|= _MT_C_CTRLCHAR
;
623 /* If there is a escape sequence in reverse solidus notation defined
624 * for this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
625 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
626 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
628 if ((c
>= '\x07' && c
<= '\x0D') || c
== '\x1B')
631 /* As a special case, if we are going for displaying data to the user
632 * or quoting a message then simply continue this, in the end, in case
633 * we get there, we will decide upon the all_len/all_bogus ratio
634 * whether this is usable plain text or not */
635 ++mtcap
->mtca_all_bogus
;
636 if(mtc
& _MT_C_DEEP_INSPECT
)
639 mtc
|= _MT_C_HASNUL
; /* Force base64 */
640 if (!(mtc
& _MT_C_ISTXTCOK
)) {
641 mtc
|= _MT_C_SUGGEST_DONE
;
644 } else if ((ui8_t
)c
& 0x80) {
645 mtc
|= _MT_C_HIGHBIT
;
646 ++mtcap
->mtca_all_highbit
;
647 if (!(mtc
& (_MT_C_NCTT
| _MT_C_ISTXT
))) { /* TODO _NCTT?? */
648 mtc
|= _MT_C_HASNUL
/* Force base64 */ | _MT_C_SUGGEST_DONE
;
651 } else if (!(mtc
& _MT_C_FROM_
) && UICMP(z
, curlnlen
, <, F_SIZEOF
)) {
653 if (UICMP(z
, curlnlen
, ==, F_SIZEOF
- 1) &&
654 PTR2SIZE(f_p
- f_buf
) == F_SIZEOF
&&
655 !memcmp(f_buf
, F_
, F_SIZEOF
)){
657 if (mtc
& _MT_C__1STLINE
)
658 mtc
|= _MT_C_FROM_1STLINE
;
662 if (c
== EOF
&& lastc
!= '\n')
663 mtc
|= _MT_C_NOTERMNL
;
665 mtcap
->mtca_curlnlen
= curlnlen
;
666 /*mtcap->mtca_lastc = lastc*/;
668 mtcap
->mtca_mtc
= mtc
;
669 mtcap
->mtca_all_len
= alllen
;
676 static enum mimecontent
677 _mt_classify_os_part(ui32_t mce
, struct mimepart
*mpp
, bool_t deep_inspect
)
679 struct str in
= {NULL
, 0}, outrest
, inrest
, dec
;
680 struct mt_class_arg mtca
;
682 enum mime_type_class mtc
;
690 assert(mpp
->m_mime_enc
!= MIMEE_BIN
);
692 outrest
= inrest
= dec
= in
;
697 /* TODO v15-compat Note we actually bypass our usual file handling by
698 * TODO directly using fseek() on mb.mb_itf -- the v15 rewrite will change
699 * TODO all of this, and until then doing it like this is the only option
700 * TODO to integrate nicely into whoever calls us */
701 start_off
= ftell(mb
.mb_itf
);
702 if ((ibuf
= setinput(&mb
, (struct message
*)mpp
, NEED_BODY
)) == NULL
) {
704 fseek(mb
.mb_itf
, start_off
, SEEK_SET
);
709 /* Skip part headers */
710 for (lc
= '\0'; cnt
> 0; lc
= c
, --cnt
)
711 if ((c
= getc(ibuf
)) == EOF
|| (c
== '\n' && lc
== '\n'))
713 if (cnt
== 0 || ferror(ibuf
))
716 /* So now let's inspect the part content, decoding content-transfer-encoding
717 * along the way TODO this should simply be "mime_factory_create(MPP)"!
718 * TODO In fact m_mime_classifier_(setup|call|call_part|finalize)() and the
719 * TODO state(s) (the _MT_C states) should become reported to the outer
720 * TODO world like that (see MIME boundary TODO around here) */
721 _mt_classify_init(&mtca
, (_MT_C_ISTXT
|
722 (deep_inspect
? _MT_C_DEEP_INSPECT
: _MT_C_NONE
)));
727 c
= (--cnt
== 0) ? EOF
: getc(ibuf
);
728 if ((dobuf
= (c
== '\n'))) {
729 /* Ignore empty lines */
732 } else if ((dobuf
= (c
== EOF
))) {
733 if (lsz
== 0 && outrest
.l
== 0)
738 in
.s
= n_realloc(in
.s
, lsz
+= LINESIZE
);
740 in
.s
[in
.l
++] = (char)c
;
745 switch (mpp
->m_mime_enc
) {
747 if (!b64_decode_part(&dec
, &in
, &outrest
,
748 (did_inrest
? NULL
: &inrest
))) {
749 mtca
.mtca_mtc
= _MT_C_HASNUL
;
750 goto jstopit
; /* break;break; */
755 if (!qp_decode_part(&dec
, &in
, &outrest
, &inrest
)) {
756 mtca
.mtca_mtc
= _MT_C_HASNUL
;
757 goto jstopit
; /* break;break; */
759 if (dec
.l
== 0 && c
!= EOF
) {
765 /* Temporarily switch those two buffers.. */
772 mtca
.mtca_buf
= dec
.s
;
773 mtca
.mtca_len
= (ssize_t
)dec
.l
;
774 if ((mtc
= _mt_classify_round(&mtca
)) & _MT_C_SUGGEST_DONE
) {
781 /* ..and restore switched */
789 if ((in
.l
= inrest
.l
) > 0) {
802 if (outrest
.s
!= NULL
)
804 if (inrest
.s
!= NULL
)
807 fseek(mb
.mb_itf
, start_off
, SEEK_SET
);
809 if (!(mtc
& (_MT_C_HASNUL
/*| _MT_C_CTRLCHAR XXX really? */))) {
810 /* In that special relaxed case we may very well wave through
811 * octet-streams full of control characters, as they do no harm
812 * TODO This should be part of m_mime_classifier_finalize() then! */
814 mtca
.mtca_all_len
- mtca
.mtca_all_bogus
< mtca
.mtca_all_len
>> 2)
817 mc
= MIME_TEXT_PLAIN
;
818 if (mce
& MIMECE_ALL_OVWR
)
819 mpp
->m_ct_type_plain
= "text/plain";
820 if (mce
& (MIMECE_BIN_OVWR
| MIMECE_ALL_OVWR
))
821 mpp
->m_ct_type_usr_ovwr
= "text/plain";
828 static enum mime_handler_flags
829 a_mt_pipe_check(struct mime_handler
*mhp
){
830 enum mime_handler_flags rv_orig
, rv
;
834 rv_orig
= rv
= mhp
->mh_flags
;
836 /* Do we have any handler for this part? */
837 if(*(cp
= mhp
->mh_shell_cmd
) == '\0')
839 else if(*cp
++ != '@'){
842 }else if(*cp
== '\0'){
849 case '*': rv
|= MIME_HDL_COPIOUSOUTPUT
; ++cp
; goto jnextc
;
850 case '#': rv
|= MIME_HDL_NOQUOTE
; ++cp
; goto jnextc
;
851 case '&': rv
|= MIME_HDL_ASYNC
; ++cp
; goto jnextc
;
852 case '!': rv
|= MIME_HDL_NEEDSTERM
; ++cp
; goto jnextc
;
854 if(rv
& MIME_HDL_TMPF
)
855 rv
|= MIME_HDL_TMPF_UNLINK
;
860 rv
|= MIME_HDL_TMPF_FILL
;
869 mhp
->mh_shell_cmd
= cp
;
872 if(rv
& MIME_HDL_TMPF_FILL
)
876 if(rv
& MIME_HDL_ISQUOTE
){
877 if(rv
& MIME_HDL_NOQUOTE
)
880 /* Cannot fetch data back from asynchronous process */
881 if(rv
& MIME_HDL_ASYNC
)
884 /* TODO Can't use a "needsterminal" program for quoting */
885 if(rv
& MIME_HDL_NEEDSTERM
)
889 if(rv
& MIME_HDL_NEEDSTERM
){
890 if(rv
& MIME_HDL_COPIOUSOUTPUT
){
891 n_err(_("MIME type handlers: cannot use needsterminal and "
892 "copiousoutput together\n"));
895 if(rv
& MIME_HDL_ASYNC
){
896 n_err(_("MIME type handlers: cannot use needsterminal and "
897 "x-mailx-async together\n"));
901 /* needsterminal needs a terminal */
902 if(!(n_psonce
& n_PSO_INTERACTIVE
))
906 if(rv
& MIME_HDL_ASYNC
){
907 if(rv
& MIME_HDL_COPIOUSOUTPUT
){
908 n_err(_("MIME type handlers: cannot use x-mailx-async and "
909 "copiousoutput together\n"));
912 if(rv
& MIME_HDL_TMPF_UNLINK
){
913 n_err(_("MIME type handlers: cannot use x-mailx-async and "
914 "x-mailx-tmpfile-unlink together\n"));
919 /* TODO mailcap-only: TMPF_UNLINK): needs -tmpfile OR -tmpfile-fill */
933 struct n_string s
, *sp
;
941 sp
= n_string_creat_auto(&s
);
943 if(*(argv
= v
) == NULL
){
947 if(_mt_list
== NULL
){
948 fprintf(n_stdout
, _("# `mimetype': no mime.types(5) available\n"));
952 if((fp
= Ftmp(NULL
, "mimetype", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)
954 n_perr(_("tmpfile"), 0);
959 sp
= n_string_reserve(sp
, 63);
961 for(l
= 0, mtnp
= _mt_list
; mtnp
!= NULL
; ++l
, mtnp
= mtnp
->mt_next
){
964 sp
= n_string_trunc(sp
, 0);
966 switch(mtnp
->mt_flags
& a_MT__TM_MARKMASK
){
967 case a_MT_TM_PLAIN
: cp
= "@t "; break;
968 case a_MT_TM_SOUP_h
: cp
= "@h "; break;
969 case a_MT_TM_SOUP_H
: cp
= "@H "; break;
970 case a_MT_TM_QUIET
: cp
= "@q "; break;
971 default: cp
= NULL
; break;
974 sp
= n_string_push_cp(sp
, cp
);
976 if((mtnp
->mt_flags
& __MT_TMASK
) != _MT_OTHER
)
977 sp
= n_string_push_cp(sp
, _mt_typnames
[mtnp
->mt_flags
&__MT_TMASK
]);
979 sp
= n_string_push_buf(sp
, mtnp
->mt_line
, mtnp
->mt_mtlen
);
980 sp
= n_string_push_c(sp
, ' ');
981 sp
= n_string_push_c(sp
, ' ');
982 sp
= n_string_push_cp(sp
, &mtnp
->mt_line
[mtnp
->mt_mtlen
]);
984 fprintf(fp
, "wysh mimetype %s%s\n", n_string_cp(sp
),
985 ((n_poption
& n_PO_D_V
) == 0 ? n_empty
986 : (mtnp
->mt_flags
& _MT_USR
? " # user"
987 : (mtnp
->mt_flags
& _MT_SYS
? " # system"
988 : (mtnp
->mt_flags
& _MT_FSPEC
? " # f= file"
989 : (mtnp
->mt_flags
& _MT_CMD
? " # command" : " # built-in"))))));
992 page_or_print(fp
, l
);
995 for(; *argv
!= NULL
; ++argv
){
997 sp
= n_string_push_c(sp
, ' ');
998 sp
= n_string_push_cp(sp
, *argv
);
1001 mtnp
= _mt_create(TRU1
, _MT_CMD
, n_string_cp(sp
), sp
->s_len
);
1003 mtnp
->mt_next
= _mt_list
;
1010 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
1014 c_unmimetype(void *v
)
1017 struct mtnode
*lnp
, *mtnp
;
1021 /* Need to load that first as necessary */
1025 for (; *argv
!= NULL
; ++argv
) {
1026 if (!asccasecmp(*argv
, "reset")) {
1031 if (argv
[0][0] == '*' && argv
[0][1] == '\0') {
1033 while ((mtnp
= _mt_list
) != NULL
) {
1034 _mt_list
= mtnp
->mt_next
;
1040 for (match
= FAL0
, lnp
= NULL
, mtnp
= _mt_list
; mtnp
!= NULL
;) {
1045 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
1049 typ
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
1053 val
= n_lofi_alloc(i
+ mtnp
->mt_mtlen
+1);
1054 memcpy(val
, typ
, i
);
1055 memcpy(val
+ i
, mtnp
->mt_line
, mtnp
->mt_mtlen
);
1056 val
[i
+= mtnp
->mt_mtlen
] = '\0';
1057 i
= asccasecmp(val
, *argv
);
1061 struct mtnode
*nnp
= mtnp
->mt_next
;
1070 lnp
= mtnp
, mtnp
= mtnp
->mt_next
;
1073 if (!(n_pstate
& n_PS_ROBOT
) || (n_poption
& n_PO_D_V
))
1074 n_err(_("No such MIME type: %s\n"), *argv
);
1079 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
1083 n_mimetype_check_mtname(char const *name
)
1085 struct mtlookup mtl
;
1089 rv
= (_mt_by_mtname(&mtl
, name
) != NULL
);
1095 n_mimetype_classify_filename(char const *name
)
1097 struct mtlookup mtl
;
1100 _mt_by_filename(&mtl
, name
, TRU1
);
1102 return mtl
.mtl_result
;
1106 n_mimetype_classify_file(FILE *fp
, char const **contenttype
,
1107 char const **charset
, int *do_iconv
)
1109 /* TODO classify once only PLEASE PLEASE PLEASE */
1110 /* TODO message/rfc822 is special in that it may only be 7bit, 8bit or
1111 * TODO binary according to RFC 2046, 5.2.1
1112 * TODO The handling of which is a hack */
1114 enum mime_type_class mtc
;
1120 assert(ftell(fp
) == 0x0l
);
1124 if (*contenttype
== NULL
) {
1127 } else if (!ascncasecmp(*contenttype
, "text/", 5)) {
1128 mtc
= ok_blook(mime_allow_text_controls
)
1129 ? _MT_C_ISTXT
| _MT_C_ISTXTCOK
: _MT_C_ISTXT
;
1131 } else if (!asccasecmp(*contenttype
, "message/rfc822")) {
1139 menc
= mime_enc_target();
1141 if ((fpsz
= fsize(fp
)) == 0)
1144 char buf
[BUFFER_SIZE
];
1145 struct mt_class_arg mtca
;
1147 _mt_classify_init(&mtca
, mtc
);
1149 mtca
.mtca_len
= fread(buf
, sizeof(buf
[0]), n_NELEM(buf
), fp
);
1150 mtca
.mtca_buf
= buf
;
1151 if ((mtc
= _mt_classify_round(&mtca
)) & _MT_C_SUGGEST_DONE
)
1153 if (mtca
.mtca_len
== 0)
1156 /* TODO ferror(fp) ! */
1160 if (mtc
& _MT_C_HASNUL
) {
1162 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
1163 * on request; else enforce what file(1)/libmagic(3) would suggest */
1164 if (mtc
& _MT_C_ISTXTCOK
)
1166 if (mtc
& (_MT_C_NCTT
| _MT_C_ISTXT
))
1167 *contenttype
= "application/octet-stream";
1172 (_MT_C_LONGLINES
| _MT_C_CTRLCHAR
| _MT_C_NOTERMNL
| _MT_C_FROM_
)) {
1173 if (menc
!= MIMEE_B64
)
1177 if (mtc
& _MT_C_HIGHBIT
) {
1179 if (mtc
& (_MT_C_NCTT
| _MT_C_ISTXT
))
1180 *do_iconv
= ((mtc
& _MT_C_HIGHBIT
) != 0);
1184 if (mtc
& _MT_C_NCTT
)
1185 *contenttype
= "text/plain";
1187 /* Not an attachment with specified charset? */
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
);
1193 /* TODO mime_type_file_classify() shouldn't return conversion */
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"),
1200 *contenttype
= "application/mbox";
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
))));
1210 c
= (menc
== MIMEE_7B
? CONV_7BIT
1211 : (menc
== MIMEE_8B
? CONV_8BIT
1212 : (menc
== MIMEE_QP
? CONV_TOQP
: CONV_TOB64
)));
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
;
1223 union {char const *cp
; ui32_t f
;} mce
;
1228 if ((ct
= mpp
->m_ct_type_plain
) == NULL
) /* TODO may not */
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"));
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
){
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
;
1256 if(*ct
== '\0' || strchr(ct
, '/') == NULL
) /* For compat with non-MIME */
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
;
1266 }else if(is_asccaseprefix("message/", ct
)){
1267 ct
+= sizeof("message/") -1;
1268 if(!asccasecmp(ct
, "rfc822"))
1272 }else if(is_asccaseprefix("multipart/", ct
)){
1275 enum mimecontent mt_mc
;
1277 {"alternative\0", MIME_ALTERNATIVE
},
1278 {"related", MIME_RELATED
},
1279 {"digest", MIME_DIGEST
},
1280 {"signed", MIME_SIGNED
},
1281 {"encrypted", MIME_ENCRYPTED
}
1284 for(ct
+= sizeof("multipart/") -1, mtap
= mta
;;)
1285 if(!asccasecmp(ct
, mtap
->mt_name
)){
1288 }else if(++mtap
== mta
+ n_NELEM(mta
)){
1292 }else if(is_asccaseprefix("application/", ct
)){
1294 goto jos_content_check
;
1295 ct
+= sizeof("application/") -1;
1296 if(!asccasecmp(ct
, "pkcs7-mime") || !asccasecmp(ct
, "x-pkcs7-mime"))
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
);
1310 FL
enum mime_handler_flags
1311 n_mimetype_handler(struct mime_handler
*mhp
, struct mimepart
const *mpp
,
1312 enum sendaction action
)
1315 #define __L (sizeof(__S) -1)
1316 struct mtlookup mtl
;
1318 enum mime_handler_flags rv
, xrv
;
1319 char const *es
, *cs
, *ccp
;
1323 memset(mhp
, 0, sizeof *mhp
);
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
)
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! */
1342 /* We don't pass the flags around, so ensure carrier is up-to-date */
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 */
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
);
1361 /* Then MIME Content-Type:, if any */
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
);
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
:
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
;
1391 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
= n_UNCONST(_("Plain text")));
1392 rv
^= MIME_HDL_NULL
| MIME_HDL_TEXT
;
1396 mhp
->mh_msg
.s
= n_UNCONST(n_empty
);
1407 if((rv
&= MIME_HDL_TYPE_MASK
) == MIME_HDL_NULL
){
1408 if(mhp
->mh_msg
.s
== NULL
)
1409 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
= n_UNCONST(
1410 A_("[-- No MIME handler installed, or not applicable --]\n")));
1411 }else if(rv
== MIME_HDL_CMD
&& !(xrv
& MIME_HDL_COPIOUSOUTPUT
) &&
1412 action
!= SEND_TODISP_PARTS
){
1413 mhp
->mh_msg
.l
= strlen(mhp
->mh_msg
.s
= n_UNCONST(
1414 _("[-- Use the command `mimeview' to display this --]\n")));
1415 xrv
&= ~MIME_HDL_TYPE_MASK
;
1416 xrv
|= (rv
= MIME_HDL_MSG
);
1418 mhp
->mh_flags
= xrv
;