1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /*
18  * http_mime.c: Sends/gets MIME headers for requests
19  *
20  * Rob McCool
21  *
22  */
23 
24 #include "apr.h"
25 #include "apr_strings.h"
26 #include "apr_lib.h"
27 #include "apr_hash.h"
28 
29 #define APR_WANT_STRFUNC
30 #include "apr_want.h"
31 
32 #include "ap_config.h"
33 #include "httpd.h"
34 #include "http_config.h"
35 #include "http_log.h"
36 #include "http_request.h"
37 #include "http_protocol.h"
38 
39 /* XXXX - fix me / EBCDIC
40  *        there was a cludge here which would use its
41  *        own version apr_isascii(). Indicating that
42  *        on some platforms that might be needed.
43  *
44  *        #define OS_ASC(c) (c)             -- for mere mortals
45  *     or
46  *        #define OS_ASC(c) (ebcdic2ascii[c]) -- for dino's
47  *
48  *        #define apr_isascii(c) ((OS_ASC(c) & 0x80) == 0)
49  */
50 
51 /* XXXXX - fix me - See note with NOT_PROXY
52  */
53 
54 typedef struct attrib_info {
55     char *name;
56     int   offset;
57 } attrib_info;
58 
59 /* Information to which an extension can be mapped
60  */
61 typedef struct extension_info {
62     char *forced_type;                /* Additional AddTyped stuff */
63     char *encoding_type;              /* Added with AddEncoding... */
64     char *language_type;              /* Added with AddLanguage... */
65     char *handler;                    /* Added with AddHandler... */
66     char *charset_type;               /* Added with AddCharset... */
67     char *input_filters;              /* Added with AddInputFilter... */
68     char *output_filters;             /* Added with AddOutputFilter... */
69 } extension_info;
70 
71 #define MULTIMATCH_UNSET      0
72 #define MULTIMATCH_ANY        1
73 #define MULTIMATCH_NEGOTIATED 2
74 #define MULTIMATCH_HANDLERS   4
75 #define MULTIMATCH_FILTERS    8
76 
77 typedef struct {
78     apr_hash_t *extension_mappings;  /* Map from extension name to
79                                       * extension_info structure */
80 
81     apr_array_header_t *remove_mappings; /* A simple list, walked once */
82 
83     char *default_language;     /* Language if no AddLanguage ext found */
84 
85     int multimatch;       /* Extensions to include in multiview matching
86                            * for filenames, e.g. Filters and Handlers
87                            */
88     int use_path_info;    /* If set to 0, only use filename.
89                            * If set to 1, append PATH_INFO to filename for
90                            *   lookups.
91                            * If set to 2, this value is unset and is
92                            *   effectively 0.
93                            */
94 } mime_dir_config;
95 
96 typedef struct param_s {
97     char *attr;
98     char *val;
99     struct param_s *next;
100 } param;
101 
102 typedef struct {
103     const char *type;
104     apr_size_t type_len;
105     const char *subtype;
106     apr_size_t subtype_len;
107     param *param;
108 } content_type;
109 
110 static char tspecial[] = {
111     '(', ')', '<', '>', '@', ',', ';', ':',
112     '\\', '"', '/', '[', ']', '?', '=',
113     '\0'
114 };
115 
116 module AP_MODULE_DECLARE_DATA mime_module;
117 
create_mime_dir_config(apr_pool_t * p,char * dummy)118 static void *create_mime_dir_config(apr_pool_t *p, char *dummy)
119 {
120     mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config));
121 
122     new->extension_mappings = NULL;
123     new->remove_mappings = NULL;
124 
125     new->default_language = NULL;
126 
127     new->multimatch = MULTIMATCH_UNSET;
128 
129     new->use_path_info = 2;
130 
131     return new;
132 }
133 /*
134  * Overlay one hash table of extension_mappings onto another
135  */
overlay_extension_mappings(apr_pool_t * p,const void * key,apr_ssize_t klen,const void * overlay_val,const void * base_val,const void * data)136 static void *overlay_extension_mappings(apr_pool_t *p,
137                                         const void *key,
138                                         apr_ssize_t klen,
139                                         const void *overlay_val,
140                                         const void *base_val,
141                                         const void *data)
142 {
143     const extension_info *overlay_info = (const extension_info *)overlay_val;
144     const extension_info *base_info = (const extension_info *)base_val;
145     extension_info *new_info = apr_pmemdup(p, base_info, sizeof(extension_info));
146 
147     if (overlay_info->forced_type) {
148         new_info->forced_type = overlay_info->forced_type;
149     }
150     if (overlay_info->encoding_type) {
151         new_info->encoding_type = overlay_info->encoding_type;
152     }
153     if (overlay_info->language_type) {
154         new_info->language_type = overlay_info->language_type;
155     }
156     if (overlay_info->handler) {
157         new_info->handler = overlay_info->handler;
158     }
159     if (overlay_info->charset_type) {
160         new_info->charset_type = overlay_info->charset_type;
161     }
162     if (overlay_info->input_filters) {
163         new_info->input_filters = overlay_info->input_filters;
164     }
165     if (overlay_info->output_filters) {
166         new_info->output_filters = overlay_info->output_filters;
167     }
168 
169     return new_info;
170 }
171 
172 /* Member is the offset within an extension_info of the pointer to reset
173  */
remove_items(apr_pool_t * p,apr_array_header_t * remove,apr_hash_t * mappings)174 static void remove_items(apr_pool_t *p, apr_array_header_t *remove,
175                          apr_hash_t *mappings)
176 {
177     attrib_info *suffix = (attrib_info *) remove->elts;
178     int i;
179     for (i = 0; i < remove->nelts; i++) {
180         extension_info *exinfo = apr_hash_get(mappings,
181                                               suffix[i].name,
182                                               APR_HASH_KEY_STRING);
183         if (exinfo && *(const char**)((char *)exinfo + suffix[i].offset)) {
184             extension_info *copyinfo = exinfo;
185             exinfo = apr_pmemdup(p, copyinfo, sizeof(*exinfo));
186             apr_hash_set(mappings, suffix[i].name,
187                          APR_HASH_KEY_STRING, exinfo);
188 
189             *(const char**)((char *)exinfo + suffix[i].offset) = NULL;
190         }
191     }
192 }
193 
merge_mime_dir_configs(apr_pool_t * p,void * basev,void * addv)194 static void *merge_mime_dir_configs(apr_pool_t *p, void *basev, void *addv)
195 {
196     mime_dir_config *base = (mime_dir_config *)basev;
197     mime_dir_config *add = (mime_dir_config *)addv;
198     mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config));
199 
200     if (base->extension_mappings && add->extension_mappings) {
201         new->extension_mappings = apr_hash_merge(p, add->extension_mappings,
202                                                  base->extension_mappings,
203                                                  overlay_extension_mappings,
204                                                  NULL);
205     }
206     else {
207         if (base->extension_mappings == NULL) {
208             new->extension_mappings = add->extension_mappings;
209         }
210         else {
211             new->extension_mappings = base->extension_mappings;
212         }
213         /* We may not be merging the tables, but if we potentially will change
214          * an exinfo member, then we are about to trounce it anyways.
215          * We must have a copy for safety.
216          */
217         if (new->extension_mappings && add->remove_mappings) {
218             new->extension_mappings =
219                 apr_hash_copy(p, new->extension_mappings);
220         }
221     }
222 
223     if (new->extension_mappings) {
224         if (add->remove_mappings)
225             remove_items(p, add->remove_mappings, new->extension_mappings);
226     }
227     new->remove_mappings = NULL;
228 
229     new->default_language = add->default_language ?
230         add->default_language : base->default_language;
231 
232     new->multimatch = (add->multimatch != MULTIMATCH_UNSET) ?
233         add->multimatch : base->multimatch;
234 
235     if ((add->use_path_info & 2) == 0) {
236         new->use_path_info = add->use_path_info;
237     }
238     else {
239         new->use_path_info = base->use_path_info;
240     }
241 
242     return new;
243 }
244 
add_extension_info(cmd_parms * cmd,void * m_,const char * value_,const char * ext)245 static const char *add_extension_info(cmd_parms *cmd, void *m_,
246                                       const char *value_, const char* ext)
247 {
248     mime_dir_config *m=m_;
249     extension_info *exinfo;
250     int offset = (int) (long) cmd->info;
251     char *key = apr_pstrdup(cmd->temp_pool, ext);
252     char *value = apr_pstrdup(cmd->pool, value_);
253     ap_str_tolower(value);
254     ap_str_tolower(key);
255 
256     if (*key == '.') {
257         ++key;
258     }
259     if (!m->extension_mappings) {
260         m->extension_mappings = apr_hash_make(cmd->pool);
261         exinfo = NULL;
262     }
263     else {
264         exinfo = (extension_info*)apr_hash_get(m->extension_mappings, key,
265                                                APR_HASH_KEY_STRING);
266     }
267     if (!exinfo) {
268         exinfo = apr_pcalloc(cmd->pool, sizeof(extension_info));
269         key = apr_pstrdup(cmd->pool, key);
270         apr_hash_set(m->extension_mappings, key, APR_HASH_KEY_STRING, exinfo);
271     }
272     *(const char**)((char *)exinfo + offset) = value;
273     return NULL;
274 }
275 
276 /*
277  * As RemoveType should also override the info from TypesConfig, we add an
278  * empty string as type instead of actually removing the type.
279  */
remove_extension_type(cmd_parms * cmd,void * m_,const char * ext)280 static const char *remove_extension_type(cmd_parms *cmd, void *m_,
281                                          const char *ext)
282 {
283     return add_extension_info(cmd, m_, "", ext);
284 }
285 
286 /*
287  * Note handler names are un-added with each per_dir_config merge.
288  * This keeps the association from being inherited, but not
289  * from being re-added at a subordinate level.
290  */
remove_extension_info(cmd_parms * cmd,void * m_,const char * ext)291 static const char *remove_extension_info(cmd_parms *cmd, void *m_,
292                                          const char *ext)
293 {
294     mime_dir_config *m = (mime_dir_config *) m_;
295     attrib_info *suffix;
296     if (*ext == '.') {
297         ++ext;
298     }
299     if (!m->remove_mappings) {
300         m->remove_mappings = apr_array_make(cmd->pool, 4, sizeof(*suffix));
301     }
302     suffix = (attrib_info *)apr_array_push(m->remove_mappings);
303     suffix->name = apr_pstrdup(cmd->pool, ext);
304     ap_str_tolower(suffix->name);
305     suffix->offset = (int) (long) cmd->info;
306     return NULL;
307 }
308 
309 /* The sole bit of server configuration that the MIME module has is
310  * the name of its config file, so...
311  */
312 
set_types_config(cmd_parms * cmd,void * dummy,const char * arg)313 static const char *set_types_config(cmd_parms *cmd, void *dummy,
314                                     const char *arg)
315 {
316     ap_set_module_config(cmd->server->module_config, &mime_module,
317                          (void *)arg);
318     return NULL;
319 }
320 
multiviews_match(cmd_parms * cmd,void * m_,const char * include)321 static const char *multiviews_match(cmd_parms *cmd, void *m_,
322                                     const char *include)
323 {
324     mime_dir_config *m = (mime_dir_config *) m_;
325     const char *errmsg;
326 
327     errmsg = ap_check_cmd_context(cmd, NOT_IN_LOCATION);
328     if (errmsg != NULL) {
329         return errmsg;
330     }
331 
332     if (strcasecmp(include, "Any") == 0) {
333         if (m->multimatch && (m->multimatch & ~MULTIMATCH_ANY)) {
334             return "Any is incompatible with NegotiatedOnly, "
335                    "Filters and Handlers";
336         }
337         m->multimatch |= MULTIMATCH_ANY;
338     }
339     else if (strcasecmp(include, "NegotiatedOnly") == 0) {
340         if (m->multimatch && (m->multimatch & ~MULTIMATCH_NEGOTIATED)) {
341             return "NegotiatedOnly is incompatible with Any, "
342                    "Filters and Handlers";
343         }
344         m->multimatch |= MULTIMATCH_NEGOTIATED;
345     }
346     else if (strcasecmp(include, "Filters") == 0) {
347         if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED
348                                              | MULTIMATCH_ANY))) {
349             return "Filters is incompatible with Any and NegotiatedOnly";
350         }
351         m->multimatch |= MULTIMATCH_FILTERS;
352     }
353     else if (strcasecmp(include, "Handlers") == 0) {
354         if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED
355                                              | MULTIMATCH_ANY))) {
356             return "Handlers is incompatible with Any and NegotiatedOnly";
357         }
358         m->multimatch |= MULTIMATCH_HANDLERS;
359     }
360     else {
361         return apr_psprintf(cmd->pool, "Unrecognized option '%s'", include);
362     }
363 
364     return NULL;
365 }
366 
367 static const command_rec mime_cmds[] =
368 {
369     AP_INIT_ITERATE2("AddCharset", add_extension_info,
370         (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO,
371         "a charset (e.g., iso-2022-jp), followed by one or more "
372         "file extensions"),
373     AP_INIT_ITERATE2("AddEncoding", add_extension_info,
374         (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO,
375         "an encoding (e.g., gzip), followed by one or more file extensions"),
376     AP_INIT_ITERATE2("AddHandler", add_extension_info,
377         (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO,
378         "a handler name followed by one or more file extensions"),
379     AP_INIT_ITERATE2("AddInputFilter", add_extension_info,
380         (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO,
381         "input filter name (or ; delimited names) followed by one or "
382         "more file extensions"),
383     AP_INIT_ITERATE2("AddLanguage", add_extension_info,
384         (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO,
385         "a language (e.g., fr), followed by one or more file extensions"),
386     AP_INIT_ITERATE2("AddOutputFilter", add_extension_info,
387         (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO,
388         "output filter name (or ; delimited names) followed by one or "
389         "more file extensions"),
390     AP_INIT_ITERATE2("AddType", add_extension_info,
391         (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO,
392         "a mime type followed by one or more file extensions"),
393     AP_INIT_TAKE1("DefaultLanguage", ap_set_string_slot,
394         (void*)APR_OFFSETOF(mime_dir_config, default_language), OR_FILEINFO,
395         "language to use for documents with no other language file extension"),
396     AP_INIT_ITERATE("MultiviewsMatch", multiviews_match, NULL, OR_FILEINFO,
397         "NegotiatedOnly (default), Handlers and/or Filters, or Any"),
398     AP_INIT_ITERATE("RemoveCharset", remove_extension_info,
399         (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO,
400         "one or more file extensions"),
401     AP_INIT_ITERATE("RemoveEncoding", remove_extension_info,
402         (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO,
403         "one or more file extensions"),
404     AP_INIT_ITERATE("RemoveHandler", remove_extension_info,
405         (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO,
406         "one or more file extensions"),
407     AP_INIT_ITERATE("RemoveInputFilter", remove_extension_info,
408         (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO,
409         "one or more file extensions"),
410     AP_INIT_ITERATE("RemoveLanguage", remove_extension_info,
411         (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO,
412         "one or more file extensions"),
413     AP_INIT_ITERATE("RemoveOutputFilter", remove_extension_info,
414         (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO,
415         "one or more file extensions"),
416     AP_INIT_ITERATE("RemoveType", remove_extension_type,
417         (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO,
418         "one or more file extensions"),
419     AP_INIT_TAKE1("TypesConfig", set_types_config, NULL, RSRC_CONF,
420         "the MIME types config file"),
421     AP_INIT_FLAG("ModMimeUsePathInfo", ap_set_flag_slot,
422         (void *)APR_OFFSETOF(mime_dir_config, use_path_info), ACCESS_CONF,
423         "Set to 'yes' to allow mod_mime to use path info for type checking"),
424     {NULL}
425 };
426 
427 static apr_hash_t *mime_type_extensions;
428 
mime_post_config(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * s)429 static int mime_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s)
430 {
431     ap_configfile_t *f;
432     char l[MAX_STRING_LEN];
433     const char *types_confname = ap_get_module_config(s->module_config,
434                                                       &mime_module);
435     apr_status_t status;
436 
437     if (!types_confname) {
438         types_confname = AP_TYPES_CONFIG_FILE;
439     }
440 
441     types_confname = ap_server_root_relative(p, types_confname);
442     if (!types_confname) {
443         ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, APLOGNO(01596)
444                      "Invalid mime types config path %s",
445                      (const char *)ap_get_module_config(s->module_config,
446                                                         &mime_module));
447         return HTTP_INTERNAL_SERVER_ERROR;
448     }
449     if ((status = ap_pcfg_openfile(&f, ptemp, types_confname))
450                 != APR_SUCCESS) {
451         ap_log_error(APLOG_MARK, APLOG_ERR, status, s, APLOGNO(01597)
452                      "could not open mime types config file %s.",
453                      types_confname);
454         return HTTP_INTERNAL_SERVER_ERROR;
455     }
456 
457     mime_type_extensions = apr_hash_make(p);
458 
459     while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) {
460         const char *ll = l, *ct;
461 
462         if (l[0] == '#') {
463             continue;
464         }
465         ct = ap_getword_conf(p, &ll);
466 
467         while (ll[0]) {
468             char *ext = ap_getword_conf(p, &ll);
469             ap_str_tolower(ext);
470             apr_hash_set(mime_type_extensions, ext, APR_HASH_KEY_STRING, ct);
471         }
472     }
473     ap_cfg_closefile(f);
474     return OK;
475 }
476 
zap_sp(const char * s)477 static const char *zap_sp(const char *s)
478 {
479     if (s == NULL) {
480         return (NULL);
481     }
482     if (*s == '\0') {
483         return (s);
484     }
485 
486     /* skip prefixed white space */
487     for (; *s == ' ' || *s == '\t' || *s == '\n'; s++)
488         ;
489 
490     return (s);
491 }
492 
zap_sp_and_dup(apr_pool_t * p,const char * start,const char * end,apr_size_t * len)493 static char *zap_sp_and_dup(apr_pool_t *p, const char *start,
494                             const char *end, apr_size_t *len)
495 {
496     while ((start < end) && apr_isspace(*start)) {
497         start++;
498     }
499     while ((end > start) && apr_isspace(*(end - 1))) {
500         end--;
501     }
502     if (len) {
503         *len = end - start;
504     }
505     return apr_pstrmemdup(p, start, end - start);
506 }
507 
is_token(char c)508 static int is_token(char c)
509 {
510     int res;
511 
512     res = (apr_isascii(c) && apr_isgraph(c)
513            && (strchr(tspecial, c) == NULL)) ? 1 : -1;
514     return res;
515 }
516 
is_qtext(char c)517 static int is_qtext(char c)
518 {
519     int res;
520 
521     res = (apr_isascii(c) && (c != '"') && (c != '\\') && (c != '\n'))
522         ? 1 : -1;
523     return res;
524 }
525 
is_quoted_pair(const char * s)526 static int is_quoted_pair(const char *s)
527 {
528     int res = -1;
529     int c;
530 
531     if (((s + 1) != NULL) && (*s == '\\')) {
532         c = (int) *(s + 1);
533         if (apr_isascii(c)) {
534             res = 1;
535         }
536     }
537     return (res);
538 }
539 
analyze_ct(request_rec * r,const char * s)540 static content_type *analyze_ct(request_rec *r, const char *s)
541 {
542     const char *cp, *mp;
543     char *attribute, *value;
544     int quoted = 0;
545     server_rec * ss = r->server;
546     apr_pool_t * p = r->pool;
547 
548     content_type *ctp;
549     param *pp, *npp;
550 
551     /* initialize ctp */
552     ctp = (content_type *)apr_palloc(p, sizeof(content_type));
553     ctp->type = NULL;
554     ctp->subtype = NULL;
555     ctp->param = NULL;
556 
557     mp = s;
558 
559     /* getting a type */
560     cp = mp;
561     while (apr_isspace(*cp)) {
562         cp++;
563     }
564     if (!*cp) {
565         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01598)
566                      "mod_mime: analyze_ct: cannot get media type from '%s'",
567                      (const char *) mp);
568         return (NULL);
569     }
570     ctp->type = cp;
571     do {
572         cp++;
573     } while (*cp && (*cp != '/') && !apr_isspace(*cp) && (*cp != ';'));
574     if (!*cp || (*cp == ';')) {
575         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01599)
576                      "Cannot get media type from '%s'",
577                      (const char *) mp);
578         return (NULL);
579     }
580     while (apr_isspace(*cp)) {
581         cp++;
582     }
583     if (*cp != '/') {
584         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01600)
585                      "mod_mime: analyze_ct: cannot get media type from '%s'",
586                      (const char *) mp);
587         return (NULL);
588     }
589     ctp->type_len = cp - ctp->type;
590 
591     cp++; /* skip the '/' */
592 
593     /* getting a subtype */
594     while (apr_isspace(*cp)) {
595         cp++;
596     }
597     if (!*cp) {
598         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01601)
599                      "Cannot get media subtype.");
600         return (NULL);
601     }
602     ctp->subtype = cp;
603     do {
604         cp++;
605     } while (*cp && !apr_isspace(*cp) && (*cp != ';'));
606     ctp->subtype_len = cp - ctp->subtype;
607     while (apr_isspace(*cp)) {
608         cp++;
609     }
610 
611     if (*cp == '\0') {
612         return (ctp);
613     }
614 
615     /* getting parameters */
616     cp++; /* skip the ';' */
617     cp = zap_sp(cp);
618     if (cp == NULL || *cp == '\0') {
619         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01602)
620                      "Cannot get media parameter.");
621         return (NULL);
622     }
623     mp = cp;
624     attribute = NULL;
625     value = NULL;
626 
627     while (cp != NULL && *cp != '\0') {
628         if (attribute == NULL) {
629             if (is_token(*cp) > 0) {
630                 cp++;
631                 continue;
632             }
633             else if (*cp == ' ' || *cp == '\t' || *cp == '\n') {
634                 cp++;
635                 continue;
636             }
637             else if (*cp == '=') {
638                 attribute = zap_sp_and_dup(p, mp, cp, NULL);
639                 if (attribute == NULL || *attribute == '\0') {
640                     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01603)
641                                  "Cannot get media parameter.");
642                     return (NULL);
643                 }
644                 cp++;
645                 cp = zap_sp(cp);
646                 if (cp == NULL || *cp == '\0') {
647                     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01604)
648                                  "Cannot get media parameter.");
649                     return (NULL);
650                 }
651                 mp = cp;
652                 continue;
653             }
654             else {
655                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01605)
656                              "Cannot get media parameter.");
657                 return (NULL);
658             }
659         }
660         else {
661             if (mp == cp) {
662                 if (*cp == '"') {
663                     quoted = 1;
664                     cp++;
665                 }
666                 else {
667                     quoted = 0;
668                 }
669             }
670             if (quoted > 0) {
671                 while (quoted && *cp != '\0') {
672                     if (is_qtext(*cp) > 0) {
673                         cp++;
674                     }
675                     else if (is_quoted_pair(cp) > 0) {
676                         cp += 2;
677                     }
678                     else if (*cp == '"') {
679                         cp++;
680                         while (*cp == ' ' || *cp == '\t' || *cp == '\n') {
681                             cp++;
682                         }
683                         if (*cp != ';' && *cp != '\0') {
684                             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01606)
685                                          "Cannot get media parameter.");
686                             return(NULL);
687                         }
688                         quoted = 0;
689                     }
690                     else {
691                         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01607)
692                                      "Cannot get media parameter.");
693                         return (NULL);
694                     }
695                 }
696             }
697             else {
698                 while (1) {
699                     if (is_token(*cp) > 0) {
700                         cp++;
701                     }
702                     else if (*cp == '\0' || *cp == ';') {
703                         break;
704                     }
705                     else {
706                         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01608)
707                                      "Cannot get media parameter.");
708                         return (NULL);
709                     }
710                 }
711             }
712             value = zap_sp_and_dup(p, mp, cp, NULL);
713             if (value == NULL || *value == '\0') {
714                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01609)
715                              "Cannot get media parameter.");
716                 return (NULL);
717             }
718 
719             pp = apr_palloc(p, sizeof(param));
720             pp->attr = attribute;
721             pp->val = value;
722             pp->next = NULL;
723 
724             if (ctp->param == NULL) {
725                 ctp->param = pp;
726             }
727             else {
728                 npp = ctp->param;
729                 while (npp->next) {
730                     npp = npp->next;
731                 }
732                 npp->next = pp;
733             }
734             quoted = 0;
735             attribute = NULL;
736             value = NULL;
737             if (*cp == '\0') {
738                 break;
739             }
740             cp++;
741             mp = cp;
742         }
743     }
744     return (ctp);
745 }
746 
747 /*
748  * find_ct is the hook routine for determining content-type and other
749  * MIME-related metadata.  It assumes that r->filename has already been
750  * set and stat has been called for r->finfo.  It also assumes that the
751  * non-path base file name is not the empty string unless it is a dir.
752  */
find_ct(request_rec * r)753 static int find_ct(request_rec *r)
754 {
755     mime_dir_config *conf;
756     apr_array_header_t *exception_list;
757     char *ext;
758     const char *fn, *fntmp, *type, *charset = NULL, *resource_name;
759     int found_metadata = 0;
760 
761     if (r->finfo.filetype == APR_DIR) {
762         ap_set_content_type(r, DIR_MAGIC_TYPE);
763         return OK;
764     }
765 
766     if (!r->filename) {
767         return DECLINED;
768     }
769 
770     conf = (mime_dir_config *)ap_get_module_config(r->per_dir_config,
771                                                    &mime_module);
772     exception_list = apr_array_make(r->pool, 2, sizeof(char *));
773 
774     /* If use_path_info is explicitly set to on (value & 1 == 1), append. */
775     if (conf->use_path_info & 1) {
776         resource_name = apr_pstrcat(r->pool, r->filename, r->path_info, NULL);
777     }
778     else {
779         resource_name = r->filename;
780     }
781 
782     /* Always drop the path leading up to the file name.
783      */
784     if ((fn = ap_strrchr_c(resource_name, '/')) == NULL) {
785         fn = resource_name;
786     }
787     else {
788         ++fn;
789     }
790 
791 
792     /* The exception list keeps track of those filename components that
793      * are not associated with extensions indicating metadata.
794      * The base name is always the first exception (i.e., "txt.html" has
795      * a basename of "txt" even though it might look like an extension).
796      * Leading dots are considered to be part of the base name (a file named
797      * ".png" is likely not a png file but just a hidden file called png).
798      */
799     fntmp = fn;
800     while (*fntmp == '.')
801         fntmp++;
802     fntmp = ap_strchr_c(fntmp, '.');
803     if (fntmp) {
804         ext = apr_pstrmemdup(r->pool, fn, fntmp - fn);
805         fn = fntmp + 1;
806     }
807     else {
808         ext = apr_pstrdup(r->pool, fn);
809         fn += strlen(fn);
810     }
811 
812     *((const char **)apr_array_push(exception_list)) = ext;
813 
814     /* Parse filename extensions which can be in any order
815      */
816     while (*fn && (ext = ap_getword(r->pool, &fn, '.'))) {
817         const extension_info *exinfo = NULL;
818         int found;
819         char *extcase;
820 
821         if (*ext == '\0') {  /* ignore empty extensions "bad..html" */
822             continue;
823         }
824 
825         found = 0;
826 
827         /* Save the ext in extcase before converting it to lower case.
828          */
829         extcase = apr_pstrdup(r->pool, ext);
830         ap_str_tolower(ext);
831 
832         if (conf->extension_mappings != NULL) {
833             exinfo = (extension_info*)apr_hash_get(conf->extension_mappings,
834                                                    ext, APR_HASH_KEY_STRING);
835         }
836 
837         if (exinfo == NULL || !exinfo->forced_type) {
838             if ((type = apr_hash_get(mime_type_extensions, ext,
839                                      APR_HASH_KEY_STRING)) != NULL) {
840                 ap_set_content_type(r, (char*) type);
841                 found = 1;
842             }
843         }
844 
845         if (exinfo != NULL) {
846 
847             /* empty string is treated as special case for RemoveType */
848             if (exinfo->forced_type && *exinfo->forced_type) {
849                 ap_set_content_type(r, exinfo->forced_type);
850                 found = 1;
851             }
852 
853             if (exinfo->charset_type) {
854                 charset = exinfo->charset_type;
855                 found = 1;
856             }
857             if (exinfo->language_type) {
858                 if (!r->content_languages) {
859                     r->content_languages = apr_array_make(r->pool, 2,
860                                                           sizeof(char *));
861                 }
862                 *((const char **)apr_array_push(r->content_languages))
863                     = exinfo->language_type;
864                 found = 1;
865             }
866             if (exinfo->encoding_type) {
867                 if (!r->content_encoding) {
868                     r->content_encoding = exinfo->encoding_type;
869                 }
870                 else {
871                     /* XXX should eliminate duplicate entities
872                      *
873                      * ah no. Order is important and double encoding is neither
874                      * forbidden nor impossible. -- nd
875                      */
876                     r->content_encoding = apr_pstrcat(r->pool,
877                                                       r->content_encoding,
878                                                       ", ",
879                                                       exinfo->encoding_type,
880                                                       NULL);
881                 }
882                 found = 1;
883             }
884             /* The following extensions are not 'Found'.  That is, they don't
885              * make any contribution to metadata negotiation, so they must have
886              * been explicitly requested by name.
887              */
888             if (exinfo->handler && r->proxyreq == PROXYREQ_NONE) {
889                 r->handler = exinfo->handler;
890                 if (conf->multimatch & MULTIMATCH_HANDLERS) {
891                     found = 1;
892                 }
893             }
894             /* XXX Two significant problems; 1, we don't check to see if we are
895              * setting redundant filters.    2, we insert these in the types
896              * config hook, which may be too early (dunno.)
897              */
898             if (exinfo->input_filters) {
899                 const char *filter, *filters = exinfo->input_filters;
900                 while (*filters
901                     && (filter = ap_getword(r->pool, &filters, ';'))) {
902                     ap_add_input_filter(filter, NULL, r, r->connection);
903                 }
904                 if (conf->multimatch & MULTIMATCH_FILTERS) {
905                     found = 1;
906                 }
907             }
908             if (exinfo->output_filters) {
909                 const char *filter, *filters = exinfo->output_filters;
910                 while (*filters
911                     && (filter = ap_getword(r->pool, &filters, ';'))) {
912                     ap_add_output_filter(filter, NULL, r, r->connection);
913                 }
914                 if (conf->multimatch & MULTIMATCH_FILTERS) {
915                     found = 1;
916                 }
917             }
918         }
919 
920         if (found || (conf->multimatch & MULTIMATCH_ANY)) {
921             found_metadata = 1;
922         }
923         else {
924             *((const char **) apr_array_push(exception_list)) = extcase;
925         }
926     }
927 
928     /*
929      * Need to set a notes entry on r for unrecognized elements.
930      * Somebody better claim them!  If we did absolutely nothing,
931      * skip the notes to alert mod_negotiation we are clueless.
932      */
933     if (found_metadata) {
934         apr_table_setn(r->notes, "ap-mime-exceptions-list",
935                        (void *)exception_list);
936     }
937 
938     if (r->content_type) {
939         content_type *ctp;
940         int override = 0;
941 
942         if ((ctp = analyze_ct(r, r->content_type))) {
943             param *pp = ctp->param;
944             char *base_content_type = apr_palloc(r->pool, ctp->type_len +
945                                                  ctp->subtype_len +
946                                                  sizeof("/"));
947             char *tmp = base_content_type;
948             memcpy(tmp, ctp->type, ctp->type_len);
949             tmp += ctp->type_len;
950             *tmp++ = '/';
951             memcpy(tmp, ctp->subtype, ctp->subtype_len);
952             tmp += ctp->subtype_len;
953             *tmp = 0;
954             ap_set_content_type(r, base_content_type);
955             while (pp != NULL) {
956                 if (charset && !strcmp(pp->attr, "charset")) {
957                     if (!override) {
958                         ap_set_content_type(r,
959                                             apr_pstrcat(r->pool,
960                                                         r->content_type,
961                                                         "; charset=",
962                                                         charset,
963                                                         NULL));
964                         override = 1;
965                     }
966                 }
967                 else {
968                     ap_set_content_type(r,
969                                         apr_pstrcat(r->pool,
970                                                     r->content_type,
971                                                     "; ", pp->attr,
972                                                     "=", pp->val,
973                                                     NULL));
974                 }
975                 pp = pp->next;
976             }
977             if (charset && !override) {
978                 ap_set_content_type(r, apr_pstrcat(r->pool, r->content_type,
979                                                    "; charset=", charset,
980                                                    NULL));
981             }
982         }
983     }
984 
985     /* Set default language, if none was specified by the extensions
986      * and we have a DefaultLanguage setting in force
987      */
988 
989     if (!r->content_languages && conf->default_language) {
990         const char **new;
991 
992         if (!r->content_languages) {
993             r->content_languages = apr_array_make(r->pool, 2, sizeof(char *));
994         }
995         new = (const char **)apr_array_push(r->content_languages);
996         *new = conf->default_language;
997     }
998 
999     if (!r->content_type) {
1000         return DECLINED;
1001     }
1002 
1003     return OK;
1004 }
1005 
register_hooks(apr_pool_t * p)1006 static void register_hooks(apr_pool_t *p)
1007 {
1008     ap_hook_post_config(mime_post_config,NULL,NULL,APR_HOOK_MIDDLE);
1009     ap_hook_type_checker(find_ct,NULL,NULL,APR_HOOK_MIDDLE);
1010     /*
1011      * this hook seems redundant ... is there any reason a type checker isn't
1012      * allowed to do this already?  I'd think that fixups in general would be
1013      * the last opportunity to get the filters right.
1014      * ap_hook_insert_filter(mime_insert_filters,NULL,NULL,APR_HOOK_MIDDLE);
1015      */
1016 }
1017 
1018 AP_DECLARE_MODULE(mime) = {
1019     STANDARD20_MODULE_STUFF,
1020     create_mime_dir_config,     /* create per-directory config structure */
1021     merge_mime_dir_configs,     /* merge per-directory config structures */
1022     NULL,                       /* create per-server config structure */
1023     NULL,                       /* merge per-server config structures */
1024     mime_cmds,                  /* command apr_table_t */
1025     register_hooks              /* register hooks */
1026 };
1027