From 9573df4790b49af143619bbb8662307683cf13aa Mon Sep 17 00:00:00 2001 From: Bill Stoddard Date: Tue, 2 Oct 2001 21:01:31 +0000 Subject: [PATCH] Modified mod_mime and mod_negotiation to prevent mod_negotiation from serving any multiview variant containing one or more 'unknown' filename extensions. In PR #8130, mod_negotiation was incorrectly serving index.html.zh.Big5 when better variants were available. The httpd.conf file on the failing server did not have an AddLanguage directive for .zh, which caused mod_mime to loose the file_type information it gleened from parsing the .html extension. The absence of any language preferences, either in the browser or configured on the server, caused mod_negotiation to consider all the variants equivalent. When that occurs, mod_negotiation picks the 'smallest' variant available, which just happened to be index.html.zh.Big5. [Bill Stoddard, Bill Rowe] PR #8130 PR: 8130 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/1.3.x@91243 13f79535-47bb-0310-9956-ffa450edef68 --- src/CHANGES | 14 +++++++ src/modules/standard/mod_mime.c | 57 +++++++++++++++----------- src/modules/standard/mod_negotiation.c | 53 ++++++++++++++++++++++-- 3 files changed, 98 insertions(+), 26 deletions(-) diff --git a/src/CHANGES b/src/CHANGES index 584af9fb793..c938de24b37 100644 --- a/src/CHANGES +++ b/src/CHANGES @@ -1,4 +1,18 @@ Changes with Apache 1.3.21 + *) Modified mod_mime and mod_negotiation to prevent mod_negotiation + from serving any multiview variant containing one or more + 'unknown' filename extensions. In PR #8130, mod_negotiation was + incorrectly serving index.html.zh.Big5 when better variants were + available. The httpd.conf file on the failing server did not have + an AddLanguage directive for .zh, which caused mod_mime to loose + the file_type information it gleened from parsing the .html + extension. The absence of any language preferences, either in + the browser or configured on the server, caused mod_negotiation + to consider all the variants equivalent. When that occurs, + mod_negotiation picks the 'smallest' variant available, which + just happened to be index.html.zh.Big5. + [Bill Stoddard, Bill Rowe] PR #8130 + *) Security: Close autoindex /?M=D directory listing hole reported in bugtraq id 3009. In some configurations where multiviews and indexes are enabled for a directory, requesting URI /?M=D could diff --git a/src/modules/standard/mod_mime.c b/src/modules/standard/mod_mime.c index e996ad905e3..c6c254fbdca 100644 --- a/src/modules/standard/mod_mime.c +++ b/src/modules/standard/mod_mime.c @@ -615,27 +615,38 @@ static content_type *analyze_ct(pool *p, char *s) static int find_ct(request_rec *r) { - const char *fn = strrchr(r->filename, '/'); - mime_dir_config *conf = - (mime_dir_config *) ap_get_module_config(r->per_dir_config, &mime_module); + mime_dir_config *conf; + array_header *exception_list; + const char *fn; char *ext; - const char *orighandler = r->handler; const char *type; const char *charset = NULL; + int found_metadata = 0; if (S_ISDIR(r->finfo.st_mode)) { r->content_type = DIR_MAGIC_TYPE; return OK; } - /* TM -- FIXME - * if r->filename does not contain a '/', the following passes a null - * pointer to getword, causing a SEGV .. - */ + conf = (mime_dir_config *) ap_get_module_config(r->per_dir_config, + &mime_module); + + exception_list = ap_make_array(r->pool, 2, sizeof(char *)); - if (fn == NULL) { + /* Always drop the leading element */ + fn = strrchr(r->filename, '/'); + if (fn == NULL) fn = r->filename; - } + 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 **) ap_push_array(exception_list)) = ext; /* Parse filename extensions, which can be in any order */ while ((ext = ap_getword(r->pool, &fn, '.')) && *ext) { @@ -683,19 +694,19 @@ static int find_ct(request_rec *r) found = 1; } - /* This is to deal with cases such as foo.gif.bak, which we want - * to not have a type. So if we find an unknown extension, we - * zap the type/language/encoding and reset the handler - */ - - if (!found) { - r->content_type = NULL; - r->content_language = NULL; - r->content_languages = NULL; - r->content_encoding = NULL; - r->handler = orighandler; - charset = NULL; - } + if (found) + found_metadata = 1; + else + *((const char **) ap_push_array(exception_list)) = ext; + } + + /* Need to see a notes entry on r for unrecognized elements. + * Somebody better claim them! If we did absolutly nothing, + * skip the notes to alert mod_negotiation we are clueless. + */ + if (found_metadata) { + ap_table_setn(r->notes, "ap-mime-exceptions-list", + (void *) exception_list); } if (r->content_type) { diff --git a/src/modules/standard/mod_negotiation.c b/src/modules/standard/mod_negotiation.c index 629660b8f04..44863b076da 100644 --- a/src/modules/standard/mod_negotiation.c +++ b/src/modules/standard/mod_negotiation.c @@ -922,10 +922,10 @@ static int read_types_multi(negotiation_state *neg) forbidden.all = 1; while ((dir_entry = readdir(dirp))) { + array_header *exception_list; request_rec *sub_req; /* Do we have a match? */ - if (strncmp(dir_entry->d_name, filp, prefix_len)) { continue; } @@ -955,7 +955,55 @@ static int read_types_multi(negotiation_state *neg) else if (sub_req->status == HTTP_FORBIDDEN) forbidden.any = 1; - if (sub_req->status != HTTP_OK || !sub_req->content_type) { + /* + * mod_mime will _always_ provide us the base name in the + * ap-mime-exception-list, if it processed anything. If + * this list is empty, give up immediately, there was + * nothing interesting. For example, looking at the files + * readme.txt and readme.foo, we will throw away .foo if + * it's an insignificant file (e.g. did not identify a + * language, charset, encoding, content type or handler,) + */ + exception_list = + (array_header *) ap_table_get(sub_req->notes, + "ap-mime-exceptions-list"); + if (!exception_list) { + ap_destroy_sub_req(sub_req); + continue; + } + + /* + * Simple enough for now, every unreconized bit better match + * our base name. When we break up our base name and allow + * index.en to match index.html.en, this gets tricker. + * XXX: index.html.foo won't be caught by testing index.html + * since the exceptions result is index.foo - this should be + * fixed as part of a new match-parts logic here. + */ + { + char *base = ap_array_pstrcat(sub_req->pool, exception_list, '.'); + int base_len = strlen(base); + if (base_len > prefix_len +#ifdef CASE_BLIND_FILESYSTEM + || strncasecmp(base, filp, base_len) +#else + || strncmp(base, filp, base_len) +#endif + || (prefix_len > base_len && filp[base_len] != '.')) { + /* + * Something you don't know is, something you don't know... + */ + ap_destroy_sub_req(sub_req); + continue; + } + } + + /* + * ###: be warned, the _default_ content type is already + * picked up here! If we failed the subrequest, or don't + * know what we are serving, then continue. + */ + if (sub_req->status != HTTP_OK || (!sub_req->content_type)) { ap_destroy_sub_req(sub_req); continue; } @@ -963,7 +1011,6 @@ static int read_types_multi(negotiation_state *neg) /* If it's a map file, we use that instead of the map * we're building... */ - if (((sub_req->content_type) && !strcmp(sub_req->content_type, MAP_FILE_MAGIC_TYPE)) || ((sub_req->handler) && -- 2.47.2