mod_negotiation.c revision 5e657cb2e8af81202f9cb47de03fa9db5f0f22b9
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* Licensed to the Apache Software Foundation (ASF) under one or more
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * contributor license agreements. See the NOTICE file distributed with
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * this work for additional information regarding copyright ownership.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * The ASF licenses this file to You under the Apache License, Version 2.0
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * (the "License"); you may not use this file except in compliance with
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * the License. You may obtain a copy of the License at
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Unless required by applicable law or agreed to in writing, software
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * distributed under the License is distributed on an "AS IS" BASIS,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * See the License for the specific language governing permissions and
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * limitations under the License.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * mod_negotiation.c: keeps track of MIME types the client is willing to
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * accept, and contains code to handle type arbitration.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* Commands --- configuring document caching on a per (virtual?)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * server basis...
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbtypedef struct {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* forcelangpriority flags
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb#define FLP_UNDEF 0 /* Same as FLP_DEFAULT, but base overrides */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb#define FLP_PREFER 2 /* Use language_priority rather than MC */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb#define FLP_FALLBACK 4 /* Use language_priority rather than NA */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* env evaluation
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic void *create_neg_dir_config(apr_pool_t *p, char *dummy)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic void *merge_neg_dir_configs(apr_pool_t *p, void *basev, void *addv)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* give priority to the config in the subdirectory */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb new->forcelangpriority = (add->forcelangpriority != FLP_UNDEF)
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowestatic const char *set_language_priority(cmd_parms *cmd, void *n_,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char *lang)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char **langp;
b45c1c292ff1fa635004ae81fa691f8cb3cdda85rbb n->language_priority = apr_array_make(cmd->pool, 4, sizeof(char *));
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb langp = (const char **) apr_array_push(n->language_priority);
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic const char *set_force_priority(cmd_parms *cmd, void *n_, const char *w)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return "Cannot combine ForceLanguagePriority options with None";
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return "Cannot combine ForceLanguagePriority options None and "
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe return "Cannot combine ForceLanguagePriority options None and "
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe "Fallback";
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe return apr_pstrcat(cmd->pool, "Invalid ForceLanguagePriority option ",
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowestatic const char *cache_negotiated_docs(cmd_parms *cmd, void *dummy,
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe ap_set_module_config(cmd->server->module_config, &negotiation_module,
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe AP_INIT_FLAG("CacheNegotiatedDocs", cache_negotiated_docs, NULL, RSRC_CONF,
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe "Either 'on' or 'off' (default)"),
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe AP_INIT_ITERATE("LanguagePriority", set_language_priority, NULL,
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe "space-delimited list of MIME language abbreviations"),
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe AP_INIT_ITERATE("ForceLanguagePriority", set_force_priority, NULL,
4775dfc34c90fada8c7c4d6a57ed8a3114d55c2dtrawick "Force LanguagePriority elections, either None, or "
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe "Fallback and/or Prefer"),
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * Record of available info on a media type specified by the client
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * (we also use 'em for encodings and languages)
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowetypedef struct accept_rec {
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * Record of available info on a particular variant
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * Note that a few of these fields are updated by the actual negotiation
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * code. These are:
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * level_matched --- initialized to zero. Set to the value of level
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * if the client actually accepts this media type at that
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * level (and *not* if it got in on a wildcard). See level_cmp
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * mime_stars -- initialized to zero. Set to the number of stars
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * present in the best matching Accept header element.
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * definite -- initialized to 1. Set to 0 if there is a match which
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * makes the variant non-definite according to the rules
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * in rfc2296.
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowetypedef struct var_rec {
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe request_rec *sub_req; /* May be NULL (is, for map files) */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe const char *file_name; /* Set to 'this' (for map file body content) */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe apr_array_header_t *content_languages; /* list of lang. for this variant */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe const char *description;
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe /* The next five items give the quality values for the dimensions
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * of negotiation for this variant. They are obtained from the
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * appropriate header lines, except for source_quality, which
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * is obtained from the variant itself (the 'qs' parameter value
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * from the variant's mime-type). Apart from source_quality,
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * these values are set when we find the quality for each variant
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * (see best_match()). source_quality is set from the 'qs' parameter
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * of the variant description or mime type: see set_mime_fields().
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe float lang_quality; /* quality of this variant's language */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe float source_quality; /* source quality for this variant */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe /* Now some special values */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe int lang_index; /* Index into LanguagePriority list */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe /* Above are all written-once properties of the variant. The
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * three fields below are changed during negotiation:
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe/* Something to carry around the state of negotiation (and to keep
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * all of this thread-safe)...
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbtypedef struct {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb float default_lang_quality; /* fiddle lang q for variants with no lang */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* the array pointers below are NULL if the corresponding accept
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * headers are not present
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb apr_array_header_t *avail_vars; /* available variants */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int count_multiviews_variants; /* number of variants found on disk */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int is_transparent; /* 1 if this resource is trans. negotiable */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int dont_fiddle_headers; /* 1 if we may not fiddle with accept hdrs */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int ua_supports_trans; /* 1 if ua supports trans negotiation */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int send_alternates; /* 1 if we want to send an Alternates header */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int may_choose; /* 1 if we may choose a variant for the client */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int use_rvsa; /* 1 if we must use RVSA/1.0 negotiation algo */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* A few functions to manipulate var_recs.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Cleaning out the fields...
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe/* Initializing the relevant fields of a variant record from the
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * accept_info read out of its content-type, one way or another.
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowestatic void set_mime_fields(var_rec *var, accept_rec *mime_info)
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe var->is_pseudo_html = (!strcmp(var->mime_type, "text/html")
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe/* Create a variant list validator in r using info from vlistr. */
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowestatic void set_vlist_validator(request_rec *r, request_rec *vlistr)
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe /* Calculating the variant list validator is similar to
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * calculating an etag for the source of the variant list
c6ed63d53853f79bf8265c434d2e25c9db8a7fd8trawick * information, so we use ap_make_etag(). Note that this
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * validator can be 'weak' in extreme case.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* ap_set_etag will later take r->vlist_validator into account
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * when creating the etag header
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/*****************************************************************
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Parsing (lists of) media types and their parameters, as seen in
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * HTTPD header lines and elsewhere.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * parse quality value. atof(3) is not well-usable here, because it
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * depends on the locale (argh).
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * However, RFC 2616 states:
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * 3.9 Quality Values
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * [...] HTTP/1.1 applications MUST NOT generate more than three digits
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * after the decimal point. User configuration of these values SHOULD also
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * be limited in this fashion.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * qvalue = ( "0" [ "." 0*3DIGIT ] )
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * | ( "1" [ "." 0*3("0") ] )
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * This is quite easy. If the supplied string doesn't match the above
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * definition (loosely), we simply return 1 (same as if there's no qvalue)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return 1.0f;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* be tolerant and accept qvalues without leading zero
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * (also for backwards compat, where atof() was in use)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return 1.0f;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* better only one division later, than dealing with fscking
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * IEEE format 0.1 factors ...
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb int i = 0;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return (float)i / 1000.0f;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return 0.0f;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Get a single mime type entry --- one media type and parameters;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * enter the values we recognize into the argument accept_rec
4ca6cbe768b4e0917ac0b76333c26a7d5396d454trawickstatic const char *get_entry(apr_pool_t *p, accept_rec *result,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char *accept_line)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Note that this handles what I gather is the "old format",
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * without any compatibility kludges --- if the token after the
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * MIME type begins with a semicolon, we know we're looking at parms,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * otherwise, we know we aren't. (So why all the pissing and moaning
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * in the CERN server code? I must be missing something).
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb ap_str_tolower(result->name); /* You want case insensitive,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * you'll *get* case insensitive.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* KLUDGE!!! Default HTML to level 2.0 unless the browser
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * *explicitly* says something else.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb if (!strcmp(result->name, "text/html") && (result->level == 0.0)) {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Parameters ... */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Look for 'var = value' --- and make sure the var is in lcase. */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb for (cp = parm; (*cp && !apr_isspace(*cp) && *cp != '='); ++cp) {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb continue; /* No '='; just ignore it. */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/*****************************************************************
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Dealing with header lines ...
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowe * Accept, Accept-Charset, Accept-Language and Accept-Encoding
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * are handled by do_header_line() - they all have the same
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * basic structure of a list of items of the format
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * name; q=N; charset=TEXT
2fa5b5878e7567e2875807c3e2a2b3b0d3ef74bewrowe * where charset is only valid in Accept.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char *accept_line)
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe accept_recs = apr_array_make(p, 40, sizeof(accept_rec));
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe accept_rec *new = (accept_rec *) apr_array_push(accept_recs);
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe/* Given the text of the Content-Languages: line from the var map file,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * return an array containing the languages of this variant
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic apr_array_header_t *do_languages_line(apr_pool_t *p,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char **lang_line)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb apr_array_header_t *lang_recs = apr_array_make(p, 2, sizeof(char *));
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb while (**lang_line) {
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe/*****************************************************************
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Handling header lines from clients...
6fed20de38221f6f8a60c0ab1d907f1173c443f4wrowestatic negotiation_state *parse_accept_headers(request_rec *r)
6fed20de38221f6f8a60c0ab1d907f1173c443f4wrowe (negotiation_state *) apr_pcalloc(r->pool, sizeof(negotiation_state));
6fed20de38221f6f8a60c0ab1d907f1173c443f4wrowe new->conf = (neg_dir_config *)ap_get_module_config(r->per_dir_config,
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe new->dir_name = ap_make_dirstr_parent(r->pool, r->filename);
6fed20de38221f6f8a60c0ab1d907f1173c443f4wrowe new->accepts = do_header_line(r->pool, apr_table_get(hdrs, "Accept"));
6fed20de38221f6f8a60c0ab1d907f1173c443f4wrowe /* calculate new->accept_q value */
6fed20de38221f6f8a60c0ab1d907f1173c443f4wrowe do_header_line(r->pool, apr_table_get(hdrs, "Accept-Encoding"));
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb do_header_line(r->pool, apr_table_get(hdrs, "Accept-Language"));
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb do_header_line(r->pool, apr_table_get(hdrs, "Accept-Charset"));
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* This is possibly overkill for some servers, heck, we have
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * only 33 index.html variants in docs/docroot (today).
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Make this configurable?
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb new->avail_vars = apr_array_make(r->pool, 40, sizeof(var_rec));
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic void parse_negotiate_header(request_rec *r, negotiation_state *neg)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char *negotiate = apr_table_get(r->headers_in, "Negotiate");
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* First, default to no TCN, no Alternates, and the original Apache
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * negotiation algorithm with fiddles for broken browser configs.
ac06e54654494445fd3d39e90bd23b436b4f84ccwrowe * To save network bandwidth, we do not configure to send an
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Alternates header to the user agent by default. User
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * agents that want an Alternates header for agent-driven
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * negotiation will have to request it by sending an
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * appropriate Negotiate header.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Lynx 2.7 and 2.8 send 'negotiate: trans' even though they
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * do not support transparent content negotiation, so for Lynx we
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * ignore the negotiate header when its contents are exactly "trans".
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * If future versions of Lynx ever need to say 'negotiate: trans',
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * they can send the equivalent 'negotiate: trans, trans' instead
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * to avoid triggering the workaround below.
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe const char *ua = apr_table_get(r->headers_in, "User-Agent");
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb neg->may_choose = 0; /* An empty Negotiate would require 300 response */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb while ((tok = ap_get_list_item(neg->pool, &negotiate)) != NULL) {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* The user agent supports transparent negotiation */
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe /* Send-alternates could be configurable, but note
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe * that it must be 1 if we have 'vlist' in the
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * negotiate header.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* we may use the RVSA/1.0 algorithm, configure for it */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* we may use any variant selection algorithm, configure
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * to use the Apache algorithm
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* We disable header fiddles on the assumption that a
ac06e54654494445fd3d39e90bd23b436b4f84ccwrowe * client sending Negotiate knows how to send correct
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * headers which don't need fiddling.
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, APLOGNO(00680)
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe "dont_fiddle_headers=%d use_rvsa=%d ua_supports_trans=%d "
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe "send_alternates=%d, may_choose=%d",
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb neg->ua_supports_trans, neg->send_alternates, neg->may_choose);
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* Sometimes clients will give us no Accept info at all; this routine sets
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * up the standard default for that case, and also arranges for us to be
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * willing to run a CGI script if we find one. (In fact, we set up to
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * dramatically prefer CGI scripts in cases where that's appropriate,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * e.g., POST or when URI includes query args or extra path info).
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic void maybe_add_default_accepts(negotiation_state *neg,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb neg->accepts = apr_array_make(neg->pool, 4, sizeof(accept_rec));
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe new_accept = (accept_rec *) apr_array_push(neg->accepts);
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb new_accept = (accept_rec *) apr_array_push(neg->accepts);
ac06e54654494445fd3d39e90bd23b436b4f84ccwrowe/*****************************************************************
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Parsing type-map files, in Roy's meta/http format augmented with
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * #-comments.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* Reading RFC822-style header lines, ignoring #-comments and
ac06e54654494445fd3d39e90bd23b436b4f84ccwrowe * handling continuations.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic enum header_state get_header_line(char *buffer, int len, apr_file_t *map)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Get a noncommented line */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb if (apr_file_gets(buffer, MAX_STRING_LEN, map) != APR_SUCCESS) {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* If blank, just return it --- this ends information on this variant */
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe /* If non-blank, go looking for header lines, but note that we still
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe * have to treat comments specially...
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* We need to shortcut the rest of this block following the Body:
8aefbd756763807188d2e3ce336a8680e4893066wrowe * tag - we will not look for continutation after this line.
8aefbd756763807188d2e3ce336a8680e4893066wrowe if (c == '#') {
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* Comment line */
8aefbd756763807188d2e3ce336a8680e4893066wrowe while (apr_file_getc(&c, map) != APR_EOF && c != '\n') {
8aefbd756763807188d2e3ce336a8680e4893066wrowe else if (apr_isspace(c)) {
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* Leading whitespace. POSSIBLE continuation line
8aefbd756763807188d2e3ce336a8680e4893066wrowe * Also, possibly blank --- if so, we ungetc() the final newline
8aefbd756763807188d2e3ce336a8680e4893066wrowe * so that we will pick up the blank line the next time 'round.
8aefbd756763807188d2e3ce336a8680e4893066wrowe if (c == '\n') {
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* Continuation */
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe && c != '\n') {
ac06e54654494445fd3d39e90bd23b436b4f84ccwrowe /* Line beginning with something other than whitespace */
8aefbd756763807188d2e3ce336a8680e4893066wrowestatic apr_off_t get_body(char *buffer, apr_size_t *len, const char *tag,
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* We are at the first character following a body:tag\n entry
8aefbd756763807188d2e3ce336a8680e4893066wrowe * Suck in the body, then backspace to the first char after the
8aefbd756763807188d2e3ce336a8680e4893066wrowe * closing tag entry. If we fail to read, find the tag or back
8aefbd756763807188d2e3ce336a8680e4893066wrowe * up then we have a hosed file, so give up already
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe /* put a copy of the tag *after* the data read from the file
ac06e54654494445fd3d39e90bd23b436b4f84ccwrowe * so that strstr() will find something with no reliance on
a8d11d78181478da6a672f7fbc58b8d523351f49wrowe * terminating '\0'
2fa5b5878e7567e2875807c3e2a2b3b0d3ef74bewrowe /* Skip all the trailing cruft after the end tag to the next line */
8aefbd756763807188d2e3ce336a8680e4893066wrowe if (apr_file_seek(map, APR_CUR, &pos) != APR_SUCCESS) {
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* Give the caller back the actual body's file offset and length */
8aefbd756763807188d2e3ce336a8680e4893066wrowe/* Stripping out RFC822 comments */
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* Hmmm... is this correct? In Roy's latest draft, (comments) can nest! */
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe /* Nope, it isn't correct. Fails to handle backslash escape as well. */
4fca95918a9c0ae93593806544b425d0adc2fcc3wrowe while (*hdr) {
8aefbd756763807188d2e3ce336a8680e4893066wrowe/* Getting to a header body from the header */
8aefbd756763807188d2e3ce336a8680e4893066wrowestatic char *lcase_header_name_return_body(char *header, request_rec *r)
8aefbd756763807188d2e3ce336a8680e4893066wrowe ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00681)
8aefbd756763807188d2e3ce336a8680e4893066wrowe "Syntax error in type map, no ':' in %s for header %s",
8aefbd756763807188d2e3ce336a8680e4893066wrowe ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00682)
8aefbd756763807188d2e3ce336a8680e4893066wrowe "Syntax error in type map --- no header body: %s for %s",
8aefbd756763807188d2e3ce336a8680e4893066wrowestatic int read_type_map(apr_file_t **map, negotiation_state *neg,
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* We are not using multiviews */
8aefbd756763807188d2e3ce336a8680e4893066wrowe if ((status = apr_file_open(map, rr->filename, APR_READ | APR_BUFFERED,
8aefbd756763807188d2e3ce336a8680e4893066wrowe ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00683)
8aefbd756763807188d2e3ce336a8680e4893066wrowe if (APR_STATUS_IS_ENOTDIR(status) || APR_STATUS_IS_ENOENT(status)) {
8aefbd756763807188d2e3ce336a8680e4893066wrowe hstate = get_header_line(buffer, MAX_STRING_LEN, *map);
b29f87f4b6c6886a04dccc296177a7033f70dfedtrawick char *body1 = lcase_header_name_return_body(buffer, neg->r);
8aefbd756763807188d2e3ce336a8680e4893066wrowe const char *body;
8aefbd756763807188d2e3ce336a8680e4893066wrowe mime_info.file_name = ap_get_token(neg->pool, &body, 0);
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe if (apr_strtoff(&number, body1, &errp, 10) != APR_SUCCESS
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00684)
8aefbd756763807188d2e3ce336a8680e4893066wrowe "Parse error in type map, Content-Length: "
8aefbd756763807188d2e3ce336a8680e4893066wrowe "'%s' in %s is invalid.",
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe mime_info.content_languages = do_languages_line(neg->pool,
8aefbd756763807188d2e3ce336a8680e4893066wrowe mime_info.content_encoding = ap_get_token(neg->pool, &body, 0);
8aefbd756763807188d2e3ce336a8680e4893066wrowe if ((mime_info.body = get_body(buffer, &len, tag, *map)) < 0) {
8aefbd756763807188d2e3ce336a8680e4893066wrowe ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00685)
8aefbd756763807188d2e3ce336a8680e4893066wrowe "Syntax error in type map, no end tag '%s'"
8aefbd756763807188d2e3ce336a8680e4893066wrowe "found in %s for Body: content.",
8aefbd756763807188d2e3ce336a8680e4893066wrowe mime_info.file_name = apr_filepath_name_get(rr->filename);
8aefbd756763807188d2e3ce336a8680e4893066wrowe memcpy(new_var, (void *) &mime_info, sizeof(var_rec));
8aefbd756763807188d2e3ce336a8680e4893066wrowe/* Sort function used by read_types_multi. */
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* First key is the source quality, sort in descending order. */
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe /* XXX: note that we currently implement no method of setting the
8aefbd756763807188d2e3ce336a8680e4893066wrowe * source quality for multiviews variants, so we are always comparing
8aefbd756763807188d2e3ce336a8680e4893066wrowe * 1.0 to 1.0 for now
8aefbd756763807188d2e3ce336a8680e4893066wrowe /* Second key is the variant name */
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe/*****************************************************************
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe * Same as read_type_map, except we use a filtered directory listing
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe * as the map...
948096a99010fccf648814fecf38f75c689172d7wrowe /* Only absolute paths here */
948096a99010fccf648814fecf38f75c689172d7wrowe ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(00686)
948096a99010fccf648814fecf38f75c689172d7wrowe "cannot read directory for multi: %s", neg->dir_name);
948096a99010fccf648814fecf38f75c689172d7wrowe while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
948096a99010fccf648814fecf38f75c689172d7wrowe /* Do we have a match? */
948096a99010fccf648814fecf38f75c689172d7wrowe /* Don't negotiate directories and other unusual files
948096a99010fccf648814fecf38f75c689172d7wrowe * Really shouldn't see anything but DIR/LNK/REG here,
948096a99010fccf648814fecf38f75c689172d7wrowe * and we aught to discover if the LNK was interesting.
3f5b4558f5410fdac5d6feed7aab0c3668f9cd13wrowe * Of course, this only helps platforms that capture the
3f5b4558f5410fdac5d6feed7aab0c3668f9cd13wrowe * the filetype in apr_dir_read(), which most can once
3f5b4558f5410fdac5d6feed7aab0c3668f9cd13wrowe * they are optimized with some magic [it's known to the
3f5b4558f5410fdac5d6feed7aab0c3668f9cd13wrowe * dirent, not associated to the inode, on most FS's.]
3f5b4558f5410fdac5d6feed7aab0c3668f9cd13wrowe if ((dirent.valid & APR_FINFO_TYPE) && (dirent.filetype == APR_DIR))
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe /* Ok, something's here. Maybe nothing useful. Remember that
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe * we tried, if we completely fail, so we can reject the request!
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* See if it's something which we have access to, and which
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * has a known type and encoding.
948096a99010fccf648814fecf38f75c689172d7wrowe sub_req = ap_sub_req_lookup_dirent(&dirent, r, AP_SUBREQ_MERGE_ARGS,
948096a99010fccf648814fecf38f75c689172d7wrowe /* Double check, we still don't multi-resolve non-ordinary files
948096a99010fccf648814fecf38f75c689172d7wrowe /* XXX sub req not destroyed -- may be a bug/unintentional ? */
948096a99010fccf648814fecf38f75c689172d7wrowe /* If it has a handler, we'll pretend it's a CGI script,
948096a99010fccf648814fecf38f75c689172d7wrowe * since that's a good indication of the sort of thing it
053497224246c4dbef9af594cacf5c00ed271e6cwrowe * might be doing.
948096a99010fccf648814fecf38f75c689172d7wrowe * mod_mime will _always_ provide us the base name in the
948096a99010fccf648814fecf38f75c689172d7wrowe * ap-mime-exception-list, if it processed anything. If
948096a99010fccf648814fecf38f75c689172d7wrowe * this list is empty, give up immediately, there was
948096a99010fccf648814fecf38f75c689172d7wrowe * nothing interesting. For example, looking at the files
053497224246c4dbef9af594cacf5c00ed271e6cwrowe * readme.txt and readme.foo, we will throw away .foo if
948096a99010fccf648814fecf38f75c689172d7wrowe * it's an insignificant file (e.g. did not identify a
948096a99010fccf648814fecf38f75c689172d7wrowe * language, charset, encoding, content type or handler,)
948096a99010fccf648814fecf38f75c689172d7wrowe "ap-mime-exceptions-list");
948096a99010fccf648814fecf38f75c689172d7wrowe /* Each unregonized bit better match our base name, in sequence.
948096a99010fccf648814fecf38f75c689172d7wrowe * A test of index.html.foo will match index.foo or index.html.foo,
948096a99010fccf648814fecf38f75c689172d7wrowe * but it will never transpose the segments and allow index.foo.html
948096a99010fccf648814fecf38f75c689172d7wrowe * because that would introduce too much CPU consumption. Better that
948096a99010fccf648814fecf38f75c689172d7wrowe * we don't attempt a many-to-many match here.
948096a99010fccf648814fecf38f75c689172d7wrowe /* Something you don't know is, something you don't know...
948096a99010fccf648814fecf38f75c689172d7wrowe * If we failed the subrequest, or don't
948096a99010fccf648814fecf38f75c689172d7wrowe * know what we are serving, then continue.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb if (sub_req->status != HTTP_OK || (!sub_req->content_type)) {
948096a99010fccf648814fecf38f75c689172d7wrowe /* If it's a map file, we use that instead of the map
948096a99010fccf648814fecf38f75c689172d7wrowe * we're building...
948096a99010fccf648814fecf38f75c689172d7wrowe !strcmp(sub_req->content_type, MAP_FILE_MAGIC_TYPE)) ||
053497224246c4dbef9af594cacf5c00ed271e6cwrowe /* Have reasonable variant --- gather notes. */
053497224246c4dbef9af594cacf5c00ed271e6cwrowe mime_info.file_name = apr_pstrdup(neg->pool, dirent.name);
948096a99010fccf648814fecf38f75c689172d7wrowe mime_info.content_encoding = sub_req->content_encoding;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb mime_info.content_languages = sub_req->content_languages;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb get_entry(neg->pool, &accept_info, sub_req->content_type);
2fa5b5878e7567e2875807c3e2a2b3b0d3ef74bewrowe memcpy(new_var, (void *) &mime_info, sizeof(var_rec));
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe /* We found some file names that matched. None could be served.
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe * Rather than fall out to autoindex or some other mapper, this
dc8692c6c0ca616a09aa12dad005f2ef23baa1a0wrowe * request must die.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00687)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb "Negotiation: discovered file(s) matching request: %s"
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb " (None could be negotiated).",
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Sort the variants into a canonical order. The negotiation
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * result sometimes depends on the order of the variants. By
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * sorting the variants into a canonical order, rather than using
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * the order in which readdir() happens to return them, we ensure
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * that the negotiation result will be consistent over filesystem
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * backup/restores and over all mirror sites.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb qsort((void *) neg->avail_vars->elts, neg->avail_vars->nelts,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb sizeof(var_rec), (int (*)(const void *, const void *)) variantsortf);
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/*****************************************************************
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * And now for the code you've been waiting for... actually
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * finding a match to the client's requirements.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* Matching MIME types ... the star/star and foo/star commenting conventions
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * are implemented here. (You know what I mean by star/star, but just
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * try mentioning those three characters in a C comment). Using strcmp()
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * is legit, because everything has already been smashed to lowercase.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Note also that if we get an exact match on the media type, we update
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * level_matched for use in level_cmp below...
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * We also give a value for mime_stars, which is used later. It should
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * be 1 for star/star, 2 for type/star and 3 for type/subtype.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic int mime_match(accept_rec *accept_r, var_rec *avail)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb if (accept_type[0] == '*') { /* Anything matches star/star */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* This code implements a piece of the tie-breaking algorithm between
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * variants of equal quality. This piece is the treatment of variants
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * of the same base media type, but different levels. What we want to
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * return is the variant at the highest level that the client explicitly
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * claimed to accept.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * If all the variants available are at a higher level than that, or if
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * the client didn't say anything specific about this media type at all
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * and these variants just got in on a wildcard, we prefer the lowest
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * level, on grounds that that's the one that the client is least likely
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * to choke on.
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * (This is all motivated by treatment of levels in HTML --- we only
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * want to give level 3 to browsers that explicitly ask for it; browsers
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * that don't, including HTTP/0.9 browsers that only get the implicit
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * "Accept: * / *" [space added to avoid confusing cpp --- no, that
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * syntax doesn't really work] should get HTML2 if available).
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * (Note that this code only comes into play when we are choosing among
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * variants of equal quality, where the draft standard gives us a fair
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * bit of leeway about what to do. It ain't specified by the standard;
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * rather, it is a choice made by this server about what to do in cases
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * where the standard does not specify a unique course of action).
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe /* Levels are only comparable between matching media types */
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe if (!var1->is_pseudo_html && strcmp(var1->mime_type, var2->mime_type)) {
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe /* The result of the above if statements is that, if we get to
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * here, both variants have the same mime_type or both are
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * pseudo-html.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Take highest level that matched, if either did match. */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return -1;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Neither matched. Take lowest level, if there's a difference. */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return -1;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Tied */
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* Finding languages. The main entry point is set_language_quality()
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * which is called for each variant. It sets two elements in the
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * variant record:
48d2edbfb84e5559b5da0f8d614ccab805cc67a8rbb * language_quality - the 'q' value of the 'best' matching language
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * from Accept-Language: header (HTTP/1.1)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * lang_index - Non-negotiated language priority, using
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * position of language on the Accept-Language:
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * header, if present, else LanguagePriority
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * directive order.
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowe * When we do the variant checking for best variant, we use language
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowe * quality first, and if a tie, language_index next (this only applies
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowe * when _not_ using the RVSA/1.0 algorithm). If using the RVSA/1.0
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowe * algorithm, lang_index is never used.
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowe * set_language_quality() calls find_lang_index() and find_default_index()
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowe * to set lang_index.
b67fb549910fa0faf4cdd8aeaf9aeab51d4b6a92wrowestatic int find_lang_index(apr_array_header_t *accept_langs, char *lang)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb const char **alang;
b45c1c292ff1fa635004ae81fa691f8cb3cdda85rbb return -1;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb return -1;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* set_default_lang_quality() sets the quality we apply to variants
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * which have no language assigned to them. If none of the variants
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * have a language, we are not negotiating on language, so all are
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * acceptable, and we set the default q value to 1.0. However if
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * some of the variants have languages, we set this default to 0.0001.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * The value of this default will be applied to all variants with
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * no explicit language -- which will have the effect of making them
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * acceptable, but only if no variants with an explicit language
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * are acceptable. The default q value set here is assigned to variants
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * with no language type in set_language_quality().
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Note that if using the RVSA/1.0 algorithm, we don't use this
efa1a34b0a7785fc72863eff175b0cfc1ecb0e38wrowestatic void set_default_lang_quality(negotiation_state *neg)
117026201e6d8fe7d82416b8a7324830f5a87292wrowe var_rec *avail_recs = (var_rec *) neg->avail_vars->elts;
55eb5bb774cfb861542b827fbf4c30e6efbbfc44wrowe/* Set the language_quality value in the variant record. Also
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * assigns lang_index for ForceLanguagePriority.
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * To find the language_quality value, we look for the 'q' value
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * of the 'best' matching language on the Accept-Language
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * header. The 'best' match is the language on Accept-Language
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * header which matches the language of this variant either fully,
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * or as far as the prefix marker (-). If two or more languages
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * match, use the longest string from the Accept-Language header
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * (see HTTP/1.1 [14.4])
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * When a variant has multiple languages, we find the 'best'
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * match for each variant language tag as above, then select the
e7505ba54ac56ae30e4e250f912f3dbaf92ca45fwrowe * one with the highest q value. Because both the accept-header
e7505ba54ac56ae30e4e250f912f3dbaf92ca45fwrowe * and variant can have multiple languages, we now have a hairy
e7505ba54ac56ae30e4e250f912f3dbaf92ca45fwrowe * loop-within-a-loop here.
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * If the variant has no language and we have no Accept-Language
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * items, leave the quality at 1.0 and return.
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * If the variant has no language, we use the default as set by
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * set_default_lang_quality() (1.0 if we are not negotiating on
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * language, 0.001 if we are).
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * Following the setting of the language quality, we drop through to
beda1fb2f11c52ca4612460a5d5ba47398143efbwrowe * set the old 'lang_index'. This is set based on either the order
beda1fb2f11c52ca4612460a5d5ba47398143efbwrowe * of the languages on the Accept-Language header, or the
beda1fb2f11c52ca4612460a5d5ba47398143efbwrowe * order on the LanguagePriority directive. This is only used
beda1fb2f11c52ca4612460a5d5ba47398143efbwrowe * in the negotiation if the language qualities tie.
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowestatic void set_language_quality(negotiation_state *neg, var_rec *variant)
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe if (!variant->content_languages || !variant->content_languages->nelts) {
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe /* This variant has no content-language, so use the default
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * quality factor for variants with no content-language
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * (previously set by set_default_lang_quality()).
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * Leave the factor alone (it remains at 1.0) when we may not fiddle
117026201e6d8fe7d82416b8a7324830f5a87292wrowe * with the headers.
117026201e6d8fe7d82416b8a7324830f5a87292wrowe return; /* no accept-language header */
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe /* Variant has one (or more) languages. Look for the best
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * match. We do this by going through each language on the
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * variant description looking for a match on the
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * Accept-Language header. The best match is the longest
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * matching language on the header. The final result is the
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * best q value from all the languages on the variant
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * description.
117026201e6d8fe7d82416b8a7324830f5a87292wrowe /* no accept-language header makes the variant indefinite */
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowe else { /* There is an accept-language with 0 or more items */
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowe accept_rec *accs = (accept_rec *) neg->accept_langs->elts;
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowe for (j = 0; j < variant->content_languages->nelts; ++j) {
650ac07cba9ab3ad9bdeda50b78c278442604814wrowe /* lang is the variant's language-tag, which is the one
650ac07cba9ab3ad9bdeda50b78c278442604814wrowe * we are allowed to use the prefix of in HTTP/1.1
650ac07cba9ab3ad9bdeda50b78c278442604814wrowe lang = ((char **) (variant->content_languages->elts))[j];
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe /* now find the best (i.e. longest) matching
1067418d9ed9ed9daeb3ca4f74e72db810c49833wrowe * Accept-Language header language. We put the best match
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * for this tag in bestthistag. We cannot update the
1067418d9ed9ed9daeb3ca4f74e72db810c49833wrowe * overall best (based on q value) because the best match
1067418d9ed9ed9daeb3ca4f74e72db810c49833wrowe * for this tag is the longest language item on the accept
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * header, not necessarily the highest q.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* Find language. We match if either the variant
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * language tag exactly matches the language range
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * from the accept header, or a prefix of the variant
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * language tag up to a '-' character matches the
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * whole of the language range in the Accept-Language
1b839c67d5c0e4b1f22b44a4217f9860b420d47cwrowe * header. Note that HTTP/1.x allows any number of
b45c1c292ff1fa635004ae81fa691f8cb3cdda85rbb * '-' characters in a tag or range, currently only
1ab995011876f631740d68d960a6d729ddff5bfawrowe * tags with zero or one '-' characters are defined
1ab995011876f631740d68d960a6d729ddff5bfawrowe * for general use (see rfc1766).
1ab995011876f631740d68d960a6d729ddff5bfawrowe * We only use language range in the Accept-Language
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * header the best match for the variant language tag
e68544ae924174ca227ede8e2e722cefa00ea0d3wrowe * if it is longer than the previous best match.
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe /* The next bit is a fiddle. Some browsers might
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * be configured to send more specific language
2d2dadb81bf34e3bc9321eabcd971a738431b364wrowe * ranges than desirable. For example, an
ecc4a080f07af3fbc1b91bbd00997ec1d592c6f9wrowe * Accept-Language of en-US should never match
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * variants with languages en or en-GB. But US
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * English speakers might pick en-US as their
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * language choice. So this fiddle checks if the
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * language range has a prefix, and if so, it
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * matches variants which match that prefix with a
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * priority of 0.001. So a request for en-US would
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * match variants of types en and en-GB, but at
5bb29f57ae0184d2b3c1cdf35132f8ceb011f882wrowe * much lower priority than matches of en-US
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * directly, or of any other language listed on
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * the Accept-Language header. Note that this
af7e32b660b02a378e91d40987e59b28864db954jwoolley * fiddle does not handle multi-level prefixes.
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe /* Finished looking at Accept-Language headers, the best
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * (longest) match is in bestthistag, or NULL if no match
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe (bestthistag && bestthistag->quality > best->quality)) {
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe /* See if the tag matches on a * in the Accept-Language
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * header. If so, record this fact for later use
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe /* If one of the language tags of the variant matched on *, we
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * need to see if its q is better than that of any non-* match
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * on any other tag of the variant. If so the * match takes
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * precedence and the overall match is not definite.
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe variant->lang_quality = best ? best->quality : fiddle_q;
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe /* Handle the ForceDefaultLanguage overrides, based on the best match
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * to LanguagePriority order. The best match is the lowest index of
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe * any LanguagePriority match.
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowe for (j = 0; j < variant->content_languages->nelts; ++j)
290ecc1ddceca1ed49bc1a5338921264b5c3e07cwrowe /* lang is the variant's language-tag, which is the one
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * we are allowed to use the prefix of in HTTP/1.1
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb char *lang = ((char **) (variant->content_languages->elts))[j];
a2a0abd88b19e042a3eb2a9fa1702c25ad51303dwrowe /* If we wish to fallback or
73fbb0a2e9cb209173d6c319c57260cbf29c8cc7wrowe * we use our own LanguagePriority index.
73fbb0a2e9cb209173d6c319c57260cbf29c8cc7wrowe idx = find_lang_index(neg->conf->language_priority, lang);
650ac07cba9ab3ad9bdeda50b78c278442604814wrowe if ((idx >= 0) && ((bestidx == -1) || (idx < bestidx))) {
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb if (bestidx >= 0) {
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe/* Determining the content length --- if the map didn't tell us,
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe * we have to do a stat() and remember for next time.
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowestatic apr_off_t find_content_length(negotiation_state *neg, var_rec *variant)
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe && (variant->sub_req->finfo.valid & APR_FINFO_SIZE)) {
83a8dc5a596a8a1b9d14f063268287d123b9ed7ewrowe char *fullname = ap_make_full_path(neg->pool, neg->dir_name,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb/* For a given variant, find the best matching Accept: header
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * and assign the Accept: header's quality value to the
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * mime_type_quality field of the variant, for later use in
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * determining the best matching variant.
b38846b15c8891c6dec44dcc4f96ca40721bf663rbbstatic void set_accept_quality(negotiation_state *neg, var_rec *variant)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb float q = 0.0f;
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb /* if no Accept: header, leave quality alone (will
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * remain at the default value of 1)
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * XXX: This if is currently never true because of the effect of
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * maybe_add_default_accepts().
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * Go through each of the ranges on the Accept: header,
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * looking for the 'best' match with this variant's
b38846b15c8891c6dec44dcc4f96ca40721bf663rbb * content-type. We use the best match's quality
int prev_mime_stars;
if (star) {
if (star) {
enum algorithm_results {
#ifdef NEG_DEBUG
if (q > bestq) {
*p_bestq = q;
if (q == bestq) {
*p_bestq = q;
int levcmp;
#ifdef NEG_DEBUG
*p_bestq = q;
*p_bestq = q;
*p_bestq = q;
*p_bestq = q;
*p_bestq = q;
*p_bestq = q;
*p_bestq = q;
int j, max;
if ( !lang
for (j = 0; j < max; ++j) {
* 'gzip-only-text/html' to get a behaviour similiar
int may_discard = 0;
/* gzip-only-text/html: send encoded documents only
"gzip-only-text/html");
if ( may_discard
if ( preferred_language
if (preferred_language) {
return algorithm_result;
* example if we have 3 variants x.html, x.ps.en and x.ps.nl, and if
int alg_result)
char *lang;
char *qstr;
int vary_by_type = 0;
int vary_by_language = 0;
int vary_by_charset = 0;
int vary_by_encoding = 0;
if (first_variant) {
if (!vary_by_type &&
if (!vary_by_charset &&
if (!vary_by_language &&
if (!vary_by_encoding &&
first_variant = 0;
if (lang) {
len);
const char *sub_vary;
int status;
return status;
return HTTP_VARIANT_ALSO_VARIES;
return HTTP_VARIANT_ALSO_VARIES;
int res;
return HTTP_MULTIPLE_CHOICES;
if (!*bestp) {
return HTTP_NOT_ACCEPTABLE;
* Manual setting of cache-control/expires always overrides this
return OK;
int res;
char *udir;
const char *new_req;
return DECLINED;
return res;
apr_bucket *e;
return HTTP_METHOD_NOT_ALLOWED;
NULL));
return res;
return res;
if (r->args) {
if (r->path_info) {
return OK;
int res;
return DECLINED;
return res;
return DECLINED;
if (res != 0)
goto return_from_multi;
goto return_from_multi;
r->mtime = 0;
return OK;
return DECLINED;
return DECLINED;
return OK;
if (x_enc) {
return OK;
return DECLINED;