Impressum mod_negotiation.c
Interaktion und PortierbarkeitC
/* Licensed to the Apache Software Foundation (ASF) under one or more *contributorlicenseagreements.SeetheNOTICEfiledistributedwith *thisworkforadditionalinformationregardingcopyrightownership. *TheASFlicensesthisfiletoYouundertheApacheLicense,Version2.0 *(the"License");youmaynotusethisfileexceptincompliancewith *theLicense.YoumayobtainacopyoftheLicenseat * *http://www.apache.org/licenses/LICENSE-2.0 * *Unlessrequiredbyapplicablelaworagreedtoinwriting,software *distributedundertheLicenseisdistributedonan"ASIS"BASIS, *WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied. *SeetheLicenseforthespecificlanguagegoverningpermissionsand *limitationsundertheLicense.
*/
/* Commands --- configuring document caching on a per (virtual?) *serverbasis...
*/
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 */
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 *ofnegotiationforthisvariant.Theyareobtainedfromthe *appropriateheaderlines,exceptforsource_quality,which *isobtainedfromthevariantitself(the'qs'parametervalue *fromthevariant'smime-type).Apartfromsource_quality, *thesevaluesaresetwhenwefindthequalityforeachvariant *(seebest_match()).source_qualityissetfromthe'qs'parameter *ofthevariantdescriptionormimetype:seeset_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 *threefieldsbelowarechangedduringnegotiation:
*/
float level_matched; int mime_stars; int definite;
} var_rec;
/* Something to carry around the state of negotiation (and to keep *allofthisthread-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 *headersarenotpresent
*/
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. *Cleaningoutthefields...
*/
/* 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 *calculatinganetagforthesourceofthevariantlist *information,soweuseap_make_etag().Notethatthis *validatorcanbe'weak'inextremecase.
*/
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 *whencreatingtheetagheader
*/
}
/* be tolerant and accept qvalues without leading zero *(alsoforbackwardscompat,whereatof()wasinuse)
*/ if (*string != '.' && *string++ != '0') { return1.0f;
}
if (*string == '.') { /* better only one division later, than dealing with fscking *IEEEformat0.1factors...
*/ 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');
}
}
}
/* This is possibly overkill for some servers, heck, we have *only33index.htmlvariantsindocs/docroot(today). *Makethisconfigurable?
*/ new->avail_vars = apr_array_make(r->pool, 40, sizeof(var_rec));
/* The user agent supports transparent negotiation */
neg->ua_supports_trans = 1;
/* Send-alternates could be configurable, but note *thatitmustbe1ifwehave'vlist'inthe *negotiateheader.
*/
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 *tousetheApachealgorithm
*/
neg->may_choose = 1;
/* We disable header fiddles on the assumption that a *clientsendingNegotiateknowshowtosendcorrect *headerswhichdon'tneedfiddling.
*/
neg->dont_fiddle_headers = 1;
}
}
}
/* Sometimes clients will give us no Accept info at all; this routine sets *upthestandarddefaultforthatcase,andalsoarrangesforustobe *willingtorunaCGIscriptifwefindone.(Infact,wesetupto *dramaticallypreferCGIscriptsincaseswherethat'sappropriate, *e.g.,POSTorwhenURIincludesqueryargsorextrapathinfo).
*/ 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));
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 *havetotreatcommentsspecially...
*/
cp += strlen(cp);
/* We need to shortcut the rest of this block following the Body: *tag-wewillnotlookforcontinutationafterthisline.
*/ 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,possiblyblank---ifso,weungetc()thefinalnewline *sothatwewillpickuptheblanklinethenexttime'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 *Suckinthebody,thenbackspacetothefirstcharafterthe *closingtagentry.Ifwefailtoread,findthetagorback *upthenwehaveahosedfile,sogiveupalready
*/
--*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 *sourcequalityformultiviewsvariants,sowearealwayscomparing *1.0to1.0fornow
*/ if (a->source_quality < b->source_quality) return1; if (a->source_quality > b->source_quality) return -1;
/* Second key is the variant name */ return strcmp(a->file_name, b->file_name);
}
/* 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 *Reallyshouldn'tseeanythingbutDIR/LNK/REGhere, *andweaughttodiscoveriftheLNKwasinteresting. * *Ofcourse,thisonlyhelpsplatformsthatcapturethe *thefiletypeinapr_dir_read(),whichmostcanonce *theyareoptimizedwithsomemagic[it'sknowntothe *dirent,notassociatedtotheinode,onmostFS's.]
*/ if ((dirent.valid & APR_FINFO_TYPE) && (dirent.filetype == APR_DIR)) continue;
/* See if it's something which we have access to, and which *hasaknowntypeandencoding.
*/
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, *sincethat'sagoodindicationofthesortofthingit *mightbedoing.
*/ if (sub_req->handler && !sub_req->content_type) {
ap_set_content_type_ex(sub_req, CGI_MAGIC_TYPE, 1);
}
/* If it's a map file, we use that instead of the map *we'rebuilding...
*/ 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. *Ratherthanfallouttoautoindexorsomeothermapper,this *requestmustdie.
*/ 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 *resultsometimesdependsontheorderofthevariants.By *sortingthevariantsintoacanonicalorder,ratherthanusing *theorderinwhichreaddir()happenstoreturnthem,weensure *thatthenegotiationresultwillbeconsistentoverfilesystem *backup/restoresandoverallmirrorsites.
*/
/* This code implements a piece of the tie-breaking algorithm between *variantsofequalquality.Thispieceisthetreatmentofvariants *ofthesamebasemediatype,butdifferentlevels.Whatwewantto *returnisthevariantatthehighestlevelthattheclientexplicitly *claimedtoaccept. * *Ifallthevariantsavailableareatahigherlevelthanthat,orif *theclientdidn'tsayanythingspecificaboutthismediatypeatall *andthesevariantsjustgotinonawildcard,wepreferthelowest *level,ongroundsthatthat'stheonethattheclientisleastlikely *tochokeon. * *(ThisisallmotivatedbytreatmentoflevelsinHTML---weonly *wanttogivelevel3tobrowsersthatexplicitlyaskforit;browsers *thatdon't,includingHTTP/0.9browsersthatonlygettheimplicit *"Accept:*/*"[spaceaddedtoavoidconfusingcpp---no,that *syntaxdoesn'treallywork]shouldgetHTML2ifavailable). * *(Notethatthiscodeonlycomesintoplaywhenwearechoosingamong *variantsofequalquality,wherethedraftstandardgivesusafair *bitofleewayaboutwhattodo.Itain'tspecifiedbythestandard; *rather,itisachoicemadebythisserveraboutwhattodoincases *wherethestandarddoesnotspecifyauniquecourseofaction).
*/
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) { return0;
}
if (!var1->is_pseudo_html && strcmp(var1->mime_type, var2->mime_type)) { return0;
} /* The result of the above if statements is that, if we get to *here,bothvariantshavethesamemime_typeorbothare *pseudo-html.
*/
/* Take highest level that matched, if either did match. */
if (var1->level_matched > var2->level_matched) { return1;
} if (var1->level_matched < var2->level_matched) { return -1;
}
/* Neither matched. Take lowest level, if there's a difference. */
if (var1->level < var2->level) { return1;
} if (var1->level > var2->level) { return -1;
}
/* Tied */
return0;
}
/* Finding languages. The main entry point is set_language_quality() *whichiscalledforeachvariant.Itsetstwoelementsinthe *variantrecord: *language_quality-the'q'valueofthe'best'matchinglanguage *fromAccept-Language:header(HTTP/1.1) *lang_index-Non-negotiatedlanguagepriority,using *positionoflanguageontheAccept-Language: *header,ifpresent,elseLanguagePriority *directiveorder. * *Whenwedothevariantcheckingforbestvariant,weuselanguage *qualityfirst,andifatie,language_indexnext(thisonlyapplies *when_not_usingtheRVSA/1.0algorithm).IfusingtheRVSA/1.0 *algorithm,lang_indexisneverused. * *set_language_quality()callsfind_lang_index()andfind_default_index() *tosetlang_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 *whichhavenolanguageassignedtothem.Ifnoneofthevariants *havealanguage,wearenotnegotiatingonlanguage,soallare *acceptable,andwesetthedefaultqvalueto1.0.Howeverif *someofthevariantshavelanguages,wesetthisdefaultto0.0001. *Thevalueofthisdefaultwillbeappliedtoallvariantswith *noexplicitlanguage--whichwillhavetheeffectofmakingthem *acceptable,butonlyifnovariantswithanexplicitlanguage *areacceptable.Thedefaultqvaluesethereisassignedtovariants *withnolanguagetypeinset_language_quality(). * *NotethatifusingtheRVSA/1.0algorithm,wedon'tusethis *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 *assignslang_indexforForceLanguagePriority. * *Tofindthelanguage_qualityvalue,welookforthe'q'value *ofthe'best'matchinglanguageontheAccept-Language *header.The'best'matchisthelanguageonAccept-Language *headerwhichmatchesthelanguageofthisvarianteitherfully, *orasfarastheprefixmarker(-).Iftwoormorelanguages *match,usethelongeststringfromtheAccept-Languageheader *(seeHTTP/1.1[14.4]) * *Whenavarianthasmultiplelanguages,wefindthe'best' *matchforeachvariantlanguagetagasabove,thenselectthe *onewiththehighestqvalue.Becauseboththeaccept-header *andvariantcanhavemultiplelanguages,wenowhaveahairy *loop-within-a-loophere. * *IfthevarianthasnolanguageandwehavenoAccept-Language *items,leavethequalityat1.0andreturn. * *Ifthevarianthasnolanguage,weusethedefaultassetby *set_default_lang_quality()(1.0ifwearenotnegotiatingon *language,0.001ifweare). * *Followingthesettingofthelanguagequality,wedropthroughto *settheold'lang_index'.Thisissetbasedoneithertheorder *ofthelanguagesontheAccept-Languageheader,orthe *orderontheLanguagePrioritydirective.Thisisonlyused *inthenegotiationifthelanguagequalitiestie.
*/
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 *qualityfactorforvariantswithnocontent-language *(previouslysetbyset_default_lang_quality()). *Leavethefactoralone(itremainsat1.0)whenwemaynotfiddle *withtheheaders.
*/ 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.Wedothisbygoingthrougheachlanguageonthe *variantdescriptionlookingforamatchonthe *Accept-Languageheader.Thebestmatchisthelongest *matchinglanguageontheheader.Thefinalresultisthe *bestqvaluefromallthelanguagesonthevariant *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 *weareallowedtousetheprefixofinHTTP/1.1
*/
lang = ((char **) (variant->content_languages->elts))[j];
/* now find the best (i.e. longest) matching *Accept-Languageheaderlanguage.Weputthebestmatch *forthistaginbestthistag.Wecannotupdatethe *overallbest(basedonqvalue)becausethebestmatch *forthistagisthelongestlanguageitemontheaccept *header,notnecessarilythehighestq.
*/ 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 *languagetagexactlymatchesthelanguagerange *fromtheacceptheader,oraprefixofthevariant *languagetaguptoa'-'charactermatchesthe *wholeofthelanguagerangeintheAccept-Language *header.NotethatHTTP/1.xallowsanynumberof *'-'charactersinatagorrange,currentlyonly *tagswithzeroorone'-'charactersaredefined *forgeneraluse(seerfc1766). * *WeonlyuselanguagerangeintheAccept-Language *headerthebestmatchforthevariantlanguagetag *ifitislongerthanthepreviousbestmatch.
*/
if (!bestthistag && !neg->dont_fiddle_headers) { /* The next bit is a fiddle. Some browsers might *beconfiguredtosendmorespecificlanguage *rangesthandesirable.Forexample,an *Accept-Languageofen-USshouldnevermatch *variantswithlanguagesenoren-GB.ButUS *Englishspeakersmightpicken-USastheir *languagechoice.Sothisfiddlechecksifthe *languagerangehasaprefix,andifso,it *matchesvariantswhichmatchthatprefixwitha *priorityof0.001.Soarequestforen-USwould *matchvariantsoftypesenanden-GB,butat *muchlowerprioritythanmatchesofen-US *directly,orofanyotherlanguagelistedon *theAccept-Languageheader.Notethatthis *fiddledoesnothandlemulti-levelprefixes.
*/ 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)matchisinbestthistag,orNULLifnomatch
*/ if (!best ||
(bestthistag && bestthistag->quality > best->quality)) {
best = bestthistag;
}
/* See if the tag matches on a * in the Accept-Language *header.Ifso,recordthisfactforlateruse
*/ if (!bestthistag && star) {
any_match_on_star = 1;
}
}
/* If one of the language tags of the variant matched on *, we *needtoseeifitsqisbetterthanthatofanynon-*match *onanyothertagofthevariant.Ifsothe*matchtakes *precedenceandtheoverallmatchisnotdefinite.
*/ 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 *toLanguagePriorityorder.Thebestmatchisthelowestindexof *anyLanguagePrioritymatch.
*/ 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 *weareallowedtousetheprefixofinHTTP/1.1
*/ char *lang = ((char **) (variant->content_languages->elts))[j]; int idx;
/* If we wish to fallback or *weuseourownLanguagePriorityindex.
*/
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, *wehavetodoastat()andrememberfornexttime.
*/
/* For a given variant, find the best matching Accept: header *andassigntheAccept:header'squalityvaluetothe *mime_type_qualityfieldofthevariant,forlaterusein *determiningthebestmatchingvariant.
*/
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 *remainatthedefaultvalueof1) * *XXX:Thisifiscurrentlynevertruebecauseoftheeffectof *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;
/* *GothrougheachoftherangesontheAccept:header, *lookingforthe'best'matchwiththisvariant's *content-type.Weusethebestmatch'squality *value(fromtheAccept:header)forthisvariant's *mime_type_qualityfield. * *Thebestmatchisdeterminedlikethis: *type/typeisbetterthantype/*isbetterthan*/* *ifmatchistype/type,usethelevelmimeparamifavailable
*/ 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 *inpreviousmatch
*/ 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 *andhavenoexplicitq=parametersintheacceptheader, *makewildcardsverylow,sowehavealowchance *ofendingupwiththemifthere'ssomethingbetter.
*/
/* For a given variant, find the 'q' value of the charset given *ontheAccept-Charsetline.Ifnocharsetsarelisted, *assumevalueof'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 *remainatthedefaultvalueof1)
*/ 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.whenrunningthervsa,asanyguesswon'tbereflected *inthevariantlistorcontent-locationheaders.
*/ if (neg->dont_fiddle_headers) return;
charset = "iso-8859-1"; /* The default charset for HTTP text types */
}
/* *GothrougheachoftheitemsontheAccept-Charsetheader, *lookingforamatchwiththisvariant'scharset.Ifnone *match,charsetisunacceptable,sosetqualityto0.
*/ 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 *use7bit,8binorbinaryintheirvarfiles??
*/
if (!neg->accept_encodings) { /* We had no Accept-Encoding header, assume that all *encodingsareacceptablewithalowquality, *butweprefernoencodingifavailable.
*/ if (!enc || is_identity_encoding(enc))
variant->encoding_quality = 1.0f; else
variant->encoding_quality = 0.5f;
/* Go through each of the encodings on the Accept-Encoding: header, *lookingforamatchwithourencoding.x-prefixesareignored.
*/ if (enc[0] == 'x' && enc[1] == '-') {
enc += 2;
} for (i = 0; i < neg->accept_encodings->nelts; ++i) {
char *name = accept_recs[i].name;
if (name[0] == 'x' && name[1] == '-') {
name += 2;
}
if (!strcmp(name, enc)) {
variant->encoding_quality = accept_recs[i].quality; return;
}
if (strcmp(name, "*") == 0) {
star = &accept_recs[i];
}
} /* No explicit match */ if (star) {
variant->encoding_quality = star->quality; return;
}
/* Encoding not found on Accept-Encoding: header, so it is *_not_acceptableunlessitistheidentity(noencoding)
*/
variant->encoding_quality = value_if_not_found;
}
/* Below is the 'best_match' function. It returns an int, which has *oneofthetwovaluesalg_choiceoralg_list,whichgivetheresult *ofthevariantselectionalgorithm.alg_listmeansthatnobest *variantwasfoundbythealgorithm,alg_choicemeansthatabest *variantwasfoundandshouldbereturned.Thelist/choice *terminologycomesfromTCN(rfc2295),butisusedinamoregeneric *wayhere.Thebestvariantisreturnedin*pbest.best_matchhas *twopossiblealgorithmsfordeterminingthebestvariant:the *RVSA/1.0algorithm(fromRFC2296),andthestandardApache *algorithm.Thesearesplitoutintoseparatefunctions *(is_variant_better_rvsa()andis_variant_better()).Selectionof *oneisthroughtheneg->use_rvsaflag. * *Thecalltobest_matchalsocreatesfullinformation,including *language,charset,etcqualityfor_every_variant.Thisisneeded *forgeneratingacorrectVaryheader,andcanbeusedforthe *Alternatesheader,thehuman-readablelistresponsesand406errors.
*/
/* TCN does not cover negotiation on content-encoding. For now, *weignoretheencodingunlessitwasexplicitlyexcluded.
*/ if (variant->encoding_quality == 0.0f) return0;
/* RFC 2296 calls for the result to be rounded to 5 decimal places, *butwedon'tdothatbecauseitservesnousefulpurposeother *thantoensurethataremotealgorithmoperatesonthesame *precisionasours.Thatissilly,sincewhatweobviouslywant *isforthealgorithmtooperateonthebestavailableprecision *regardlessofwhorunsit.Sincetheabovecalculationmay *resultinsignificantvarianceat1e-12,roundingwouldbebogus.
*/
if (q <= 0.0f) { return0;
} if (q > bestq) {
*p_bestq = q; return1;
} if (q == bestq) { /* If the best variant's encoding is of lesser quality than *thisvariant,thenwepreferthisvariant
*/ if (variant->encoding_quality > best->encoding_quality) {
*p_bestq = q; return1;
}
} return0;
}
/* Negotiation algorithm as used by previous versions of Apache *(justabout).
*/
/* language */ if (variant->lang_quality < best->lang_quality) { return0;
} if (variant->lang_quality > best->lang_quality) {
*p_bestq = q; return1;
}
/* if language qualities were equal, try the LanguagePriority stuff */ if (best->lang_index != -1 &&
(variant->lang_index == -1 || variant->lang_index > best->lang_index)) { return0;
} if (variant->lang_index != -1 &&
(best->lang_index == -1 || variant->lang_index < best->lang_index)) {
*p_bestq = q; return1;
}
/* content-type level (sometimes used with text/html, though we *supportitonothertypestoo)
*/
levcmp = level_cmp(variant, best); if (levcmp == -1) { return0;
} if (levcmp == 1) {
*p_bestq = q; return1;
}
/* charset */ if (variant->charset_quality < best->charset_quality) { return0;
} /* If the best variant's charset is ISO-8859-1 and this variant has *thesamecharsetquality,thenwepreferthisvariant
*/
/* Prefer the highest value for encoding_quality.
*/ if (variant->encoding_quality < best->encoding_quality) { return0;
} if (variant->encoding_quality > best->encoding_quality) {
*p_bestq = q; return1;
}
/* content length if all else equal */ if (find_content_length(neg, variant) >= find_content_length(neg, best)) { return0;
}
/* ok, to get here means every thing turned out equal, except *wehaveashortercontentlength,sousethisvariant
*/
*p_bestq = q; return1;
}
/* figure out, whether a variant is in a specific language *itreturnsalsofalse,ifthevarianthasnolanguage.
*/ staticint variant_has_language(var_rec *variant, constchar *lang)
{ /* fast exit */ if ( !lang
|| !variant->content_languages) { return0;
}
if (ap_array_str_contains(variant->content_languages, lang)) { return1;
}
return0;
}
/* check for environment variables 'no-gzip' and *'gzip-only-text/html'togetabehavioursimilar *tomod_deflate
*/ staticint discard_variant_by_env(var_rec *variant, int discard)
{ if ( is_identity_encoding(variant->content_encoding)
|| !strcmp(variant->content_encoding, "identity")) { return0;
}
/* if this variant is encoded somehow and there are special *variablesset,wedonotnegotiateit.seeabove.
*/ if ( may_discard
&& discard_variant_by_env(variant, may_discard)) { continue;
}
/* if a language is preferred, but the current variant *isnotinthatlanguage,thendropitfornow
*/ if ( preferred_language
&& !variant_has_language(variant, preferred_language)) { continue;
}
/* Find all the relevant 'quality' values from the *Accept...headers,andstoreinthevariant.Thisalso *preparesforsendinganAlternatesheaderetcsoweneedto *doitevenifwedonotactuallyplantofindabest *variant.
*/
set_accept_quality(neg, variant); /* accept the preferred language, even when it's not listed within *theAccept-Languageheader
*/ if (preferred_language) {
variant->lang_quality = 1.0f;
variant->definite = 1;
} else {
set_language_quality(neg, variant);
}
set_encoding_quality(neg, variant);
set_charset_quality(neg, variant);
/* Only do variant selection if we may actually choose a *variantfortheclient
*/ if (neg->may_choose) {
/* Now find out if this variant is better than the current *best,eitherusingtheRVSA/1.0algorithm,orApache's *internalserver-drivenalgorithm.Presumablyother *server-drivenalgorithmsarepossible,andcouldbe *implementedhere.
*/
if (neg->use_rvsa) { if (is_variant_better_rvsa(neg, variant, best, &bestq)) {
best = variant;
}
} else { if (is_variant_better(neg, variant, best, &bestq)) {
best = variant;
}
}
}
}
/* We now either have a best variant, or no best variant */
if (neg->use_rvsa) { /* calculate result for RVSA/1.0 algorithm: *onlyachoiceresponseifthebestvarianthasq>0 *andisdefinite
*/
algorithm_result = (best && best->definite) && (bestq > 0) ?
alg_choice : alg_list;
} else { /* calculate result for Apache negotiation algorithm */
algorithm_result = bestq > 0 ? alg_choice : alg_list;
}
/* run the loop again, if the "prefer-language" got no clear result */ if (preferred_language && (!best || algorithm_result != alg_choice)) {
preferred_language = NULL; continue;
}
break;
} while (1);
/* Returning a choice response with a non-neighboring variant is a *protocolsecurityerrorinTCN(seerfc2295).Wedo*not* *verifyherethatthevariantandURIareneighbors,eventhough *wemayreturnalg_choice.Wedependontheenvironment(the *caller)toonlydeclaretheresourcetransparentlynegotiableif *allvariantsareneighbors.
*/
*pbest = best; return algorithm_result;
}
/* Sets response headers for a negotiated response. *neg->is_transparentdetermineswhetheratransparentlynegotiated *responseoraplain`serverdrivennegotiation'responseis *created.ApplicableheadersareAlternates,Vary,andTCN. * *TheVaryheaderwecreateissometimeslongerthanisrequiredfor *thecorrectcachingofnegotiatedresultsbyHTTP/1.1caches.For *exampleifwehave3variantsx.html,x.ps.enandx.ps.nl,andif *theAccept:headerassignsa0qualityto.ps,thentheresultsof *thetwoserver-sidenegotiationalgorithmswecurrentlyimplement *willneverdependonAccept-Languagesowecouldreturn`Vary: *negotiate,accept'insteadofthelonger'Vary:negotiate,accept, *accept-language'whichthecodebelowwillreturn.Aroutinefor *computingtheexactminimalVaryheaderwouldbeahugepaintocode *andmaintainthough,especiallybecauseweneedtotakeallpossible *twiddlesintheserver-sidenegotiationalgorithmsintoaccount.
*/ staticvoid set_neg_headers(request_rec *r, negotiation_state *neg, int alg_result)
{
apr_table_t *hdrs;
var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; constchar *sample_type = NULL; constchar *sample_language = NULL; constchar *sample_encoding = NULL; constchar *sample_charset = NULL; char *lang; char *qstr;
apr_off_t len;
apr_array_header_t *arr; int max_vlist_array = (neg->avail_vars->nelts * 21); int first_variant = 1; int vary_by_type = 0; int vary_by_language = 0; int vary_by_charset = 0; int vary_by_encoding = 0; int j;
/* In order to avoid O(n^2) memory copies in building Alternates, *wepreallocateaapr_table_twiththemaximumsubstringspossible, *fillitwiththevariantlist,andthenconcatenatetheentirearray. *Notethatifyouchangethenumberofsubstringspushed,youalso *needtochangethecalculationofmax_vlist_arrayabove.
*/ if (neg->send_alternates && neg->avail_vars->nelts)
arr = apr_array_make(r->pool, max_vlist_array, sizeof(char *)); else
arr = NULL;
/* Put headers into err_headers_out, since send_http_header() *outputsbothheaders_outanderr_headers_out.
*/
hdrs = r->err_headers_out;
staticchar *make_variant_list(request_rec *r, negotiation_state *neg)
{
apr_array_header_t *arr; int i; int max_vlist_array = (neg->avail_vars->nelts * 15) + 2;
/* In order to avoid O(n^2) memory copies in building the list, *wepreallocateaapr_table_twiththemaximumsubstringspossible, *fillitwiththevariantlist,andthenconcatenatetheentirearray.
*/
arr = apr_array_make(r->pool, max_vlist_array, sizeof(char *));
/* Called if we got a "Choice" response from the variant selection algorithm. *Itcheckstheresultofthechosenvarianttoseeifit *isitselfnegotiated(ifso,returnerrorHTTP_VARIANT_ALSO_VARIES). *Otherwise,addtheappropriateheaderstothecurrentresponse.
*/
/* The variant selection algorithm told us to return a "Choice" *response.Thisisthenormalvariantresponse,with *someextraheaders.First,ensurethatthechosen *variantdidorwillnotitselfengageintransparentnegotiation. *Ifnot,settheappropriateheaders,andfallthroughto *thenormalvarianthandling
*/
/* This catches the error that a transparent type map selects a *transparentmultiviewsresourceasthebestvariant. * *XXX:Wedonotsignalanerrorifatransparenttypemap *selectsa_non_transparentmultiviewsresourceasthebest *variant,becausewecangeneratealegalnegotiationresponse *inthiscase.Inthiscase,thevlist_validatorofthe *nontransparentsubrequestwillbelosthowever.Thiscould *leadtocasesinwhichachangeinthesetofvariantsorthe *negotiationalgorithmofthenontransparentresourceisnever *propagateduptoaHTTP/1.1cachewhichinterpretsVary.Tobe *completelyonthesafesideweshouldreturnHTTP_VARIANT_ALSO_VARIES *forthistypeofrecursivenegotiationtoo.
*/ if (neg->is_transparent &&
apr_table_get(sub_req->err_headers_out, "TCN")) { return HTTP_VARIANT_ALSO_VARIES;
}
/* This catches the error that a transparent type map recursively *selects,asthebestvariant,anothertypemapwhichitself *causestransparentnegotiationtobedone. * *XXX:Actually,wecatchthiserrorbycatchingallcasesof *typemaprecursion.Therearesomeborderlinerecursivetype *maparrangementswhichwouldnotproducetransparent *negotiationprotocolerrorsorlackofcachepropagation *problems,butsucharrangementsareveryhardtodetectatthis *pointinthecontrolflow,sowedonotbothertosinglethem *out. * *Recursivetypemapsimplyarecursivearrangementofnegotiated *resourceswhichisvisibletooutsideclients,andthisisnot *supportedbythetransparentnegotiationcachingprotocols,so *ifwearetohavegenericsupportforrecursivetypemaps,we *havetocreatesomeconfigurationsettingwhichmakesalltype *mapsnon-transparentwhenrecursionisenabled.Also,ifwe *wantrecursivetypemapsupportwhichensurespropagationof *typemapchangesintoHTTP/1.1cachesthathandleVary,we *wouldhavetoextendthecurrentmechanismforgenerating *variantlistvalidators.
*/ if (sub_req->handler && strcmp(sub_req->handler, "type-map") == 0) { return HTTP_VARIANT_ALSO_VARIES;
}
/* This adds an appropriate Variant-Vary header if the subrequest *isamultiviewsresource. * *XXX:TODO:Notethatthisdoes_not_handleanyVaryheader *returnedbyaCGIifsub_reqisaCGIscript,becausewedon't *seethatVaryheaderyetatthispointinthecontrolflow. *Thiswon'tcauseanycacheconsistencyproblems_unless_the *CGIscriptalsoreturnsaCache-Controlheadermarkingthe *responseascacheable.Thisneedstobefixed,alsothereare *problemsifaCGIreturnsanEtagheaderwhichalsoneedtobe *fixed.
*/ if ((sub_vary = apr_table_get(sub_req->err_headers_out, "Vary")) != NULL) {
apr_table_setn(r->err_headers_out, "Variant-Vary", sub_vary);
/* Move the subreq Vary header into the main request to *preventhavingtwoVaryheadersintheresponse,which *wouldbelegalbutstrange.
*/
apr_table_setn(r->err_headers_out, "Vary", sub_vary);
apr_table_unset(sub_req->err_headers_out, "Vary");
}
staticint do_negotiation(request_rec *r, negotiation_state *neg,
var_rec **bestp, int prefer_scripts)
{
var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; int alg_result; /* result of variant selection algorithm */ int res; int j;
/* Decide if resource is transparently negotiable */
/* GET or HEAD? (HEAD has same method number as GET) */ if (r->method_number == M_GET) {
/* maybe this should be configurable, see also the comment *aboutrecursivetypemapsinsetup_choice_response()
*/
neg->is_transparent = 1;
/* We can't be transparent if we are a map file in the middle *oftherequestURI.
*/ if (r->path_info && *r->path_info)
neg->is_transparent = 0;
/* We can't be transparent, because of internal *assumptionsinbest_match(),ifthereisa *non-neighboringvariant.Wecanhaveanon-neighboring *variantwhenprocessingatypemap.
*/ if (ap_strchr_c(variant->file_name, '/'))
neg->is_transparent = 0;
/* We can't be transparent, because of the behavior *ofvarianttypemapbodies.
*/ if (variant->body) {
neg->is_transparent = 0;
}
}
}
if (neg->is_transparent) {
parse_negotiate_header(r, neg);
} else { /* configure negotiation on non-transparent resource */
neg->may_choose = 1;
}
maybe_add_default_accepts(neg, prefer_scripts);
alg_result = best_match(neg, bestp);
/* alg_result is one of *alg_choice:abestvariantischosen *alg_list:nobestvariantischosen
*/
if (alg_result == alg_list) { /* send a list response or HTTP_NOT_ACCEPTABLE error response */
/* Some HTTP/1.0 clients are known to choke when they get *a300(multiplechoices)responsewithoutaLocation *header.Howeverthe300coderesponseweareabout *togeneratewillonlyreach1.0clientswhichsupport *transparentnegotiation,andtheyshouldbeOK.The *responseshouldneverreacholder1.0clients,evenif *wehaveCacheNegotiatedDocsenabled,becauseno1.0 *proxycache(weknowof)willcacheandreturn300 *responses(theycertainlywon'tiftheyconformtothe *HTTP/1.0specification).
*/ return HTTP_MULTIPLE_CHOICES;
}
if (!*bestp) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00690) "no acceptable variant: %s", r->filename); return HTTP_NOT_ACCEPTABLE;
}
}
/* Variant selection chose a variant */
/* XXX todo: merge the two cases in the if statement below */ if (neg->is_transparent) {
/* ### These may be implemented by adding some 'extra' info *ofthefileoffsetontotheetag *ap_update_mtime(r,r->finfo.mtime); *ap_set_last_modified(r); *ap_set_etag(r);
*/
ap_set_accept_ranges(r);
ap_set_content_length(r, best->bytes);
/* set MIME type and charset as negotiated */ if (best->mime_type && *best->mime_type) { if (best->content_charset && *best->content_charset) {
ap_set_content_type_ex(r, apr_pstrcat(r->pool,
best->mime_type, "; charset=",
best->content_charset,
NULL), 1);
} else {
ap_set_content_type_ex(r, apr_pstrdup(r->pool, best->mime_type), 1);
}
}
/* set Content-language(s) as negotiated */ if (best->content_languages && best->content_languages->nelts) {
r->content_languages = apr_array_copy(r->pool,
best->content_languages);
}
/* set Content-Encoding as negotiated */ if (best->content_encoding && *best->content_encoding) {
r->content_encoding = apr_pstrdup(r->pool,
best->content_encoding);
}
if ((res = ap_meets_conditions(r)) != OK) { return res;
}
if ((res = read_types_multi(neg))) {
return_from_multi: /* free all allocated memory from subrequests */
avail_recs = (var_rec *) neg->avail_vars->elts; for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j]; if (variant->sub_req) {
ap_destroy_sub_req(variant->sub_req);
}
} return res;
} if (neg->avail_vars->nelts == 0) { return DECLINED;
}
res = do_negotiation(r, neg, &best,
(r->method_number != M_GET) || r->args ||
(r->path_info && *r->path_info)); if (res != 0) goto return_from_multi;
if (!(sub_req = best->sub_req)) { /* We got this out of a map file, so we don't actually have *asub_reqstructureyet.Getonenow.
*/
sub_req = ap_sub_req_lookup_file(best->file_name, r, r->output_filters); if (sub_req->status != HTTP_OK) {
res = sub_req->status;
ap_destroy_sub_req(sub_req); goto return_from_multi;
}
} if (sub_req->args == NULL) {
sub_req->args = r->args;
}
/* now do a "fast redirect" ... promotes the sub_req into the main req */
ap_internal_fast_redirect(sub_req, r);
/* give no advise for time on this subrequest. Perhaps we *shouldtallythelastmtimeamongallvariants,anddate *themostrecent,butthatcouldconfusetheproxies.
*/
r->mtime = 0;
/* clean up all but our favorite variant, since that sub_req *isnowmergedintothemainrequest!
*/
avail_recs = (var_rec *) neg->avail_vars->elts; for (j = 0; j < neg->avail_vars->nelts; ++j) {
var_rec *variant = &avail_recs[j]; if (variant != best && variant->sub_req) {
ap_destroy_sub_req(variant->sub_req);
}
} return OK;
}
AP_DECLARE_MODULE(negotiation) =
{
STANDARD20_MODULE_STUFF,
create_neg_dir_config, /* dir config creator */
merge_neg_dir_configs, /* dir merger --- default is to override */
NULL, /* server config */
NULL, /* merge server config */
negotiation_cmds, /* command apr_table_t */
register_hooks /* register hooks */
};
Messung V0.5 in Prozent
¤ 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.0.304Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-09-29)
¤
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 und die Messung sind noch experimentell.