| /* Copyright 1999-2005 The Apache Software Foundation or its licensors, as | 
 |  * applicable. | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *     http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | /* | 
 |  * http_mime.c: Sends/gets MIME headers for requests | 
 |  * | 
 |  * Rob McCool | 
 |  * | 
 |  */ | 
 |  | 
 | #include "apr.h" | 
 | #include "apr_strings.h" | 
 | #include "apr_lib.h" | 
 | #include "apr_hash.h" | 
 |  | 
 | #define APR_WANT_STRFUNC | 
 | #include "apr_want.h" | 
 |  | 
 | #include "ap_config.h" | 
 | #include "httpd.h" | 
 | #include "http_config.h" | 
 | #include "http_log.h" | 
 | #include "http_request.h" | 
 | #include "http_protocol.h" | 
 |  | 
 | /* XXXX - fix me / EBCDIC | 
 |  *        there was a cludge here which would use its | 
 |  *        own version apr_isascii(). Indicating that | 
 |  *        on some platforms that might be needed. | 
 |  * | 
 |  *        #define OS_ASC(c) (c)             -- for mere mortals | 
 |  *     or | 
 |  *        #define OS_ASC(c) (ebcdic2ascii[c]) -- for dino's | 
 |  * | 
 |  *        #define apr_isascii(c) ((OS_ASC(c) & 0x80) == 0) | 
 |  */ | 
 |  | 
 | /* XXXXX - fix me - See note with NOT_PROXY | 
 |  */ | 
 |  | 
 | typedef struct attrib_info { | 
 |     char *name; | 
 |     int   offset; | 
 | } attrib_info; | 
 |  | 
 | /* Information to which an extension can be mapped | 
 |  */ | 
 | typedef struct extension_info { | 
 |     char *forced_type;                /* Additional AddTyped stuff */ | 
 |     char *encoding_type;              /* Added with AddEncoding... */ | 
 |     char *language_type;              /* Added with AddLanguage... */ | 
 |     char *handler;                    /* Added with AddHandler... */ | 
 |     char *charset_type;               /* Added with AddCharset... */ | 
 |     char *input_filters;              /* Added with AddInputFilter... */ | 
 |     char *output_filters;             /* Added with AddOutputFilter... */ | 
 | } extension_info; | 
 |  | 
 | #define MULTIMATCH_UNSET      0 | 
 | #define MULTIMATCH_ANY        1 | 
 | #define MULTIMATCH_NEGOTIATED 2 | 
 | #define MULTIMATCH_HANDLERS   4 | 
 | #define MULTIMATCH_FILTERS    8 | 
 |  | 
 | typedef struct { | 
 |     apr_hash_t *extension_mappings;  /* Map from extension name to | 
 |                                       * extension_info structure */ | 
 |  | 
 |     apr_array_header_t *remove_mappings; /* A simple list, walked once */ | 
 |  | 
 |     char *default_language;     /* Language if no AddLanguage ext found */ | 
 |  | 
 |     int multimatch;       /* Extensions to include in multiview matching | 
 |                            * for filenames, e.g. Filters and Handlers | 
 |                            */ | 
 |     int use_path_info;    /* If set to 0, only use filename. | 
 |                            * If set to 1, append PATH_INFO to filename for | 
 |                            *   lookups. | 
 |                            * If set to 2, this value is unset and is | 
 |                            *   effectively 0. | 
 |                            */ | 
 | } mime_dir_config; | 
 |  | 
 | typedef struct param_s { | 
 |     char *attr; | 
 |     char *val; | 
 |     struct param_s *next; | 
 | } param; | 
 |  | 
 | typedef struct { | 
 |     const char *type; | 
 |     apr_size_t type_len; | 
 |     const char *subtype; | 
 |     apr_size_t subtype_len; | 
 |     param *param; | 
 | } content_type; | 
 |  | 
 | static char tspecial[] = { | 
 |     '(', ')', '<', '>', '@', ',', ';', ':', | 
 |     '\\', '"', '/', '[', ']', '?', '=', | 
 |     '\0' | 
 | }; | 
 |  | 
 | module AP_MODULE_DECLARE_DATA mime_module; | 
 |  | 
 | static void *create_mime_dir_config(apr_pool_t *p, char *dummy) | 
 | { | 
 |     mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config)); | 
 |  | 
 |     new->extension_mappings = NULL; | 
 |     new->remove_mappings = NULL; | 
 |  | 
 |     new->default_language = NULL; | 
 |  | 
 |     new->multimatch = MULTIMATCH_UNSET; | 
 |  | 
 |     new->use_path_info = 2; | 
 |  | 
 |     return new; | 
 | } | 
 | /* | 
 |  * Overlay one hash table of extension_mappings onto another | 
 |  */ | 
 | static void *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) | 
 | { | 
 |     extension_info *new_info = apr_palloc(p, sizeof(extension_info)); | 
 |     const extension_info *overlay_info = (const extension_info *)overlay_val; | 
 |     const extension_info *base_info = (const extension_info *)base_val; | 
 |  | 
 |     memcpy(new_info, base_info, sizeof(extension_info)); | 
 |     if (overlay_info->forced_type) { | 
 |         new_info->forced_type = overlay_info->forced_type; | 
 |     } | 
 |     if (overlay_info->encoding_type) { | 
 |         new_info->encoding_type = overlay_info->encoding_type; | 
 |     } | 
 |     if (overlay_info->language_type) { | 
 |         new_info->language_type = overlay_info->language_type; | 
 |     } | 
 |     if (overlay_info->handler) { | 
 |         new_info->handler = overlay_info->handler; | 
 |     } | 
 |     if (overlay_info->charset_type) { | 
 |         new_info->charset_type = overlay_info->charset_type; | 
 |     } | 
 |     if (overlay_info->input_filters) { | 
 |         new_info->input_filters = overlay_info->input_filters; | 
 |     } | 
 |     if (overlay_info->output_filters) { | 
 |         new_info->output_filters = overlay_info->output_filters; | 
 |     } | 
 |  | 
 |     return new_info; | 
 | } | 
 |  | 
 | /* Member is the offset within an extension_info of the pointer to reset | 
 |  */ | 
 | static void remove_items(apr_pool_t *p, apr_array_header_t *remove, | 
 |                          apr_hash_t *mappings) | 
 | { | 
 |     attrib_info *suffix = (attrib_info *) remove->elts; | 
 |     int i; | 
 |     for (i = 0; i < remove->nelts; i++) { | 
 |         extension_info *exinfo = apr_hash_get(mappings, | 
 |                                               suffix[i].name, | 
 |                                               APR_HASH_KEY_STRING); | 
 |         if (exinfo && *(const char**)((char *)exinfo + suffix[i].offset)) { | 
 |             extension_info *copyinfo = exinfo; | 
 |             exinfo = (extension_info*)apr_palloc(p, sizeof(*exinfo)); | 
 |             apr_hash_set(mappings, suffix[i].name, | 
 |                          APR_HASH_KEY_STRING, exinfo); | 
 |             memcpy(exinfo, copyinfo, sizeof(*exinfo)); | 
 |             *(const char**)((char *)exinfo + suffix[i].offset) = NULL; | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | static void *merge_mime_dir_configs(apr_pool_t *p, void *basev, void *addv) | 
 | { | 
 |     mime_dir_config *base = (mime_dir_config *)basev; | 
 |     mime_dir_config *add = (mime_dir_config *)addv; | 
 |     mime_dir_config *new = apr_palloc(p, sizeof(mime_dir_config)); | 
 |  | 
 |     if (base->extension_mappings && add->extension_mappings) { | 
 |         new->extension_mappings = apr_hash_merge(p, add->extension_mappings, | 
 |                                                  base->extension_mappings, | 
 |                                                  overlay_extension_mappings, | 
 |                                                  NULL); | 
 |     } | 
 |     else { | 
 |         if (base->extension_mappings == NULL) { | 
 |             new->extension_mappings = add->extension_mappings; | 
 |         } | 
 |         else { | 
 |             new->extension_mappings = base->extension_mappings; | 
 |         } | 
 |         /* We may not be merging the tables, but if we potentially will change | 
 |          * an exinfo member, then we are about to trounce it anyways. | 
 |          * We must have a copy for safety. | 
 |          */ | 
 |         if (new->extension_mappings && add->remove_mappings) { | 
 |             new->extension_mappings = | 
 |                 apr_hash_copy(p, new->extension_mappings); | 
 |         } | 
 |     } | 
 |  | 
 |     if (new->extension_mappings) { | 
 |         if (add->remove_mappings) | 
 |             remove_items(p, add->remove_mappings, new->extension_mappings); | 
 |     } | 
 |     new->remove_mappings = NULL; | 
 |  | 
 |     new->default_language = add->default_language ? | 
 |         add->default_language : base->default_language; | 
 |  | 
 |     new->multimatch = (add->multimatch != MULTIMATCH_UNSET) ? | 
 |         add->multimatch : base->multimatch; | 
 |  | 
 |     if ((add->use_path_info & 2) == 0) { | 
 |         new->use_path_info = add->use_path_info; | 
 |     } | 
 |     else { | 
 |         new->use_path_info = base->use_path_info; | 
 |     } | 
 |  | 
 |     return new; | 
 | } | 
 |  | 
 | static const char *add_extension_info(cmd_parms *cmd, void *m_, | 
 |                                       const char *value_, const char* ext) | 
 | { | 
 |     mime_dir_config *m=m_; | 
 |     extension_info *exinfo; | 
 |     int offset = (int) (long) cmd->info; | 
 |     char *key = apr_pstrdup(cmd->temp_pool, ext); | 
 |     char *value = apr_pstrdup(cmd->pool, value_); | 
 |     ap_str_tolower(value); | 
 |     ap_str_tolower(key); | 
 |  | 
 |     if (*key == '.') { | 
 |         ++key; | 
 |     } | 
 |     if (!m->extension_mappings) { | 
 |         m->extension_mappings = apr_hash_make(cmd->pool); | 
 |         exinfo = NULL; | 
 |     } | 
 |     else { | 
 |         exinfo = (extension_info*)apr_hash_get(m->extension_mappings, key, | 
 |                                                APR_HASH_KEY_STRING); | 
 |     } | 
 |     if (!exinfo) { | 
 |         exinfo = apr_pcalloc(cmd->pool, sizeof(extension_info)); | 
 |         key = apr_pstrdup(cmd->pool, key); | 
 |         apr_hash_set(m->extension_mappings, key, APR_HASH_KEY_STRING, exinfo); | 
 |     } | 
 |     *(const char**)((char *)exinfo + offset) = value; | 
 |     return NULL; | 
 | } | 
 |  | 
 | /* | 
 |  * Note handler names are un-added with each per_dir_config merge. | 
 |  * This keeps the association from being inherited, but not | 
 |  * from being re-added at a subordinate level. | 
 |  */ | 
 | static const char *remove_extension_info(cmd_parms *cmd, void *m_, | 
 |                                          const char *ext) | 
 | { | 
 |     mime_dir_config *m = (mime_dir_config *) m_; | 
 |     attrib_info *suffix; | 
 |     if (*ext == '.') { | 
 |         ++ext; | 
 |     } | 
 |     if (!m->remove_mappings) { | 
 |         m->remove_mappings = apr_array_make(cmd->pool, 4, sizeof(*suffix)); | 
 |     } | 
 |     suffix = (attrib_info *)apr_array_push(m->remove_mappings); | 
 |     suffix->name = apr_pstrdup(cmd->pool, ext); | 
 |     ap_str_tolower(suffix->name); | 
 |     suffix->offset = (int) (long) cmd->info; | 
 |     return NULL; | 
 | } | 
 |  | 
 | /* The sole bit of server configuration that the MIME module has is | 
 |  * the name of its config file, so... | 
 |  */ | 
 |  | 
 | static const char *set_types_config(cmd_parms *cmd, void *dummy, | 
 |                                     const char *arg) | 
 | { | 
 |     ap_set_module_config(cmd->server->module_config, &mime_module, | 
 |                          (void *)arg); | 
 |     return NULL; | 
 | } | 
 |  | 
 | static const char *multiviews_match(cmd_parms *cmd, void *m_, | 
 |                                     const char *include) | 
 | { | 
 |     mime_dir_config *m = (mime_dir_config *) m_; | 
 |  | 
 |     if (strcasecmp(include, "Any") == 0) { | 
 |         if (m->multimatch && (m->multimatch & ~MULTIMATCH_ANY)) { | 
 |             return "Any is incompatible with NegotiatedOnly, " | 
 |                    "Filters and Handlers"; | 
 |         } | 
 |         m->multimatch |= MULTIMATCH_ANY; | 
 |     } | 
 |     else if (strcasecmp(include, "NegotiatedOnly") == 0) { | 
 |         if (m->multimatch && (m->multimatch & ~MULTIMATCH_NEGOTIATED)) { | 
 |             return "NegotiatedOnly is incompatible with Any, " | 
 |                    "Filters and Handlers"; | 
 |         } | 
 |         m->multimatch |= MULTIMATCH_NEGOTIATED; | 
 |     } | 
 |     else if (strcasecmp(include, "Filters") == 0) { | 
 |         if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED | 
 |                                              | MULTIMATCH_ANY))) { | 
 |             return "Filters is incompatible with Any and NegotiatedOnly"; | 
 |         } | 
 |         m->multimatch |= MULTIMATCH_FILTERS; | 
 |     } | 
 |     else if (strcasecmp(include, "Handlers") == 0) { | 
 |         if (m->multimatch && (m->multimatch & (MULTIMATCH_NEGOTIATED | 
 |                                              | MULTIMATCH_ANY))) { | 
 |             return "Handlers is incompatible with Any and NegotiatedOnly"; | 
 |         } | 
 |         m->multimatch |= MULTIMATCH_HANDLERS; | 
 |     } | 
 |     else { | 
 |         return apr_psprintf(cmd->pool, "Unrecognized option '%s'", include); | 
 |     } | 
 |  | 
 |     return NULL; | 
 | } | 
 |  | 
 | static const command_rec mime_cmds[] = | 
 | { | 
 |     AP_INIT_ITERATE2("AddCharset", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO, | 
 |         "a charset (e.g., iso-2022-jp), followed by one or more " | 
 |         "file extensions"), | 
 |     AP_INIT_ITERATE2("AddEncoding", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO, | 
 |         "an encoding (e.g., gzip), followed by one or more file extensions"), | 
 |     AP_INIT_ITERATE2("AddHandler", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO, | 
 |         "a handler name followed by one or more file extensions"), | 
 |     AP_INIT_ITERATE2("AddInputFilter", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO, | 
 |         "input filter name (or ; delimited names) followed by one or " | 
 |         "more file extensions"), | 
 |     AP_INIT_ITERATE2("AddLanguage", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO, | 
 |         "a language (e.g., fr), followed by one or more file extensions"), | 
 |     AP_INIT_ITERATE2("AddOutputFilter", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO, | 
 |         "output filter name (or ; delimited names) followed by one or " | 
 |         "more file extensions"), | 
 |     AP_INIT_ITERATE2("AddType", add_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO, | 
 |         "a mime type followed by one or more file extensions"), | 
 |     AP_INIT_TAKE1("DefaultLanguage", ap_set_string_slot, | 
 |         (void*)APR_OFFSETOF(mime_dir_config, default_language), OR_FILEINFO, | 
 |         "language to use for documents with no other language file extension"), | 
 |     AP_INIT_ITERATE("MultiviewsMatch", multiviews_match, NULL, OR_FILEINFO, | 
 |         "NegotiatedOnly (default), Handlers and/or Filters, or Any"), | 
 |     AP_INIT_ITERATE("RemoveCharset", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, charset_type), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_ITERATE("RemoveEncoding", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, encoding_type), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_ITERATE("RemoveHandler", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, handler), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_ITERATE("RemoveInputFilter", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, input_filters), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_ITERATE("RemoveLanguage", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, language_type), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_ITERATE("RemoveOutputFilter", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, output_filters), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_ITERATE("RemoveType", remove_extension_info, | 
 |         (void *)APR_OFFSETOF(extension_info, forced_type), OR_FILEINFO, | 
 |         "one or more file extensions"), | 
 |     AP_INIT_TAKE1("TypesConfig", set_types_config, NULL, RSRC_CONF, | 
 |         "the MIME types config file"), | 
 |     AP_INIT_FLAG("ModMimeUsePathInfo", ap_set_flag_slot, | 
 |         (void *)APR_OFFSETOF(mime_dir_config, use_path_info), ACCESS_CONF, | 
 |         "Set to 'yes' to allow mod_mime to use path info for type checking"), | 
 |     {NULL} | 
 | }; | 
 |  | 
 | static apr_hash_t *mime_type_extensions; | 
 |  | 
 | static int mime_post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) | 
 | { | 
 |     ap_configfile_t *f; | 
 |     char l[MAX_STRING_LEN]; | 
 |     const char *types_confname = ap_get_module_config(s->module_config, | 
 |                                                       &mime_module); | 
 |     apr_status_t status; | 
 |  | 
 |     if (!types_confname) { | 
 |         types_confname = AP_TYPES_CONFIG_FILE; | 
 |     } | 
 |  | 
 |     types_confname = ap_server_root_relative(p, types_confname); | 
 |     if (!types_confname) { | 
 |         ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, | 
 |                      "Invalid mime types config path %s", | 
 |                      (const char *)ap_get_module_config(s->module_config, | 
 |                                                         &mime_module)); | 
 |         return HTTP_INTERNAL_SERVER_ERROR; | 
 |     } | 
 |     if ((status = ap_pcfg_openfile(&f, ptemp, types_confname)) | 
 |                 != APR_SUCCESS) { | 
 |         ap_log_error(APLOG_MARK, APLOG_ERR, status, s, | 
 |                      "could not open mime types config file %s.", | 
 |                      types_confname); | 
 |         return HTTP_INTERNAL_SERVER_ERROR; | 
 |     } | 
 |  | 
 |     mime_type_extensions = apr_hash_make(p); | 
 |  | 
 |     while (!(ap_cfg_getline(l, MAX_STRING_LEN, f))) { | 
 |         const char *ll = l, *ct; | 
 |  | 
 |         if (l[0] == '#') { | 
 |             continue; | 
 |         } | 
 |         ct = ap_getword_conf(p, &ll); | 
 |  | 
 |         while (ll[0]) { | 
 |             char *ext = ap_getword_conf(p, &ll); | 
 |             ap_str_tolower(ext); | 
 |             apr_hash_set(mime_type_extensions, ext, APR_HASH_KEY_STRING, ct); | 
 |         } | 
 |     } | 
 |     ap_cfg_closefile(f); | 
 |     return OK; | 
 | } | 
 |  | 
 | static const char *zap_sp(const char *s) | 
 | { | 
 |     if (s == NULL) { | 
 |         return (NULL); | 
 |     } | 
 |     if (*s == '\0') { | 
 |         return (s); | 
 |     } | 
 |  | 
 |     /* skip prefixed white space */ | 
 |     for (; *s == ' ' || *s == '\t' || *s == '\n'; s++) | 
 |         ; | 
 |  | 
 |     return (s); | 
 | } | 
 |  | 
 | static char *zap_sp_and_dup(apr_pool_t *p, const char *start, | 
 |                             const char *end, apr_size_t *len) | 
 | { | 
 |     while ((start < end) && apr_isspace(*start)) { | 
 |         start++; | 
 |     } | 
 |     while ((end > start) && apr_isspace(*(end - 1))) { | 
 |         end--; | 
 |     } | 
 |     if (len) { | 
 |         *len = end - start; | 
 |     } | 
 |     return apr_pstrmemdup(p, start, end - start); | 
 | } | 
 |  | 
 | static int is_token(char c) | 
 | { | 
 |     int res; | 
 |  | 
 |     res = (apr_isascii(c) && apr_isgraph(c) | 
 |            && (strchr(tspecial, c) == NULL)) ? 1 : -1; | 
 |     return res; | 
 | } | 
 |  | 
 | static int is_qtext(char c) | 
 | { | 
 |     int res; | 
 |  | 
 |     res = (apr_isascii(c) && (c != '"') && (c != '\\') && (c != '\n')) | 
 |         ? 1 : -1; | 
 |     return res; | 
 | } | 
 |  | 
 | static int is_quoted_pair(const char *s) | 
 | { | 
 |     int res = -1; | 
 |     int c; | 
 |  | 
 |     if (((s + 1) != NULL) && (*s == '\\')) { | 
 |         c = (int) *(s + 1); | 
 |         if (apr_isascii(c)) { | 
 |             res = 1; | 
 |         } | 
 |     } | 
 |     return (res); | 
 | } | 
 |  | 
 | static content_type *analyze_ct(request_rec *r, const char *s) | 
 | { | 
 |     const char *cp, *mp; | 
 |     char *attribute, *value; | 
 |     int quoted = 0; | 
 |     server_rec * ss = r->server; | 
 |     apr_pool_t * p = r->pool; | 
 |  | 
 |     content_type *ctp; | 
 |     param *pp, *npp; | 
 |  | 
 |     /* initialize ctp */ | 
 |     ctp = (content_type *)apr_palloc(p, sizeof(content_type)); | 
 |     ctp->type = NULL; | 
 |     ctp->subtype = NULL; | 
 |     ctp->param = NULL; | 
 |  | 
 |     mp = s; | 
 |  | 
 |     /* getting a type */ | 
 |     cp = mp; | 
 |     while (apr_isspace(*cp)) { | 
 |         cp++; | 
 |     } | 
 |     if (!*cp) { | 
 |         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                      "mod_mime: analyze_ct: cannot get media type from '%s'", | 
 |                      (const char *) mp); | 
 |         return (NULL); | 
 |     } | 
 |     ctp->type = cp; | 
 |     do { | 
 |         cp++; | 
 |     } while (*cp && (*cp != '/') && !apr_isspace(*cp) && (*cp != ';')); | 
 |     if (!*cp || (*cp == ';')) { | 
 |         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                      "Cannot get media type from '%s'", | 
 |                      (const char *) mp); | 
 |         return (NULL); | 
 |     } | 
 |     while (apr_isspace(*cp)) { | 
 |         cp++; | 
 |     } | 
 |     if (*cp != '/') { | 
 |         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                      "mod_mime: analyze_ct: cannot get media type from '%s'", | 
 |                      (const char *) mp); | 
 |         return (NULL); | 
 |     } | 
 |     ctp->type_len = cp - ctp->type; | 
 |  | 
 |     cp++; /* skip the '/' */ | 
 |  | 
 |     /* getting a subtype */ | 
 |     while (apr_isspace(*cp)) { | 
 |         cp++; | 
 |     } | 
 |     if (!*cp) { | 
 |         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                      "Cannot get media subtype."); | 
 |         return (NULL); | 
 |     } | 
 |     ctp->subtype = cp; | 
 |     do { | 
 |         cp++; | 
 |     } while (*cp && !apr_isspace(*cp) && (*cp != ';')); | 
 |     ctp->subtype_len = cp - ctp->subtype; | 
 |     while (apr_isspace(*cp)) { | 
 |         cp++; | 
 |     } | 
 |  | 
 |     if (*cp == '\0') { | 
 |         return (ctp); | 
 |     } | 
 |  | 
 |     /* getting parameters */ | 
 |     cp++; /* skip the ';' */ | 
 |     cp = zap_sp(cp); | 
 |     if (cp == NULL || *cp == '\0') { | 
 |         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                      "Cannot get media parameter."); | 
 |         return (NULL); | 
 |     } | 
 |     mp = cp; | 
 |     attribute = NULL; | 
 |     value = NULL; | 
 |  | 
 |     while (cp != NULL && *cp != '\0') { | 
 |         if (attribute == NULL) { | 
 |             if (is_token(*cp) > 0) { | 
 |                 cp++; | 
 |                 continue; | 
 |             } | 
 |             else if (*cp == ' ' || *cp == '\t' || *cp == '\n') { | 
 |                 cp++; | 
 |                 continue; | 
 |             } | 
 |             else if (*cp == '=') { | 
 |                 attribute = zap_sp_and_dup(p, mp, cp, NULL); | 
 |                 if (attribute == NULL || *attribute == '\0') { | 
 |                     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                                  "Cannot get media parameter."); | 
 |                     return (NULL); | 
 |                 } | 
 |                 cp++; | 
 |                 cp = zap_sp(cp); | 
 |                 if (cp == NULL || *cp == '\0') { | 
 |                     ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                                  "Cannot get media parameter."); | 
 |                     return (NULL); | 
 |                 } | 
 |                 mp = cp; | 
 |                 continue; | 
 |             } | 
 |             else { | 
 |                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                              "Cannot get media parameter."); | 
 |                 return (NULL); | 
 |             } | 
 |         } | 
 |         else { | 
 |             if (mp == cp) { | 
 |                 if (*cp == '"') { | 
 |                     quoted = 1; | 
 |                     cp++; | 
 |                 } | 
 |                 else { | 
 |                     quoted = 0; | 
 |                 } | 
 |             } | 
 |             if (quoted > 0) { | 
 |                 while (quoted && *cp != '\0') { | 
 |                     if (is_qtext(*cp) > 0) { | 
 |                         cp++; | 
 |                     } | 
 |                     else if (is_quoted_pair(cp) > 0) { | 
 |                         cp += 2; | 
 |                     } | 
 |                     else if (*cp == '"') { | 
 |                         cp++; | 
 |                         while (*cp == ' ' || *cp == '\t' || *cp == '\n') { | 
 |                             cp++; | 
 |                         } | 
 |                         if (*cp != ';' && *cp != '\0') { | 
 |                             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                                          "Cannot get media parameter."); | 
 |                             return(NULL); | 
 |                         } | 
 |                         quoted = 0; | 
 |                     } | 
 |                     else { | 
 |                         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                                      "Cannot get media parameter."); | 
 |                         return (NULL); | 
 |                     } | 
 |                 } | 
 |             } | 
 |             else { | 
 |                 while (1) { | 
 |                     if (is_token(*cp) > 0) { | 
 |                         cp++; | 
 |                     } | 
 |                     else if (*cp == '\0' || *cp == ';') { | 
 |                         break; | 
 |                     } | 
 |                     else { | 
 |                         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                                      "Cannot get media parameter."); | 
 |                         return (NULL); | 
 |                     } | 
 |                 } | 
 |             } | 
 |             value = zap_sp_and_dup(p, mp, cp, NULL); | 
 |             if (value == NULL || *value == '\0') { | 
 |                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, | 
 |                              "Cannot get media parameter."); | 
 |                 return (NULL); | 
 |             } | 
 |  | 
 |             pp = apr_palloc(p, sizeof(param)); | 
 |             pp->attr = attribute; | 
 |             pp->val = value; | 
 |             pp->next = NULL; | 
 |  | 
 |             if (ctp->param == NULL) { | 
 |                 ctp->param = pp; | 
 |             } | 
 |             else { | 
 |                 npp = ctp->param; | 
 |                 while (npp->next) { | 
 |                     npp = npp->next; | 
 |                 } | 
 |                 npp->next = pp; | 
 |             } | 
 |             quoted = 0; | 
 |             attribute = NULL; | 
 |             value = NULL; | 
 |             if (*cp == '\0') { | 
 |                 break; | 
 |             } | 
 |             cp++; | 
 |             mp = cp; | 
 |         } | 
 |     } | 
 |     return (ctp); | 
 | } | 
 |  | 
 | /* | 
 |  * find_ct is the hook routine for determining content-type and other | 
 |  * MIME-related metadata.  It assumes that r->filename has already been | 
 |  * set and stat has been called for r->finfo.  It also assumes that the | 
 |  * non-path base file name is not the empty string unless it is a dir. | 
 |  */ | 
 | static int find_ct(request_rec *r) | 
 | { | 
 |     mime_dir_config *conf; | 
 |     apr_array_header_t *exception_list; | 
 |     char *ext; | 
 |     const char *fn, *type, *charset = NULL, *resource_name; | 
 |     int found_metadata = 0; | 
 |  | 
 |     if (r->finfo.filetype == APR_DIR) { | 
 |         ap_set_content_type(r, DIR_MAGIC_TYPE); | 
 |         return OK; | 
 |     } | 
 |  | 
 |     if (!r->filename) { | 
 |         return DECLINED; | 
 |     } | 
 |  | 
 |     conf = (mime_dir_config *)ap_get_module_config(r->per_dir_config, | 
 |                                                    &mime_module); | 
 |     exception_list = apr_array_make(r->pool, 2, sizeof(char *)); | 
 |  | 
 |     /* If use_path_info is explicitly set to on (value & 1 == 1), append. */ | 
 |     if (conf->use_path_info & 1) { | 
 |         resource_name = apr_pstrcat(r->pool, r->filename, r->path_info, NULL); | 
 |     } | 
 |     else { | 
 |         resource_name = r->filename; | 
 |     } | 
 |  | 
 |     /* Always drop the path leading up to the file name. | 
 |      */ | 
 |     if ((fn = ap_strrchr_c(resource_name, '/')) == NULL) { | 
 |         fn = resource_name; | 
 |     } | 
 |     else { | 
 |         ++fn; | 
 |     } | 
 |  | 
 |     /* The exception list keeps track of those filename components that | 
 |      * are not associated with extensions indicating metadata. | 
 |      * The base name is always the first exception (i.e., "txt.html" has | 
 |      * a basename of "txt" even though it might look like an extension). | 
 |      */ | 
 |     ext = ap_getword(r->pool, &fn, '.'); | 
 |     *((const char **)apr_array_push(exception_list)) = ext; | 
 |  | 
 |     /* Parse filename extensions which can be in any order | 
 |      */ | 
 |     while (*fn && (ext = ap_getword(r->pool, &fn, '.'))) { | 
 |         const extension_info *exinfo = NULL; | 
 |         int found; | 
 |  | 
 |         if (*ext == '\0') {  /* ignore empty extensions "bad..html" */ | 
 |             continue; | 
 |         } | 
 |  | 
 |         found = 0; | 
 |  | 
 |         ap_str_tolower(ext); | 
 |  | 
 |         if (conf->extension_mappings != NULL) { | 
 |             exinfo = (extension_info*)apr_hash_get(conf->extension_mappings, | 
 |                                                    ext, APR_HASH_KEY_STRING); | 
 |         } | 
 |  | 
 |         if (exinfo == NULL || !exinfo->forced_type) { | 
 |             if ((type = apr_hash_get(mime_type_extensions, ext, | 
 |                                      APR_HASH_KEY_STRING)) != NULL) { | 
 |                 ap_set_content_type(r, (char*) type); | 
 |                 found = 1; | 
 |             } | 
 |         } | 
 |  | 
 |         if (exinfo != NULL) { | 
 |  | 
 |             if (exinfo->forced_type) { | 
 |                 ap_set_content_type(r, exinfo->forced_type); | 
 |                 found = 1; | 
 |             } | 
 |  | 
 |             if (exinfo->charset_type) { | 
 |                 charset = exinfo->charset_type; | 
 |                 found = 1; | 
 |             } | 
 |             if (exinfo->language_type) { | 
 |                 if (!r->content_languages) { | 
 |                     r->content_languages = apr_array_make(r->pool, 2, | 
 |                                                           sizeof(char *)); | 
 |                 } | 
 |                 *((const char **)apr_array_push(r->content_languages)) | 
 |                     = exinfo->language_type; | 
 |                 found = 1; | 
 |             } | 
 |             if (exinfo->encoding_type) { | 
 |                 if (!r->content_encoding) { | 
 |                     r->content_encoding = exinfo->encoding_type; | 
 |                 } | 
 |                 else { | 
 |                     /* XXX should eliminate duplicate entities | 
 |                      * | 
 |                      * ah no. Order is important and double encoding is neither | 
 |                      * forbidden nor impossible. -- nd | 
 |                      */ | 
 |                     r->content_encoding = apr_pstrcat(r->pool, | 
 |                                                       r->content_encoding, | 
 |                                                       ", ", | 
 |                                                       exinfo->encoding_type, | 
 |                                                       NULL); | 
 |                 } | 
 |                 found = 1; | 
 |             } | 
 |             /* The following extensions are not 'Found'.  That is, they don't | 
 |              * make any contribution to metadata negotation, so they must have | 
 |              * been explicitly requested by name. | 
 |              */ | 
 |             if (exinfo->handler && r->proxyreq == PROXYREQ_NONE) { | 
 |                 r->handler = exinfo->handler; | 
 |                 if (conf->multimatch & MULTIMATCH_HANDLERS) { | 
 |                     found = 1; | 
 |                 } | 
 |             } | 
 |             /* XXX Two significant problems; 1, we don't check to see if we are | 
 |              * setting redundant filters.    2, we insert these in the types config | 
 |              * hook, which may be too early (dunno.) | 
 |              */ | 
 |             if (exinfo->input_filters && r->proxyreq == PROXYREQ_NONE) { | 
 |                 const char *filter, *filters = exinfo->input_filters; | 
 |                 while (*filters | 
 |                     && (filter = ap_getword(r->pool, &filters, ';'))) { | 
 |                     ap_add_input_filter(filter, NULL, r, r->connection); | 
 |                 } | 
 |                 if (conf->multimatch & MULTIMATCH_FILTERS) { | 
 |                     found = 1; | 
 |                 } | 
 |             } | 
 |             if (exinfo->output_filters && r->proxyreq == PROXYREQ_NONE) { | 
 |                 const char *filter, *filters = exinfo->output_filters; | 
 |                 while (*filters | 
 |                     && (filter = ap_getword(r->pool, &filters, ';'))) { | 
 |                     ap_add_output_filter(filter, NULL, r, r->connection); | 
 |                 } | 
 |                 if (conf->multimatch & MULTIMATCH_FILTERS) { | 
 |                     found = 1; | 
 |                 } | 
 |             } | 
 |         } | 
 |  | 
 |         if (found || (conf->multimatch & MULTIMATCH_ANY)) { | 
 |             found_metadata = 1; | 
 |         } | 
 |         else { | 
 |             *((const char **) apr_array_push(exception_list)) = ext; | 
 |         } | 
 |     } | 
 |  | 
 |     /* | 
 |      * Need to set a notes entry on r for unrecognized elements. | 
 |      * Somebody better claim them!  If we did absolutely nothing, | 
 |      * skip the notes to alert mod_negotiation we are clueless. | 
 |      */ | 
 |     if (found_metadata) { | 
 |         apr_table_setn(r->notes, "ap-mime-exceptions-list", | 
 |                        (void *)exception_list); | 
 |     } | 
 |  | 
 |     if (r->content_type) { | 
 |         content_type *ctp; | 
 |         int override = 0; | 
 |  | 
 |         if ((ctp = analyze_ct(r, r->content_type))) { | 
 |             param *pp = ctp->param; | 
 |             char *base_content_type = apr_palloc(r->pool, ctp->type_len + | 
 |                                                  ctp->subtype_len + | 
 |                                                  sizeof("/")); | 
 |             char *tmp = base_content_type; | 
 |             memcpy(tmp, ctp->type, ctp->type_len); | 
 |             tmp += ctp->type_len; | 
 |             *tmp++ = '/'; | 
 |             memcpy(tmp, ctp->subtype, ctp->subtype_len); | 
 |             tmp += ctp->subtype_len; | 
 |             *tmp = 0; | 
 |             ap_set_content_type(r, base_content_type); | 
 |             while (pp != NULL) { | 
 |                 if (charset && !strcmp(pp->attr, "charset")) { | 
 |                     if (!override) { | 
 |                         ap_set_content_type(r, | 
 |                                             apr_pstrcat(r->pool, | 
 |                                                         r->content_type, | 
 |                                                         "; charset=", | 
 |                                                         charset, | 
 |                                                         NULL)); | 
 |                         override = 1; | 
 |                     } | 
 |                 } | 
 |                 else { | 
 |                     ap_set_content_type(r, | 
 |                                         apr_pstrcat(r->pool, | 
 |                                                     r->content_type, | 
 |                                                     "; ", pp->attr, | 
 |                                                     "=", pp->val, | 
 |                                                     NULL)); | 
 |                 } | 
 |                 pp = pp->next; | 
 |             } | 
 |             if (charset && !override) { | 
 |                 ap_set_content_type(r, apr_pstrcat(r->pool, r->content_type, | 
 |                                                    "; charset=", charset, | 
 |                                                    NULL)); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     /* Set default language, if none was specified by the extensions | 
 |      * and we have a DefaultLanguage setting in force | 
 |      */ | 
 |  | 
 |     if (!r->content_languages && conf->default_language) { | 
 |         const char **new; | 
 |  | 
 |         if (!r->content_languages) { | 
 |             r->content_languages = apr_array_make(r->pool, 2, sizeof(char *)); | 
 |         } | 
 |         new = (const char **)apr_array_push(r->content_languages); | 
 |         *new = conf->default_language; | 
 |     } | 
 |  | 
 |     if (!r->content_type) { | 
 |         return DECLINED; | 
 |     } | 
 |  | 
 |     return OK; | 
 | } | 
 |  | 
 | static void register_hooks(apr_pool_t *p) | 
 | { | 
 |     ap_hook_post_config(mime_post_config,NULL,NULL,APR_HOOK_MIDDLE); | 
 |     ap_hook_type_checker(find_ct,NULL,NULL,APR_HOOK_MIDDLE); | 
 |     /* | 
 |      * this hook seems redundant ... is there any reason a type checker isn't | 
 |      * allowed to do this already?  I'd think that fixups in general would be | 
 |      * the last opportunity to get the filters right. | 
 |      * ap_hook_insert_filter(mime_insert_filters,NULL,NULL,APR_HOOK_MIDDLE); | 
 |      */ | 
 | } | 
 |  | 
 | module AP_MODULE_DECLARE_DATA mime_module = { | 
 |     STANDARD20_MODULE_STUFF, | 
 |     create_mime_dir_config,     /* create per-directory config structure */ | 
 |     merge_mime_dir_configs,     /* merge per-directory config structures */ | 
 |     NULL,                       /* create per-server config structure */ | 
 |     NULL,                       /* merge per-server config structures */ | 
 |     mime_cmds,                  /* command apr_table_t */ | 
 |     register_hooks              /* register hooks */ | 
 | }; |