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
56 struct mtnode
*mt_next
;
58 ui32_t mt_mtlen
; /* Length of MIME type string, rest thereafter */
59 /* C99 forbids flexible arrays in union, so unfortunately we waste a pointer
60 * that could already store character data here */
67 struct mtnode
const *mtl_node
;
68 char *mtl_result
; /* If requested, salloc()ed MIME type */
71 static struct mtbltin
const _mt_bltin
[] = {
72 #include "mime_types.h"
75 static char const _mt_typnames
[][16] = {
76 "application/", "audio/", "image/",
77 "message/", "multipart/", "text/",
80 CTA(_MT_APPLICATION
== 0 && _MT_AUDIO
== 1 && _MT_IMAGE
== 2 &&
81 _MT_MESSAGE
== 3 && _MT_MULTIPART
== 4 && _MT_TEXT
== 5 &&
85 static bool_t _mt_is_init
;
86 static struct mtnode
*_mt_list
;
88 /* Initialize MIME type list in order */
89 static void _mt_init(void);
90 static bool_t
__mt_load_file(ui32_t orflags
,
91 char const *file
, char **line
, size_t *linesize
);
93 /* Create (prepend) a new MIME type; cmdcalled results in a bit more verbosity
95 static struct mtnode
* _mt_create(bool_t cmdcalled
, ui32_t orflags
,
96 char const *line
, size_t len
);
98 /* Try to find MIME type by X (after zeroing mtlp), return NULL if not found;
99 * if with_result >mtl_result will be created upon success for the former */
100 static struct mtlookup
* _mt_by_filename(struct mtlookup
*mtlp
,
101 char const *name
, bool_t with_result
);
102 static struct mtlookup
* _mt_by_mtname(struct mtlookup
*mtlp
,
109 char c
, *line
; /* TODO line pool (below) */
112 char const *srcs_arr
[10], *ccp
, **srcs
;
118 /* Always load our builtins */
119 for (tail
= NULL
, i
= 0; i
< NELEM(_mt_bltin
); ++i
) {
120 struct mtbltin
const *mtbp
= _mt_bltin
+ i
;
121 struct mtnode
*mtnp
= smalloc(sizeof *mtnp
);
124 tail
->mt_next
= mtnp
;
128 mtnp
->mt_next
= NULL
;
129 mtnp
->mt_flags
= mtbp
->mtb_flags
;
130 mtnp
->mt_mtlen
= mtbp
->mtb_mtlen
;
131 mtnp
->mt_line
= mtbp
->mtb_line
;
134 /* Decide which files sources have to be loaded */
135 if ((ccp
= ok_vlook(mimetypes_load_control
)) == NULL
)
137 else if (*ccp
== '\0')
141 srcs
[-1] = srcs
[-2] = NULL
;
143 if (strchr(ccp
, '=') != NULL
) {
146 while ((ccp
= n_strsep(&line
, ',', TRU1
)) != NULL
) {
147 switch ((c
= *ccp
)) {
149 srcs_arr
[1] = MIME_TYPES_SYS
;
153 srcs_arr
[0] = MIME_TYPES_USR
;
159 if (*++ccp
== '=' && *++ccp
!= '\0') {
160 if (PTR2SIZE(srcs
- srcs_arr
) < NELEM(srcs_arr
))
163 n_err(_("*mimetypes-load-control*: too many sources, "
164 "skipping \"%s\"\n"), ccp
);
172 } else for (i
= 0; (c
= ccp
[i
]) != '\0'; ++i
)
174 case 'S': case 's': srcs_arr
[1] = MIME_TYPES_SYS
; break;
175 case 'U': case 'u': srcs_arr
[0] = MIME_TYPES_USR
; break;
178 n_err(_("*mimetypes-load-control*: unsupported content: \"%s\"\n"),
183 /* Load all file-based sources in the desired order */
186 for (j
= 0, i
= (ui32_t
)PTR2SIZE(srcs
- srcs_arr
), srcs
= srcs_arr
;
187 i
> 0; ++j
, ++srcs
, --i
)
190 else if (!__mt_load_file((j
== 0 ? _MT_USR
: (j
== 1 ? _MT_SYS
: 0)),
191 *srcs
, &line
, &linesize
)) {
192 if ((options
& OPT_D_V
) || j
> 1)
193 n_err(_("*mimetypes-load-control*: can't open or load \"%s\"\n"),
204 __mt_load_file(ui32_t orflags
, char const *file
, char **line
, size_t *linesize
)
208 struct mtnode
*head
, *tail
, *mtnp
;
212 if ((cp
= file_expand(file
)) == NULL
|| (fp
= Fopen(cp
, "r")) == NULL
) {
217 for (head
= tail
= NULL
; fgetline(line
, linesize
, NULL
, &len
, fp
, 0) != 0;)
218 if ((mtnp
= _mt_create(FAL0
, orflags
, *line
, len
)) != NULL
) {
222 tail
->mt_next
= mtnp
;
226 tail
->mt_next
= _mt_list
;
236 static struct mtnode
*
237 _mt_create(bool_t cmdcalled
, ui32_t orflags
, char const *line
, size_t len
)
239 struct mtnode
*mtnp
= NULL
;
240 char const *typ
, *subtyp
;
244 /* Drop anything after a comment first */
245 if ((typ
= memchr(line
, '#', len
)) != NULL
)
246 len
= PTR2SIZE(typ
- line
);
248 /* Then trim any trailing whitespace from line (including NL/CR) */
249 while (len
> 0 && spacechar(line
[len
- 1]))
252 /* Isolate MIME type, trim any whitespace from it */
253 while (len
> 0 && blankchar(*line
))
257 /* (But wait - is there a type marker?) */
258 if (!(orflags
& (_MT_USR
| _MT_SYS
)) && *typ
== '@') {
262 orflags
|= _MT_PLAIN
;
266 } else if (len
> 4 && typ
[2] == '@' && typ
[3] == ' ') {
268 case 't': orflags
|= _MT_PLAIN
; goto jexttypmar
;
269 case 'h': orflags
|= _MT_SOUP_h
; goto jexttypmar
;
270 case 'H': orflags
|= _MT_SOUP_H
;
283 while (len
> 0 && !blankchar(*line
))
285 /* Ignore empty lines and even incomplete specifications (only MIME type)
286 * because this is quite common in mime.types(5) files */
287 if (len
== 0 || (tlen
= PTR2SIZE(line
- typ
)) == 0) {
289 n_err(_("Empty MIME type or no extensions given: \"%s\"\n"),
290 (len
== 0 ? _("(no value)") : line
));
294 if ((subtyp
= memchr(typ
, '/', tlen
)) == NULL
) {
296 if (cmdcalled
|| (options
& OPT_D_V
))
297 n_err(_("%s MIME type: \"%s\"\n"),
298 (cmdcalled
? _("Invalid") : _("mime.types(5): invalid")), typ
);
303 /* Map to mime_type */
304 tlen
= PTR2SIZE(subtyp
- typ
);
305 for (i
= __MT_TMIN
;;) {
306 if (!ascncasecmp(_mt_typnames
[i
], typ
, tlen
)) {
308 tlen
= PTR2SIZE(line
- subtyp
);
312 if (++i
== __MT_TMAX
) {
313 orflags
|= _MT_OTHER
;
314 tlen
= PTR2SIZE(line
- typ
);
319 /* Strip leading whitespace from the list of extensions;
320 * trailing WS has already been trimmed away above.
321 * Be silent on slots which define a mimetype without any value */
322 while (len
> 0 && blankchar(*line
))
328 mtnp
= smalloc(sizeof(*mtnp
) + tlen
+ len
+1);
329 mtnp
->mt_next
= NULL
;
330 mtnp
->mt_flags
= (orflags
|= _MT_LOADED
);
331 mtnp
->mt_mtlen
= (ui32_t
)tlen
;
332 { char *l
= (char*)(mtnp
+ 1);
334 memcpy(l
, typ
, tlen
);
335 memcpy(l
+ tlen
, line
, len
);
345 static struct mtlookup
*
346 _mt_by_filename(struct mtlookup
*mtlp
, char const *name
, bool_t with_result
)
350 char const *ext
, *cp
;
353 memset(mtlp
, 0, sizeof *mtlp
);
355 if ((nlen
= strlen(name
)) == 0) /* TODO name should be a URI */
357 /* We need a period TODO we should support names like README etc. */
358 for (i
= nlen
; name
[--i
] != '.';)
359 if (i
== 0 || name
[i
] == '/') /* XXX no magics */
361 /* While here, basename() it */
362 while (i
> 0 && name
[i
- 1] != '/')
366 mtlp
->mtl_name
= name
;
367 mtlp
->mtl_nlen
= nlen
;
372 /* ..all the MIME types */
373 for (mtnp
= _mt_list
; mtnp
!= NULL
; mtnp
= mtnp
->mt_next
)
374 for (ext
= mtnp
->mt_line
+ mtnp
->mt_mtlen
;; ext
= cp
) {
376 while (whitechar(*cp
))
379 while (!whitechar(*cp
) && *cp
!= '\0')
382 if ((i
= PTR2SIZE(cp
- ext
)) == 0)
384 /* Don't allow neither of ".txt" or "txt" to match "txt" */
385 else if (i
+ 1 >= nlen
|| name
[(j
= nlen
- i
) - 1] != '.' ||
386 ascncasecmp(name
+ j
, ext
, i
))
390 mtlp
->mtl_node
= mtnp
;
395 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
399 name
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
403 mtlp
->mtl_result
= salloc(i
+ j
+1);
405 memcpy(mtlp
->mtl_result
, name
, j
);
406 memcpy(mtlp
->mtl_result
+ j
, mtnp
->mt_line
, i
);
407 mtlp
->mtl_result
[j
+= i
] = '\0';
417 static struct mtlookup
*
418 _mt_by_mtname(struct mtlookup
*mtlp
, char const *mtname
)
425 memset(mtlp
, 0, sizeof *mtlp
);
427 if ((mtlp
->mtl_nlen
= nlen
= strlen(mtlp
->mtl_name
= mtname
)) == 0)
433 /* ..all the MIME types */
434 for (mtnp
= _mt_list
; mtnp
!= NULL
; mtnp
= mtnp
->mt_next
) {
435 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
439 cp
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
444 if (i
+ j
== mtlp
->mtl_nlen
) {
445 char *xmt
= ac_alloc(i
+ j
+1);
448 memcpy(xmt
+ j
, mtnp
->mt_line
, i
);
450 i
= asccasecmp(mtname
, xmt
);
455 mtlp
->mtl_node
= mtnp
;
481 if (_mt_list
== NULL
) {
482 printf(_("*mimetypes-load-control*: no mime.types(5) available\n"));
486 if ((fp
= Ftmp(NULL
, "mimelist", OF_RDWR
| OF_UNLINK
| OF_REGISTER
, 0600))
488 n_perr(_("tmpfile"), 0);
493 for (l
= 0, mtnp
= _mt_list
; mtnp
!= NULL
; ++l
, mtnp
= mtnp
->mt_next
) {
494 char const *tmark
, *typ
;
496 switch (mtnp
->mt_flags
& __MT_MARKMASK
) {
497 case _MT_PLAIN
: tmark
= "/t"; break;
498 case _MT_SOUP_h
: tmark
= "/h"; break;
499 case _MT_SOUP_H
: tmark
= "/H"; break;
500 default: tmark
= " "; break;
502 typ
= ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
)
503 ? "" : _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
505 fprintf(fp
, "%c%s %s%.*s <%s>\n",
506 (mtnp
->mt_flags
& _MT_USR
? 'U'
507 : (mtnp
->mt_flags
& _MT_SYS
? 'S'
508 : (mtnp
->mt_flags
& _MT_LOADED
? 'F' : 'B'))),
509 tmark
, typ
, (int)mtnp
->mt_mtlen
, mtnp
->mt_line
,
510 mtnp
->mt_line
+ mtnp
->mt_mtlen
);
513 page_or_print(fp
, l
);
516 for (; *argv
!= NULL
; ++argv
) {
517 mtnp
= _mt_create(TRU1
, _MT_LOADED
, *argv
, strlen(*argv
));
519 mtnp
->mt_next
= _mt_list
;
527 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
531 c_unmimetype(void *v
)
534 struct mtnode
*lnp
, *mtnp
;
538 /* Need to load that first as necessary */
542 for (; *argv
!= NULL
; ++argv
) {
543 if (!asccasecmp(*argv
, "reset")) {
548 if (argv
[0][0] == '*' && argv
[0][1] == '\0') {
550 while ((mtnp
= _mt_list
) != NULL
) {
551 _mt_list
= mtnp
->mt_next
;
557 for (match
= FAL0
, lnp
= NULL
, mtnp
= _mt_list
; mtnp
!= NULL
;) {
562 if ((mtnp
->mt_flags
& __MT_TMASK
) == _MT_OTHER
) {
566 typ
= _mt_typnames
[mtnp
->mt_flags
& __MT_TMASK
];
570 val
= ac_alloc(i
+ mtnp
->mt_mtlen
+1);
572 memcpy(val
+ i
, mtnp
->mt_line
, mtnp
->mt_mtlen
);
573 val
[i
+= mtnp
->mt_mtlen
] = '\0';
574 i
= asccasecmp(val
, *argv
);
578 struct mtnode
*nnp
= mtnp
->mt_next
;
587 lnp
= mtnp
, mtnp
= mtnp
->mt_next
;
590 if (!(pstate
& PS_IN_LOAD
) || (options
& OPT_D_V
))
591 n_err(_("No such MIME type: \"%s\"\n"), *argv
);
596 return (v
== NULL
? !STOP
: !OKAY
); /* xxx 1:bad 0:good -- do some */
600 mime_type_by_filename(char const *name
)
605 _mt_by_filename(&mtl
, name
, TRU1
);
607 return mtl
.mtl_result
;
611 mime_type_file_classify(FILE *fp
, char const **contenttype
,
612 char const **charset
, int *do_iconv
)
614 /* TODO classify once only PLEASE PLEASE PLEASE */
615 /* TODO BTW., after the MIME/send layer rewrite we could use a MIME
616 * TODO boundary of "=-=-=" if we would add a B_ in EQ spirit to F_,
617 * TODO and report that state to the outer world */
619 #define F_SIZEOF (sizeof(F_) -1)
621 char f_buf
[F_SIZEOF
], *f_p
= f_buf
;
623 _CLEAN
= 0, /* Plain RFC 2822 message */
624 _NCTT
= 1<<0, /* *contenttype == NULL */
625 _ISTXT
= 1<<1, /* *contenttype =~ text/ */
626 _ISTXTCOK
= 1<<2, /* _ISTXT + *mime-allow-text-controls* */
627 _HIGHBIT
= 1<<3, /* Not 7bit clean */
628 _LONGLINES
= 1<<4, /* MIME_LINELEN_LIMIT exceed. */
629 _CTRLCHAR
= 1<<5, /* Control characters seen */
630 _HASNUL
= 1<<6, /* Contains \0 characters */
631 _NOTERMNL
= 1<<7, /* Lacks a final newline */
632 _FROM_
= 1<<8 /* ^From_ seen */
639 assert(ftell(fp
) == 0x0l
);
643 if (*contenttype
== NULL
)
645 else if (!ascncasecmp(*contenttype
, "text/", 5))
646 ctt
= ok_blook(mime_allow_text_controls
) ? _ISTXT
| _ISTXTCOK
: _ISTXT
;
648 menc
= mime_enc_target();
653 /* We have to inspect the file content */
654 for (curlen
= 0, c
= EOF
;; ++curlen
) {
660 if (!(ctt
& _ISTXTCOK
))
664 if (c
== '\n' || c
== EOF
) {
665 if (curlen
>= MIME_LINELEN_LIMIT
)
673 /* A bit hairy is handling of \r=\x0D=CR.
675 * Control characters other than TAB, or CR and LF as parts of CRLF
676 * pairs, must not appear. \r alone does not force _CTRLCHAR below since
677 * we cannot peek the next character. Thus right here, inspect the last
678 * seen character for if its \r and set _CTRLCHAR in a delayed fashion */
679 /*else*/ if (lastc
== '\r')
682 /* Control character? XXX this is all ASCII here */
683 if (c
< 0x20 || c
== 0x7F) {
684 /* RFC 2045, 6.7, as above ... */
685 if (c
!= '\t' && c
!= '\r')
687 /* If there is a escape sequence in backslash notation defined for
688 * this in ANSI X3.159-1989 (ANSI C89), don't treat it as a control
689 * for real. I.e., \a=\x07=BEL, \b=\x08=BS, \t=\x09=HT. Don't follow
690 * libmagic(1) in respect to \v=\x0B=VT. \f=\x0C=NP; do ignore
692 if ((c
>= '\x07' && c
<= '\x0D') || c
== '\x1B')
694 ctt
|= _HASNUL
; /* Force base64 */
695 if (!(ctt
& _ISTXTCOK
))
697 } else if ((ui8_t
)c
& 0x80) {
699 /* TODO count chars with HIGHBIT? libmagic?
700 * TODO try encode part - base64 if bails? */
701 if (!(ctt
& (_NCTT
| _ISTXT
))) { /* TODO _NCTT?? */
702 ctt
|= _HASNUL
; /* Force base64 */
705 } else if (!(ctt
& _FROM_
) && UICMP(z
, curlen
, <, F_SIZEOF
)) {
707 if (UICMP(z
, curlen
, ==, F_SIZEOF
- 1) &&
708 PTR2SIZE(f_p
- f_buf
) == F_SIZEOF
&&
709 !memcmp(f_buf
, F_
, F_SIZEOF
))
719 /* Don't overwrite a text content-type to allow UTF-16 and such, but only
720 * on request; else enforce what file(1)/libmagic(3) would suggest */
723 if (ctt
& (_NCTT
| _ISTXT
))
724 *contenttype
= "application/octet-stream";
725 if (*charset
== NULL
)
730 if (ctt
& (_LONGLINES
| _CTRLCHAR
| _NOTERMNL
| _FROM_
)) {
731 if (menc
!= MIMEE_B64
)
735 if (ctt
& _HIGHBIT
) {
737 if (ctt
& (_NCTT
| _ISTXT
))
738 *do_iconv
= ((ctt
& _HIGHBIT
) != 0);
743 *contenttype
= "text/plain";
745 /* Not an attachment with specified charset? */
747 if (*charset
== NULL
) /* TODO MIME/send: iter active? iter! else */
748 *charset
= (ctt
& _HIGHBIT
) ? charset_iter_or_fallback()
749 : charset_get_7bit();
752 /* TODO mime_type_file_classify() shouldn't return conversion */
753 return (menc
== MIMEE_7B
? CONV_7BIT
:
754 (menc
== MIMEE_8B
? CONV_8BIT
:
755 (menc
== MIMEE_QP
? CONV_TOQP
: CONV_TOB64
)));
762 mime_type_mimepart_content(struct mimepart
*mpp
)
767 union {char const *cp
; long l
;} mce
;
771 if ((ct
= mpp
->m_ct_type_plain
) == NULL
) /* TODO may not */
774 if ((mce
.cp
= ok_vlook(mime_counter_evidence
)) != NULL
) {
778 l
= strtol(mce
.cp
, &eptr
, 0); /* XXX strtol */
779 mce
.l
= (*mce
.cp
== '\0' || *eptr
!= '\0' || l
< 0) ? 0 : l
| MIMECE_SET
;
782 if (mce
.l
!= 0 && mpp
->m_filename
!= NULL
) {
783 bool_t is_os
= !asccasecmp(ct
, "application/octet-stream");
785 if (is_os
|| (mce
.l
& MIMECE_ALL_OVWR
)) {
786 if (_mt_by_filename(&mtl
, mpp
->m_filename
, TRU1
) == NULL
) {
788 if (mce
.l
& MIMECE_BIN_PARSE
) {
789 /* TODO Code MIMECE_BIN_PARSE (*mime-counter-evidence* bit 4)
790 * TODO This requires v15 framework since we must decode the
791 * TODO single mimepart mpp to a temporary file in order to
792 * TODO inspect the content! */
797 } else if (is_os
|| asccasecmp(ct
, mtl
.mtl_result
)) {
798 if (mce
.l
& MIMECE_ALL_OVWR
)
799 mpp
->m_ct_type_plain
= mtl
.mtl_result
;
800 if (mce
.l
& (MIMECE_BIN_OVWR
| MIMECE_ALL_OVWR
))
801 mpp
->m_ct_type_usr_ovwr
= mtl
.mtl_result
;
806 if (strchr(ct
, '/') == NULL
) /* For compatibility with non-MIME */
808 else if (is_asccaseprefix(ct
, "text/")) {
809 ct
+= sizeof("text/") -1;
810 if (!asccasecmp(ct
, "plain"))
811 mc
= MIME_TEXT_PLAIN
;
812 else if (!asccasecmp(ct
, "html"))
816 } else if (is_asccaseprefix(ct
, "message/")) {
817 ct
+= sizeof("message/") -1;
818 if (!asccasecmp(ct
, "rfc822"))
822 } else if (!ascncasecmp(ct
, "multipart/", 10)) {
823 ct
+= sizeof("multipart/") -1;
824 if (!asccasecmp(ct
, "alternative"))
825 mc
= MIME_ALTERNATIVE
;
826 else if (!asccasecmp(ct
, "related"))
828 else if (!asccasecmp(ct
, "digest"))
832 } else if (is_asccaseprefix(ct
, "application/")) {
833 ct
+= sizeof("application/") -1;
834 if (!asccasecmp(ct
, "pkcs7-mime") || !asccasecmp(ct
, "x-pkcs7-mime"))
843 mime_type_mimepart_handler(struct mimepart
const *mpp
)
846 #define __L (sizeof(__S) -1)
848 char const *es
, *cs
, *rv
;
853 el
= ((es
= mpp
->m_filename
) != NULL
&& (es
= strrchr(es
, '.')) != NULL
&&
854 *++es
!= '\0') ? strlen(es
) : 0;
855 cl
= ((cs
= mpp
->m_ct_type_usr_ovwr
) != NULL
||
856 (cs
= mpp
->m_ct_type_plain
) != NULL
) ? strlen(cs
) : 0;
857 if ((l
= MAX(el
, cl
)) == 0) {
859 /* FIXME here and below : another mime-counter-evidence bit, content check */
865 buf
= ac_alloc(__L
+ l
+1);
866 memcpy(buf
, __S
, __L
);
868 /* File-extension handlers take precedence.
869 * Yes, we really "fail" here for file extensions which clash MIME types */
871 memcpy(buf
+ __L
, es
, el
+1);
872 for (cp
= buf
+ __L
; *cp
!= '\0'; ++cp
)
873 *cp
= lowerconv(*cp
);
875 if ((rv
= vok_vlook(buf
)) != NULL
)
879 /* Then MIME Content-Type: */
881 memcpy(buf
+ __L
, cs
, cl
+1);
882 for (cp
= buf
+ __L
; *cp
!= '\0'; ++cp
)
883 *cp
= lowerconv(*cp
);
885 if ((rv
= vok_vlook(buf
)) != NULL
)
888 if (_mt_by_mtname(&mtl
, cs
) != NULL
)
889 switch (mtl
.mtl_node
->mt_flags
& __MT_MARKMASK
) {
890 #ifndef HAVE_FILTER_HTML_TAGSOUP
896 #ifdef HAVE_FILTER_HTML_TAGSOUP
898 rv
= MIME_TYPE_HANDLER_HTML
;
902 rv
= MIME_TYPE_HANDLER_TEXT
;