/* 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.
*/
/* * mod_negotiation.c: keeps track of MIME types the client is willing to * accept, and contains code to handle type arbitration. * * rst
*/
/* Commands --- configuring document caching on a per (virtual?) * server basis...
*/
typedefstruct { int forcelangpriority;
apr_array_header_t *language_priority;
} neg_dir_config;
/* forcelangpriority flags
*/ #define FLP_UNDEF 0 /* Same as FLP_DEFAULT, but base overrides */ #define FLP_NONE 1 /* Return 406, HTTP_NOT_ACCEPTABLE */ #define FLP_PREFER 2 /* Use language_priority rather than MC */ #define FLP_FALLBACK 4 /* Use language_priority rather than NA */
staticconst command_rec negotiation_cmds[] =
{
AP_INIT_FLAG("CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF, "Either 'on' or 'off' (default)"),
AP_INIT_ITERATE("LanguagePriority", set_language_priority, NULL,
OR_FILEINFO, "space-delimited list of MIME language abbreviations"),
AP_INIT_ITERATE("ForceLanguagePriority", set_force_priority, NULL,
OR_FILEINFO, "Force LanguagePriority elections, either None, or " "Fallback and/or Prefer"),
{NULL}
};
/* * Record of available info on a media type specified by the client * (we also use 'em for encodings and languages)
*/
typedefstruct accept_rec { char *name; /* MUST be lowercase */ float quality; float level; char *charset; /* for content-type only */
} accept_rec;
/* * Record of available info on a particular variant * * Note that a few of these fields are updated by the actual negotiation * code. These are: * * level_matched --- initialized to zero. Set to the value of level * if the client actually accepts this media type at that * level (and *not* if it got in on a wildcard). See level_cmp * below. * mime_stars -- initialized to zero. Set to the number of stars * present in the best matching Accept header element. * 1 for star/star, 2 for type/star and 3 for * type/subtype. * * definite -- initialized to 1. Set to 0 if there is a match which * makes the variant non-definite according to the rules * in rfc2296.
*/
typedefstruct var_rec {
request_rec *sub_req; /* May be NULL (is, for map files) */ constchar *mime_type; /* MUST be lowercase */ constchar *file_name; /* Set to 'this' (for map file body content) */
apr_off_t body; /* Only for map file body content */ constchar *content_encoding;
apr_array_header_t *content_languages; /* list of lang. for this variant */ constchar *content_charset; constchar *description;
/* The next five items give the quality values for the dimensions * of negotiation for this variant. They are obtained from the * appropriate header lines, except for source_quality, which * is obtained from the variant itself (the 'qs' parameter value * from the variant's mime-type). Apart from source_quality, * these values are set when we find the quality for each variant * (see best_match()). source_quality is set from the 'qs' parameter * of the variant description or mime type: see set_mime_fields().
*/ float lang_quality; /* quality of this variant's language */ float encoding_quality; /* ditto encoding */ float charset_quality; /* ditto charset */ float mime_type_quality; /* ditto media type */ float source_quality; /* source quality for this variant */
/* Now some special values */ float level; /* Auxiliary to content-type... */
apr_off_t bytes; /* content length, if known */ int lang_index; /* Index into LanguagePriority list */ int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
/* Above are all written-once properties of the variant. The * three fields below are changed during negotiation:
*/
float level_matched; int mime_stars; int definite;
} var_rec;
/* Something to carry around the state of negotiation (and to keep * all of this thread-safe)...
*/
typedefstruct {
apr_pool_t *pool;
request_rec *r;
neg_dir_config *conf; char *dir_name; int accept_q; /* 1 if an Accept item has a q= param */ float default_lang_quality; /* fiddle lang q for variants with no lang */
/* the array pointers below are NULL if the corresponding accept * headers are not present
*/
apr_array_header_t *accepts; /* accept_recs */
apr_array_header_t *accept_encodings; /* accept_recs */
apr_array_header_t *accept_charsets; /* accept_recs */
apr_array_header_t *accept_langs; /* accept_recs */
apr_array_header_t *avail_vars; /* available variants */
int count_multiviews_variants; /* number of variants found on disk */
int is_transparent; /* 1 if this resource is trans. negotiable */
int dont_fiddle_headers; /* 1 if we may not fiddle with accept hdrs */ int ua_supports_trans; /* 1 if ua supports trans negotiation */ int send_alternates; /* 1 if we want to send an Alternates header */ int may_choose; /* 1 if we may choose a variant for the client */ int use_rvsa; /* 1 if we must use RVSA/1.0 negotiation algo */
} negotiation_state;
/* A few functions to manipulate var_recs. * Cleaning out the fields...
*/
/* Create a variant list validator in r using info from vlistr. */
staticvoid set_vlist_validator(request_rec *r, request_rec *vlistr)
{ /* Calculating the variant list validator is similar to * calculating an etag for the source of the variant list * information, so we use ap_make_etag(). Note that this * validator can be 'weak' in extreme case.
*/
ap_update_mtime(vlistr, vlistr->finfo.mtime);
r->vlist_validator = ap_make_etag(vlistr, 0);
/* ap_set_etag will later take r->vlist_validator into account * when creating the etag header
*/
}
/***************************************************************** * * Parsing (lists of) media types and their parameters, as seen in * HTTPD header lines and elsewhere.
*/
/* * parse quality value. atof(3) is not well-usable here, because it * depends on the locale (argh). * * However, RFC 2616 states: * 3.9 Quality Values * * [...] HTTP/1.1 applications MUST NOT generate more than three digits * after the decimal point. User configuration of these values SHOULD also * be limited in this fashion. * * qvalue = ( "0" [ "." 0*3DIGIT ] ) * | ( "1" [ "." 0*3("0") ] ) * * This is quite easy. If the supplied string doesn't match the above * definition (loosely), we simply return 1 (same as if there's no qvalue)
*/
/* be tolerant and accept qvalues without leading zero * (also for backwards compat, where atof() was in use)
*/ if (*string != '.' && *string++ != '0') { return 1.0f;
}
if (*string == '.') { /* better only one division later, than dealing with fscking * IEEE format 0.1 factors ...
*/ int i = 0;
if (*++string >= '0' && *string <= '9') {
i += (*string - '0') * 100;
if (*++string >= '0' && *string <= '9') {
i += (*string - '0') * 10;
if (*++string > '0' && *string <= '9') {
i += (*string - '0');
}
}
}
return (float)i / 1000.0f;
}
return 0.0f;
}
/* * Get a single mime type entry --- one media type and parameters; * enter the values we recognize into the argument accept_rec
*/
/* * Note that this handles what I gather is the "old format", * * Accept: text/html text/plain moo/zot * * without any compatibility kludges --- if the token after the * MIME type begins with a semicolon, we know we're looking at parms, * otherwise, we know we aren't. (So why all the pissing and moaning * in the CERN server code? I must be missing something).
*/
result->name = ap_get_token(p, &accept_line, 0);
ap_str_tolower(result->name); /* You want case insensitive, * you'll *get* case insensitive.
*/
/* KLUDGE!!! Default HTML to level 2.0 unless the browser * *explicitly* says something else.
*/
/***************************************************************** * * Dealing with header lines ... * * Accept, Accept-Charset, Accept-Language and Accept-Encoding * are handled by do_header_line() - they all have the same * basic structure of a list of items of the format * name; q=N; charset=TEXT * * where charset is only valid in Accept.
*/
/* This is possibly overkill for some servers, heck, we have * only 33 index.html variants in docs/docroot (today). * Make this configurable?
*/
new->avail_vars = apr_array_make(r->pool, 40, sizeof(var_rec));
/* First, default to no TCN, no Alternates, and the original Apache * negotiation algorithm with fiddles for broken browser configs. * * To save network bandwidth, we do not configure to send an * Alternates header to the user agent by default. User * agents that want an Alternates header for agent-driven * negotiation will have to request it by sending an * appropriate Negotiate header.
*/
neg->ua_supports_trans = 0;
neg->send_alternates = 0;
neg->may_choose = 1;
neg->use_rvsa = 0;
neg->dont_fiddle_headers = 0;
if (!negotiate) return;
if (strcmp(negotiate, "trans") == 0) { /* Lynx 2.7 and 2.8 send 'negotiate: trans' even though they * do not support transparent content negotiation, so for Lynx we * ignore the negotiate header when its contents are exactly "trans". * If future versions of Lynx ever need to say 'negotiate: trans', * they can send the equivalent 'negotiate: trans, trans' instead * to avoid triggering the workaround below.
*/ constchar *ua = apr_table_get(r->headers_in, "User-Agent");
if (ua && (strncmp(ua, "Lynx", 4) == 0)) return;
}
neg->may_choose = 0; /* An empty Negotiate would require 300 response */
while ((tok = ap_get_list_item(neg->pool, &negotiate)) != NULL) {
/* The user agent supports transparent negotiation */
neg->ua_supports_trans = 1;
/* Send-alternates could be configurable, but note * that it must be 1 if we have 'vlist' in the * negotiate header.
*/
neg->send_alternates = 1;
if (strcmp(tok, "1.0") == 0) { /* we may use the RVSA/1.0 algorithm, configure for it */
neg->may_choose = 1;
neg->use_rvsa = 1;
neg->dont_fiddle_headers = 1;
} elseif (tok[0] == '*') { /* we may use any variant selection algorithm, configure * to use the Apache algorithm
*/
neg->may_choose = 1;
/* We disable header fiddles on the assumption that a * client sending Negotiate knows how to send correct * headers which don't need fiddling.
*/
neg->dont_fiddle_headers = 1;
}
}
}
/* Sometimes clients will give us no Accept info at all; this routine sets * up the standard default for that case, and also arranges for us to be * willing to run a CGI script if we find one. (In fact, we set up to * dramatically prefer CGI scripts in cases where that's appropriate, * e.g., POST or when URI includes query args or extra path info).
*/ staticvoid maybe_add_default_accepts(negotiation_state *neg, int prefer_scripts)
{
accept_rec *new_accept;
if (!neg->accepts) {
neg->accepts = apr_array_make(neg->pool, 4, sizeof(accept_rec));
/***************************************************************** * * Parsing type-map files, in Roy's meta/http format augmented with * #-comments.
*/
do { if (apr_file_gets(buffer, MAX_STRING_LEN, map) != APR_SUCCESS) { return header_eof;
}
} while (buffer[0] == '#');
/* If blank, just return it --- this ends information on this variant */
for (cp = buffer; apr_isspace(*cp); ++cp) { continue;
}
if (*cp == '\0') { return header_sep;
}
/* If non-blank, go looking for header lines, but note that we still * have to treat comments specially...
*/
cp += strlen(cp);
/* We need to shortcut the rest of this block following the Body: * tag - we will not look for continutation after this line.
*/ if (!ap_cstr_casecmpn(buffer, "Body:", 5)) return header_seen;
while (apr_file_getc(&c, map) != APR_EOF) { if (c == '#') { /* Comment line */ while (apr_file_getc(&c, map) != APR_EOF && c != '\n') { continue;
}
} elseif (apr_isspace(c)) { /* Leading whitespace. POSSIBLE continuation line * Also, possibly blank --- if so, we ungetc() the final newline * so that we will pick up the blank line the next time 'round.
*/
while (c != '\n' && apr_isspace(c)) { if (apr_file_getc(&c, map) != APR_SUCCESS) { break;
}
}
apr_file_ungetc(c, map);
if (c == '\n') { return header_seen; /* Blank line */
}
/* We are at the first character following a body:tag\n entry * Suck in the body, then backspace to the first char after the * closing tag entry. If we fail to read, find the tag or back * up then we have a hosed file, so give up already
*/
--*len; /* Reserve space for '\0' */ if (apr_file_read(map, buffer, len) != APR_SUCCESS) { return -1;
}
buffer[*len] = '\0';
endbody = ap_strstr(buffer, tag); if (!endbody) { return -1;
}
bodylen = endbody - buffer;
endbody += strlen(tag); /* Skip all the trailing cruft after the end tag to the next line */ while (*endbody) { if (*endbody == '\n') {
++endbody; break;
}
++endbody;
}
/* Give the caller back the actual body's file offset and length */
*len = bodylen; return pos - (endbody - buffer);
}
/* Stripping out RFC822 comments */
staticvoid strip_paren_comments(char *hdr)
{ /* Hmmm... is this correct? In Roy's latest draft, (comments) can nest! */ /* Nope, it isn't correct. Fails to handle backslash escape as well. */
while (*hdr) { if (*hdr == '"') {
hdr = strchr(hdr, '"'); if (hdr == NULL) { return;
}
++hdr;
} elseif (*hdr == '(') { while (*hdr && *hdr != ')') {
*hdr++ = ' ';
}
if (!*cp) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00681) "Syntax error in type map, no ':' in %s for header %s",
r->filename, header); return NULL;
}
do {
++cp;
} while (apr_isspace(*cp));
if (!*cp) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00682) "Syntax error in type map --- no header body: %s for %s",
r->filename, header); return NULL;
}
clean_var_rec(&mime_info);
has_content = 0;
}
} while (hstate != header_eof);
if (map_)
apr_file_close(map_);
set_vlist_validator(r, rr);
return OK;
}
/* Sort function used by read_types_multi. */ staticint variantsortf(var_rec *a, var_rec *b)
{ /* First key is the source quality, sort in descending order. */
/* XXX: note that we currently implement no method of setting the * source quality for multiviews variants, so we are always comparing * 1.0 to 1.0 for now
*/ if (a->source_quality < b->source_quality) return 1; if (a->source_quality > b->source_quality) return -1;
/* Second key is the variant name */ return strcmp(a->file_name, b->file_name);
}
/***************************************************************** * * Same as read_type_map, except we use a filtered directory listing * as the map...
*/
/* Do we have a match? */ #ifdef CASE_BLIND_FILESYSTEM if (strncasecmp(dirent.name, filp, prefix_len)) { #else if (strncmp(dirent.name, filp, prefix_len)) { #endif continue;
} if (dirent.name[prefix_len] != '.') { continue;
}
/* Don't negotiate directories and other unusual files * Really shouldn't see anything but DIR/LNK/REG here, * and we aught to discover if the LNK was interesting. * * Of course, this only helps platforms that capture the * the filetype in apr_dir_read(), which most can once * they are optimized with some magic [it's known to the * dirent, not associated to the inode, on most FS's.]
*/ if ((dirent.valid & APR_FINFO_TYPE) && (dirent.filetype == APR_DIR)) continue;
/* Ok, something's here. Maybe nothing useful. Remember that * we tried, if we completely fail, so we can reject the request!
*/
anymatch = 1;
/* See if it's something which we have access to, and which * has a known type and encoding.
*/
sub_req = ap_sub_req_lookup_dirent(&dirent, r, AP_SUBREQ_MERGE_ARGS,
NULL);
/* Double check, we still don't multi-resolve non-ordinary files
*/ if (sub_req->finfo.filetype != APR_REG) { /* XXX sub req not destroyed -- may be a bug/unintentional ? */ continue;
}
/* If it has a handler, we'll pretend it's a CGI script, * since that's a good indication of the sort of thing it * might be doing.
*/ if (sub_req->handler && !sub_req->content_type) {
ap_set_content_type_ex(sub_req, CGI_MAGIC_TYPE, 1);
}
/* * 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 =
(apr_array_header_t *)apr_table_get(sub_req->notes, "ap-mime-exceptions-list");
if (!exception_list) {
ap_destroy_sub_req(sub_req); continue;
}
/* Each unregonized bit better match our base name, in sequence. * A test of index.html.foo will match index.foo or index.html.foo, * but it will never transpose the segments and allow index.foo.html * because that would introduce too much CPU consumption. Better that * we don't attempt a many-to-many match here.
*/
{ int nexcept = exception_list->nelts; char **cur_except = (char**)exception_list->elts; char *segstart = filp, *segend, saveend;
while (*segstart && nexcept) { if (!(segend = strchr(segstart, '.')))
segend = strchr(segstart, '\0');
saveend = *segend;
*segend = '\0';
#ifdef CASE_BLIND_FILESYSTEM if (strcasecmp(segstart, *cur_except) == 0) { #else if (strcmp(segstart, *cur_except) == 0) { #endif
--nexcept;
++cur_except;
}
if (!saveend) break;
*segend = saveend;
segstart = segend + 1;
}
if (nexcept) { /* Something you don't know is, something you don't know...
*/
ap_destroy_sub_req(sub_req); continue;
}
}
/* * 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;
}
/* 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) &&
!strcmp(sub_req->handler, "type-map"))) {
/* We found some file names that matched. None could be served. * Rather than fall out to autoindex or some other mapper, this * request must die.
*/ if (anymatch && !neg->avail_vars->nelts) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00687) "Negotiation: discovered file(s) matching request: %s" " (None could be negotiated).",
r->filename); return HTTP_NOT_FOUND;
}
set_vlist_validator(r, r);
/* Sort the variants into a canonical order. The negotiation * result sometimes depends on the order of the variants. By * sorting the variants into a canonical order, rather than using * the order in which readdir() happens to return them, we ensure * that the negotiation result will be consistent over filesystem * backup/restores and over all mirror sites.
*/
/***************************************************************** * And now for the code you've been waiting for... actually * finding a match to the client's requirements.
*/
/* Matching MIME types ... the star/star and foo/star commenting conventions * are implemented here. (You know what I mean by star/star, but just * try mentioning those three characters in a C comment). Using strcmp() * is legit, because everything has already been smashed to lowercase. * * Note also that if we get an exact match on the media type, we update * level_matched for use in level_cmp below... * * We also give a value for mime_stars, which is used later. It should * be 1 for star/star, 2 for type/star and 3 for type/subtype.
*/
staticint mime_match(accept_rec *accept_r, var_rec *avail)
{ constchar *accept_type = accept_r->name; constchar *avail_type = avail->mime_type; int len = strlen(accept_type);
/* This code implements a piece of the tie-breaking algorithm between * variants of equal quality. This piece is the treatment of variants * of the same base media type, but different levels. What we want to * return is the variant at the highest level that the client explicitly * claimed to accept. * * If all the variants available are at a higher level than that, or if * the client didn't say anything specific about this media type at all * and these variants just got in on a wildcard, we prefer the lowest * level, on grounds that that's the one that the client is least likely * to choke on. * * (This is all motivated by treatment of levels in HTML --- we only * want to give level 3 to browsers that explicitly ask for it; browsers * that don't, including HTTP/0.9 browsers that only get the implicit * "Accept: * / *" [space added to avoid confusing cpp --- no, that * syntax doesn't really work] should get HTML2 if available). * * (Note that this code only comes into play when we are choosing among * variants of equal quality, where the draft standard gives us a fair * bit of leeway about what to do. It ain't specified by the standard; * rather, it is a choice made by this server about what to do in cases * where the standard does not specify a unique course of action).
*/
staticint level_cmp(var_rec *var1, var_rec *var2)
{ /* Levels are only comparable between matching media types */
if (var1->is_pseudo_html && !var2->is_pseudo_html) { return 0;
}
if (!var1->is_pseudo_html && strcmp(var1->mime_type, var2->mime_type)) { return 0;
} /* The result of the above if statements is that, if we get to * here, both variants have the same mime_type or both are * pseudo-html.
*/
/* Take highest level that matched, if either did match. */
if (var1->level_matched > var2->level_matched) { return 1;
} if (var1->level_matched < var2->level_matched) { return -1;
}
/* Neither matched. Take lowest level, if there's a difference. */
if (var1->level < var2->level) { return 1;
} if (var1->level > var2->level) { return -1;
}
/* Tied */
return 0;
}
/* Finding languages. The main entry point is set_language_quality() * which is called for each variant. It sets two elements in the * variant record: * language_quality - the 'q' value of the 'best' matching language * from Accept-Language: header (HTTP/1.1) * lang_index - Non-negotiated language priority, using * position of language on the Accept-Language: * header, if present, else LanguagePriority * directive order. * * When we do the variant checking for best variant, we use language * quality first, and if a tie, language_index next (this only applies * when _not_ using the RVSA/1.0 algorithm). If using the RVSA/1.0 * algorithm, lang_index is never used. * * set_language_quality() calls find_lang_index() and find_default_index() * to set lang_index.
*/
staticint find_lang_index(apr_array_header_t *accept_langs, char *lang)
{ constchar **alang; int i;
if (!lang || !accept_langs) { return -1;
}
alang = (constchar **) accept_langs->elts;
for (i = 0; i < accept_langs->nelts; ++i) { if (!ap_cstr_casecmpn(lang, *alang, strlen(*alang))) { return i;
}
alang += (accept_langs->elt_size / sizeof(char*));
}
return -1;
}
/* set_default_lang_quality() sets the quality we apply to variants * which have no language assigned to them. If none of the variants * have a language, we are not negotiating on language, so all are * acceptable, and we set the default q value to 1.0. However if * some of the variants have languages, we set this default to 0.0001. * The value of this default will be applied to all variants with * no explicit language -- which will have the effect of making them * acceptable, but only if no variants with an explicit language * are acceptable. The default q value set here is assigned to variants * with no language type in set_language_quality(). * * Note that if using the RVSA/1.0 algorithm, we don't use this * fiddle.
*/
if (!neg->dont_fiddle_headers) { for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j]; if (variant->content_languages &&
variant->content_languages->nelts) {
neg->default_lang_quality = 0.0001f; return;
}
}
}
neg->default_lang_quality = 1.0f;
}
/* Set the language_quality value in the variant record. Also * assigns lang_index for ForceLanguagePriority. * * To find the language_quality value, we look for the 'q' value * of the 'best' matching language on the Accept-Language * header. The 'best' match is the language on Accept-Language * header which matches the language of this variant either fully, * or as far as the prefix marker (-). If two or more languages * match, use the longest string from the Accept-Language header * (see HTTP/1.1 [14.4]) * * When a variant has multiple languages, we find the 'best' * match for each variant language tag as above, then select the * one with the highest q value. Because both the accept-header * and variant can have multiple languages, we now have a hairy * loop-within-a-loop here. * * If the variant has no language and we have no Accept-Language * items, leave the quality at 1.0 and return. * * If the variant has no language, we use the default as set by * set_default_lang_quality() (1.0 if we are not negotiating on * language, 0.001 if we are). * * Following the setting of the language quality, we drop through to * set the old 'lang_index'. This is set based on either the order * of the languages on the Accept-Language header, or the * order on the LanguagePriority directive. This is only used * in the negotiation if the language qualities tie.
*/
staticvoid set_language_quality(negotiation_state *neg, var_rec *variant)
{ int forcepriority = neg->conf->forcelangpriority; if (forcepriority == FLP_UNDEF) {
forcepriority = FLP_DEFAULT;
}
if (!variant->content_languages || !variant->content_languages->nelts) { /* This variant has no content-language, so use the default * quality factor for variants with no content-language * (previously set by set_default_lang_quality()). * Leave the factor alone (it remains at 1.0) when we may not fiddle * with the headers.
*/ if (!neg->dont_fiddle_headers) {
variant->lang_quality = neg->default_lang_quality;
} return;
} else { /* Variant has one (or more) languages. Look for the best * match. We do this by going through each language on the * variant description looking for a match on the * Accept-Language header. The best match is the longest * matching language on the header. The final result is the * best q value from all the languages on the variant * description.
*/
if (!neg->accept_langs) { /* no accept-language header makes the variant indefinite */
variant->definite = 0;
} else { /* There is an accept-language with 0 or more items */
accept_rec *accs = (accept_rec *) neg->accept_langs->elts;
accept_rec *best = NULL, *star = NULL;
accept_rec *bestthistag; char *lang, *p; float fiddle_q = 0.0f; int any_match_on_star = 0; int i, j;
apr_size_t alen, longest_lang_range_len;
for (j = 0; j < variant->content_languages->nelts; ++j) {
p = NULL;
bestthistag = NULL;
longest_lang_range_len = 0;
/* lang is the variant's language-tag, which is the one * we are allowed to use the prefix of in HTTP/1.1
*/
lang = ((char **) (variant->content_languages->elts))[j];
/* now find the best (i.e. longest) matching * Accept-Language header language. We put the best match * for this tag in bestthistag. We cannot update the * overall best (based on q value) because the best match * for this tag is the longest language item on the accept * header, not necessarily the highest q.
*/ for (i = 0; i < neg->accept_langs->nelts; ++i) { if (!strcmp(accs[i].name, "*")) { if (!star) {
star = &accs[i];
} continue;
} /* Find language. We match if either the variant * language tag exactly matches the language range * from the accept header, or a prefix of the variant * language tag up to a '-' character matches the * whole of the language range in the Accept-Language * header. Note that HTTP/1.x allows any number of * '-' characters in a tag or range, currently only * tags with zero or one '-' characters are defined * for general use (see rfc1766). * * We only use language range in the Accept-Language * header the best match for the variant language tag * if it is longer than the previous best match.
*/
if (!bestthistag && !neg->dont_fiddle_headers) { /* The next bit is a fiddle. Some browsers might * be configured to send more specific language * ranges than desirable. For example, an * Accept-Language of en-US should never match * variants with languages en or en-GB. But US * English speakers might pick en-US as their * language choice. So this fiddle checks if the * language range has a prefix, and if so, it * matches variants which match that prefix with a * priority of 0.001. So a request for en-US would * match variants of types en and en-GB, but at * much lower priority than matches of en-US * directly, or of any other language listed on * the Accept-Language header. Note that this * fiddle does not handle multi-level prefixes.
*/ if ((p = strchr(accs[i].name, '-'))) { int plen = p - accs[i].name;
if (!strncmp(lang, accs[i].name, plen)) {
fiddle_q = 0.001f;
}
}
}
} /* Finished looking at Accept-Language headers, the best * (longest) match is in bestthistag, or NULL if no match
*/ if (!best ||
(bestthistag && bestthistag->quality > best->quality)) {
best = bestthistag;
}
/* See if the tag matches on a * in the Accept-Language * header. If so, record this fact for later use
*/ if (!bestthistag && star) {
any_match_on_star = 1;
}
}
/* If one of the language tags of the variant matched on *, we * need to see if its q is better than that of any non-* match * on any other tag of the variant. If so the * match takes * precedence and the overall match is not definite.
*/ if ( any_match_on_star &&
((best && star->quality > best->quality) ||
(!best)) ) {
best = star;
variant->definite = 0;
}
variant->lang_quality = best ? best->quality : fiddle_q;
}
}
/* Handle the ForceDefaultLanguage overrides, based on the best match * to LanguagePriority order. The best match is the lowest index of * any LanguagePriority match.
*/ if (((forcepriority & FLP_PREFER)
&& (variant->lang_index < 0))
|| ((forcepriority & FLP_FALLBACK)
&& !variant->lang_quality))
{ int bestidx = -1; int j;
for (j = 0; j < variant->content_languages->nelts; ++j)
{ /* lang is the variant's language-tag, which is the one * we are allowed to use the prefix of in HTTP/1.1
*/ char *lang = ((char **) (variant->content_languages->elts))[j]; int idx;
/* If we wish to fallback or * we use our own LanguagePriority index.
*/
idx = find_lang_index(neg->conf->language_priority, lang); if ((idx >= 0) && ((bestidx == -1) || (idx < bestidx))) {
bestidx = idx;
}
}
if (bestidx >= 0) { if (variant->lang_quality) { if (forcepriority & FLP_PREFER) {
variant->lang_index = bestidx;
}
} else { if (forcepriority & FLP_FALLBACK) {
variant->lang_index = bestidx;
variant->lang_quality = .0001f;
variant->definite = 0;
}
}
}
}
}
/* Determining the content length --- if the map didn't tell us, * we have to do a stat() and remember for next time.
*/
/* For a given variant, find the best matching Accept: header * and assign the Accept: header's quality value to the * mime_type_quality field of the variant, for later use in * determining the best matching variant.
*/
staticvoid set_accept_quality(negotiation_state *neg, var_rec *variant)
{ int i;
accept_rec *accept_recs; float q = 0.0f; int q_definite = 1;
/* if no Accept: header, leave quality alone (will * remain at the default value of 1) * * XXX: This if is currently never true because of the effect of * maybe_add_default_accepts().
*/ if (!neg->accepts) { if (variant->mime_type && *variant->mime_type)
variant->definite = 0; return;
}
accept_recs = (accept_rec *) neg->accepts->elts;
/* * Go through each of the ranges on the Accept: header, * looking for the 'best' match with this variant's * content-type. We use the best match's quality * value (from the Accept: header) for this variant's * mime_type_quality field. * * The best match is determined like this: * type/type is better than type/ * is better than * / * * if match is type/type, use the level mime param if available
*/ for (i = 0; i < neg->accepts->nelts; ++i) {
accept_rec *type = &accept_recs[i]; int prev_mime_stars;
prev_mime_stars = variant->mime_stars;
if (!mime_match(type, variant)) { continue; /* didn't match the content type at all */
} else { /* did match - see if there were less or more stars than * in previous match
*/ if (prev_mime_stars == variant->mime_stars) { continue; /* more stars => not as good a match */
}
}
/* If we are allowed to mess with the q-values * and have no explicit q= parameters in the accept header, * make wildcards very low, so we have a low chance * of ending up with them if there's something better.
*/
/* For a given variant, find the 'q' value of the charset given * on the Accept-Charset line. If no charsets are listed, * assume value of '1'.
*/ staticvoid set_charset_quality(negotiation_state *neg, var_rec *variant)
{ int i;
accept_rec *accept_recs; constchar *charset = variant->content_charset;
accept_rec *star = NULL;
/* if no Accept-Charset: header, leave quality alone (will * remain at the default value of 1)
*/ if (!neg->accept_charsets) { if (charset && *charset)
variant->definite = 0; return;
}
if (charset == NULL || !*charset) { /* Charset of variant not known */
/* if not a text / * type, leave quality alone */ if (!(!strncmp(variant->mime_type, "text/", 5)
|| !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE)
|| !strcmp(variant->mime_type, INCLUDES_MAGIC_TYPE3)
)) return;
/* Don't go guessing if we are in strict header mode, * e.g. when running the rvsa, as any guess won't be reflected * in the variant list or content-location headers.
*/ if (neg->dont_fiddle_headers) return;
charset = "iso-8859-1"; /* The default charset for HTTP text types */
}
/* * Go through each of the items on the Accept-Charset header, * looking for a match with this variant's charset. If none * match, charset is unacceptable, so set quality to 0.
*/ for (i = 0; i < neg->accept_charsets->nelts; ++i) {
accept_rec *type = &accept_recs[i];
if (!strcmp(type->name, charset)) {
variant->charset_quality = type->quality; return;
} elseif (strcmp(type->name, "*") == 0) {
star = type;
}
} /* No explicit match */ if (star) {
variant->charset_quality = star->quality;
variant->definite = 0; return;
} /* If this variant is in charset iso-8859-1, the default is 1.0 */ if (strcmp(charset, "iso-8859-1") == 0) {
variant->charset_quality = 1.0f;
} else {
variant->charset_quality = 0.0f;
}
}
/* is_identity_encoding is included for back-compat, but does anyone * use 7bit, 8bin or binary in their var files??
*/
/* * set_encoding_quality determines whether the encoding for a particular * variant is acceptable for the user-agent. * * The rules for encoding are that if the user-agent does not supply * any Accept-Encoding header, then all encodings are allowed but a * variant with no encoding should be preferred. * If there is an empty Accept-Encoding header, then no encodings are * acceptable. If there is a non-empty Accept-Encoding header, then * any of the listed encodings are acceptable, as well as no encoding * unless the "identity" encoding is specifically excluded.
*/ staticvoid set_encoding_quality(negotiation_state *neg, var_rec *variant)
{
accept_rec *accept_recs; constchar *enc = variant->content_encoding;
accept_rec *star = NULL; float value_if_not_found = 0.0f; int i;
if (!neg->accept_encodings) { /* We had no Accept-Encoding header, assume that all * encodings are acceptable with a low quality, * but we prefer no encoding if available.
*/ if (!enc || is_identity_encoding(enc))
variant->encoding_quality = 1.0f; else
variant->encoding_quality = 0.5f;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.