| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You 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; | |
| apr_size_t 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. | |
| */ | |
| /* Only use the final extension for Content-Type */ | |
| enum {CT_LAST_INIT, CT_LAST_ON, CT_LAST_OFF} ct_last_ext; | |
| /* Only use the final extension for anything */ | |
| enum {ALL_LAST_INIT, ALL_LAST_ON, ALL_LAST_OFF} all_last_ext; | |
| /* don't do any detection */ | |
| enum {NOMIME_INIT, NOMIME_ON, NOMIME_OFF} nomime; | |
| } 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; | |
| new->ct_last_ext = CT_LAST_INIT; | |
| new->all_last_ext = ALL_LAST_INIT; | |
| new->nomime = NOMIME_INIT; | |
| 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) | |
| { | |
| const extension_info *overlay_info = (const extension_info *)overlay_val; | |
| const extension_info *base_info = (const extension_info *)base_val; | |
| extension_info *new_info = apr_pmemdup(p, 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 = apr_pmemdup(p, copyinfo, sizeof(*exinfo)); | |
| apr_hash_set(mappings, suffix[i].name, | |
| APR_HASH_KEY_STRING, 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; | |
| } | |
| new->ct_last_ext = (add->ct_last_ext != CT_LAST_INIT) | |
| ? add->ct_last_ext | |
| : base->ct_last_ext; | |
| new->all_last_ext = (add->all_last_ext != ALL_LAST_INIT) | |
| ? add->all_last_ext | |
| : base->all_last_ext; | |
| new->nomime = (add->nomime != NOMIME_INIT) | |
| ? add->nomime | |
| : base->nomime; | |
| 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; | |
| apr_size_t offset = (apr_size_t) 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; | |
| } | |
| /* | |
| * As RemoveType should also override the info from TypesConfig, we add an | |
| * empty string as type instead of actually removing the type. | |
| */ | |
| static const char *remove_extension_type(cmd_parms *cmd, void *m_, | |
| const char *ext) | |
| { | |
| return add_extension_info(cmd, m_, "", ext); | |
| } | |
| /* | |
| * 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 = (apr_size_t) 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_; | |
| const char *errmsg; | |
| errmsg = ap_check_cmd_context(cmd, NOT_IN_LOCATION); | |
| if (errmsg != NULL) { | |
| return errmsg; | |
| } | |
| 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 char *add_mime_options(cmd_parms *cmd, void *in_dc, | |
| const char *flag) | |
| { | |
| mime_dir_config *dc = in_dc; | |
| if (!strcasecmp(flag, "TypesLastExtension")) { | |
| dc->ct_last_ext = CT_LAST_ON; | |
| } | |
| else if (!strcasecmp(flag, "NoTypesLastExtension")) { | |
| dc->ct_last_ext = CT_LAST_OFF; | |
| } | |
| else if (!strcasecmp(flag, "AllLastExtension")) { | |
| dc->all_last_ext = ALL_LAST_ON; | |
| } | |
| else if (!strcasecmp(flag, "NoAllLastExtension")) { | |
| dc->all_last_ext = ALL_LAST_OFF; | |
| } | |
| else if (!strcasecmp(flag, "Disable")) { | |
| dc->nomime = NOMIME_ON; | |
| } | |
| else if (!strcasecmp(flag, "Enable")) { | |
| dc->nomime = NOMIME_OFF; | |
| } | |
| else { | |
| return apr_pstrcat(cmd->temp_pool, | |
| "Invalid MimeOptions option: ", | |
| flag, | |
| NULL); | |
| } | |
| 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_type, | |
| (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"), | |
| AP_INIT_ITERATE("MimeOptions", | |
| add_mime_options, | |
| NULL, | |
| OR_FILEINFO, | |
| "valid options: [No]TypesLastExtension, [No]AllLastExtension"), | |
| {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, APLOGNO(01596) | |
| "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, APLOGNO(01597) | |
| "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 == '\\') { | |
| c = (int) *(s + 1); | |
| if (c && 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, APLOGNO(01598) | |
| "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, APLOGNO(01599) | |
| "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, APLOGNO(01600) | |
| "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, APLOGNO(01601) | |
| "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, APLOGNO(01602) | |
| "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, APLOGNO(01603) | |
| "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, APLOGNO(01604) | |
| "Cannot get media parameter."); | |
| return (NULL); | |
| } | |
| mp = cp; | |
| continue; | |
| } | |
| else { | |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01605) | |
| "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, APLOGNO(01606) | |
| "Cannot get media parameter."); | |
| return(NULL); | |
| } | |
| quoted = 0; | |
| } | |
| else { | |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ss, APLOGNO(01607) | |
| "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, APLOGNO(01608) | |
| "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, APLOGNO(01609) | |
| "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, *fntmp, *type, *charset = NULL, *resource_name, *qm; | |
| int found_metadata = 0; | |
| if (r->finfo.filetype == APR_DIR) { | |
| ap_set_content_type_ex(r, DIR_MAGIC_TYPE, 1); | |
| return OK; | |
| } | |
| if (!r->filename) { | |
| return DECLINED; | |
| } | |
| conf = (mime_dir_config *)ap_get_module_config(r->per_dir_config, | |
| &mime_module); | |
| if (conf->nomime == NOMIME_ON) { | |
| return DECLINED; | |
| } | |
| 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); | |
| } | |
| /* | |
| * In the reverse proxy case r->filename might contain a query string if | |
| * the nocanon option was used with ProxyPass. | |
| * If this is the case cut off the query string as the last parameter in | |
| * this query string might end up on an extension we take care about, but | |
| * we only want to match against path components not against query | |
| * parameters. | |
| */ | |
| else if ((r->proxyreq == PROXYREQ_REVERSE) | |
| && (apr_table_get(r->notes, "proxy-nocanon")) | |
| && ((qm = ap_strchr_c(r->filename, '?')) != NULL)) { | |
| resource_name = apr_pstrmemdup(r->pool, r->filename, qm - r->filename); | |
| } | |
| 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). | |
| * Leading dots are considered to be part of the base name (a file named | |
| * ".png" is likely not a png file but just a hidden file called png). | |
| */ | |
| fntmp = fn; | |
| while (*fntmp == '.') | |
| fntmp++; | |
| fntmp = ap_strchr_c(fntmp, '.'); | |
| if (fntmp) { | |
| ext = apr_pstrmemdup(r->pool, fn, fntmp - fn); | |
| fn = fntmp + 1; | |
| } | |
| else { | |
| ext = apr_pstrdup(r->pool, fn); | |
| fn += strlen(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; | |
| char *extcase; | |
| int skipct = (conf->ct_last_ext == CT_LAST_ON) && (*fn); | |
| int skipall = (conf->all_last_ext == ALL_LAST_ON) && (*fn); | |
| if (*ext == '\0') { /* ignore empty extensions "bad..html" */ | |
| continue; | |
| } | |
| if (skipall) { | |
| continue; | |
| } | |
| found = 0; | |
| /* Save the ext in extcase before converting it to lower case. | |
| */ | |
| extcase = apr_pstrdup(r->pool, ext); | |
| 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) && !skipct) { | |
| if ((type = apr_hash_get(mime_type_extensions, ext, | |
| APR_HASH_KEY_STRING)) != NULL) { | |
| ap_set_content_type_ex(r, (char*) type, 1); | |
| found = 1; | |
| } | |
| } | |
| if (exinfo != NULL) { | |
| /* empty string is treated as special case for RemoveType */ | |
| if ((exinfo->forced_type && *exinfo->forced_type) && !skipct) { | |
| ap_set_content_type_ex(r, exinfo->forced_type, 1); | |
| 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 negotiation, 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) { | |
| 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) { | |
| 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)) = extcase; | |
| } | |
| } | |
| /* | |
| * 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_ex(r, base_content_type, AP_REQUEST_IS_TRUSTED_CT(r)); | |
| while (pp != NULL) { | |
| if (charset && !strcmp(pp->attr, "charset")) { | |
| if (!override) { | |
| ap_set_content_type_ex(r, | |
| apr_pstrcat(r->pool, | |
| r->content_type, | |
| "; charset=", | |
| charset, | |
| NULL), AP_REQUEST_IS_TRUSTED_CT(r)); | |
| override = 1; | |
| } | |
| } | |
| else { | |
| ap_set_content_type_ex(r, | |
| apr_pstrcat(r->pool, | |
| r->content_type, | |
| "; ", pp->attr, | |
| "=", pp->val, | |
| NULL), AP_REQUEST_IS_TRUSTED_CT(r)); | |
| } | |
| pp = pp->next; | |
| } | |
| if (charset && !override) { | |
| ap_set_content_type_ex(r, apr_pstrcat(r->pool, r->content_type, | |
| "; charset=", charset, | |
| NULL), AP_REQUEST_IS_TRUSTED_CT(r)); | |
| } | |
| } | |
| } | |
| /* 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; | |
| 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); | |
| */ | |
| } | |
| AP_DECLARE_MODULE(mime) = { | |
| 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 */ | |
| }; |