mjc 96/09/24 05:45:04
Modified: src http_protocol.c httpd.h mod_negotiation.c CHANGES Log: Submitted by: Paul Sutton <[EMAIL PROTECTED]> Reviewed by: Mark Cox, Alexei Kosut, Roy Fielding, Brian Behlendorf Negotiation updated to implement all aspects of HTTP/1.1, including charset and encoding negotiation. Some code included for transparent negotiation (not compiled in by default). Revision Changes Path 1.47 +20 -4 apache/src/http_protocol.c Index: http_protocol.c =================================================================== RCS file: /export/home/cvs/apache/src/http_protocol.c,v retrieving revision 1.46 retrieving revision 1.47 diff -C3 -r1.46 -r1.47 *** http_protocol.c 1996/09/17 14:53:54 1.46 --- http_protocol.c 1996/09/24 12:44:57 1.47 *************** *** 50,56 **** * */ ! /* $Id: http_protocol.c,v 1.46 1996/09/17 14:53:54 chuck Exp $ */ /* * http_protocol.c --- routines which directly communicate with the --- 50,56 ---- * */ ! /* $Id: http_protocol.c,v 1.47 1996/09/24 12:44:57 mjc Exp $ */ /* * http_protocol.c --- routines which directly communicate with the *************** *** 793,799 **** return OK; } ! #define RESPONSE_CODE_LIST " 200 206 301 302 304 400 401 403 404 405 406 411 412 500 503 501 502 " /* New Apache routine to map error responses into array indicies * e.g. 400 -> 0, 500 -> 1, 502 -> 2 ... --- 793,799 ---- return OK; } ! #define RESPONSE_CODE_LIST " 200 206 300 301 302 304 400 401 403 404 405 406 411 412 500 503 501 502 506" /* New Apache routine to map error responses into array indicies * e.g. 400 -> 0, 500 -> 1, 502 -> 2 ... *************** *** 803,808 **** --- 803,809 ---- char *status_lines[] = { "200 OK", "206 Partial Content", + "300 Multiple Choices", "301 Moved Permanently", "302 Found", "304 Not Modified", *************** *** 817,828 **** "500 Server error", "503 Out of resources", "501 Not Implemented", ! "502 Bad Gateway" }; char *response_titles[] = { "200 OK", /* Never actually sent, barring die(200,...) */ "206 Partial Content", /* Never sent as an error (we hope) */ "Document moved", /* 301 Redirect */ "Document moved", /* 302 Redirect */ "304 Not Modified", /* Never sent... 304 MUST be header only */ --- 818,831 ---- "500 Server error", "503 Out of resources", "501 Not Implemented", ! "502 Bad Gateway", ! "506 Variant Also Varies" }; char *response_titles[] = { "200 OK", /* Never actually sent, barring die(200,...) */ "206 Partial Content", /* Never sent as an error (we hope) */ + "Multiple Choices", /* 300 Multiple Choices */ "Document moved", /* 301 Redirect */ "Document moved", /* 302 Redirect */ "304 Not Modified", /* Never sent... 304 MUST be header only */ *************** *** 837,843 **** "Server Error", "Out of resources", "Method not implemented", ! "Bad Gateway" }; int index_of_response(int err_no) { --- 840,847 ---- "Server Error", "Out of resources", "Method not implemented", ! "Bad Gateway", ! "Variant Also Varies" }; int index_of_response(int err_no) { *************** *** 1361,1366 **** --- 1365,1377 ---- bvputs(fd, "An appropriate variant to the requested entity ", escape_html(r->pool, r->uri), " could not be found " "on this server.<P>\n", NULL); + /* fall through */ + case MULTIPLE_CHOICES: + { + char *list; + if (list = table_get (r->notes, "variant-list")) + bputs(list, fd); + } break; case LENGTH_REQUIRED: bvputs(fd, "A request of the requested method ", r->method, *************** *** 1389,1394 **** --- 1400,1410 ---- bputs("The proxy server received an invalid\015\012", fd); bputs("response from an upstream server.<P>\015\012", fd); break; + case VARIANT_ALSO_VARIES: + bvputs(fd, "A variant for the requested entity ", + escape_html(r->pool, r->uri), " is itself a ", + "transparently negotiable resource.<P>\n", NULL); + break; } if (recursive_error) { 1.50 +4 -2 apache/src/httpd.h Index: httpd.h =================================================================== RCS file: /export/home/cvs/apache/src/httpd.h,v retrieving revision 1.49 retrieving revision 1.50 diff -C3 -r1.49 -r1.50 *** httpd.h 1996/09/03 00:31:27 1.49 --- httpd.h 1996/09/24 12:44:58 1.50 *************** *** 50,56 **** * */ ! /* $Id: httpd.h,v 1.49 1996/09/03 00:31:27 akosut Exp $ */ /* * httpd.h: header for simple (ha! not anymore) http daemon --- 50,56 ---- * */ ! /* $Id: httpd.h,v 1.50 1996/09/24 12:44:58 mjc Exp $ */ /* * httpd.h: header for simple (ha! not anymore) http daemon *************** *** 255,260 **** --- 255,261 ---- #define DOCUMENT_FOLLOWS 200 #define PARTIAL_CONTENT 206 + #define MULTIPLE_CHOICES 300 #define MOVED 301 #define REDIRECT 302 #define USE_LOCAL_COPY 304 *************** *** 270,276 **** #define NOT_IMPLEMENTED 501 #define BAD_GATEWAY 502 #define HTTP_SERVICE_UNAVAILABLE 503 ! #define RESPONSE_CODES 16 #define METHODS 8 #define M_GET 0 --- 271,278 ---- #define NOT_IMPLEMENTED 501 #define BAD_GATEWAY 502 #define HTTP_SERVICE_UNAVAILABLE 503 ! #define VARIANT_ALSO_VARIES 506 ! #define RESPONSE_CODES 18 #define METHODS 8 #define M_GET 0 1.18 +959 -284 apache/src/mod_negotiation.c Index: mod_negotiation.c =================================================================== RCS file: /export/home/cvs/apache/src/mod_negotiation.c,v retrieving revision 1.17 retrieving revision 1.18 diff -C3 -r1.17 -r1.18 *** mod_negotiation.c 1996/08/20 11:51:17 1.17 --- mod_negotiation.c 1996/09/24 12:44:59 1.18 *************** *** 50,56 **** * */ ! /* $Id: mod_negotiation.c,v 1.17 1996/08/20 11:51:17 paul Exp $ */ /* * mod_negotiation.c: keeps track of MIME types the client is willing to --- 50,56 ---- * */ ! /* $Id: mod_negotiation.c,v 1.18 1996/09/24 12:44:59 mjc Exp $ */ /* * mod_negotiation.c: keeps track of MIME types the client is willing to *************** *** 65,70 **** --- 65,79 ---- #include "http_core.h" #include "http_log.h" + /* define HOLTMAN to allow for Holtman I-D transparent negotiation. + * This file currently implements the draft-02, except for + * anything to do with features and cache-control (max-age etc) + * + * Since the draft is just that, and we don't yet implement + * everything, regard the transparent negotiation stuff as experimental. + */ + /*#define HOLTMAN*/ + /* Commands --- configuring document caching on a per (virtual?) * server basis... */ *************** *** 136,141 **** --- 145,151 ---- float quality; float max_bytes; float level; + char *charset; /* for content-type only */ } accept_rec; /* Record of available info on a particular variant *************** *** 143,155 **** * Note that a few of these fields are updated by the actual negotiation * code. These are: * - * quality --- initialized to the value of qs, and subsequently jiggered - * to reflect the client's preferences. In particular, it - * gets zeroed out if the variant has an unacceptable content - * encoding, or if it is in a language which the client - * doesn't accept and some other variant *is* in a language - * the client accepts. - * * level_matched --- initialized to zero. Set to the value of level * if the client actually accepts this media type at that * level (and *not* if it got in on a wildcard). See level_cmp --- 153,158 ---- *************** *** 157,178 **** */ typedef struct var_rec { ! request_rec *sub_req; /* May be NULL (is, for map files) */ char *type_name; char *file_name; char *content_encoding; char *content_language; ! float level; /* Auxiliary to content-type... */ ! float qs; ! float bytes; ! int lang_index; ! int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */ /* Above are all written-once properties of the variant. The ! * three fields below are changed during negotiation: */ - float quality; float level_matched; int mime_stars; } var_rec; --- 160,198 ---- */ typedef struct var_rec { ! request_rec *sub_req; /* May be NULL (is, for map files) */ char *type_name; char *file_name; char *content_encoding; char *content_language; ! char *content_charset; ! char *description; ! ! /* The next five items give the quality values for the dimensions ! * of negotiation for this variant. They are obtained from the ! * appropriate header lines, except for accept_type_quality, which ! * is obtained from the variant itself (the 'qs' parameter value ! * from the variant's mime-type). Apart from type_quality, ! * these values are set when we find the quality for each variant ! * (see best_match()). type_quality is set from the 'qs' parameter ! * of the variant description or mime type: see set_mime_fields(). ! */ ! float lang_quality; /* quality of this variant's language */ ! int encoding_quality; /* ditto encoding (1 or 0 only) */ ! float charset_quality; /* ditto charset */ ! float accept_type_quality; /* ditto media type */ ! float type_quality; /* quality of source for this type */ ! ! /* Now some special values */ ! float level; /* Auxiliary to content-type... */ ! float bytes; /* content length, if known */ ! int lang_index; /* pre HTTP/1.1 language priority stuff */ ! int is_pseudo_html; /* text/html, *or* the INCLUDES_MAGIC_TYPEs */ /* Above are all written-once properties of the variant. The ! * two fields below are changed during negotiation: */ float level_matched; int mime_stars; } var_rec; *************** *** 185,196 **** pool *pool; request_rec *r; char *dir_name; ! int accept_q; /* Do any of the Accept: headers have a q-value ? */ ! ! array_header *accepts; /* accept_recs */ ! array_header *accept_encodings; /* accept_recs */ ! array_header *accept_langs; /* accept_recs */ ! array_header *avail_vars; /* available variants */ } negotiation_state; /* A few functions to manipulate var_recs. --- 205,224 ---- pool *pool; request_rec *r; 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 */ ! ! ! array_header *accepts; /* accept_recs */ ! int have_accept_header; /* 1 if Accept-Header present */ ! array_header *accept_encodings; /* accept_recs */ ! array_header *accept_charsets; /* accept_recs */ ! array_header *accept_langs; /* accept_recs */ ! array_header *avail_vars; /* available variants */ ! ! int ua_can_negotiate; /* 1 if ua can do transparent negotiate */ ! int use_transparent_neg; /* 1 if we are using transparent neg */ ! int short_accept_headers; /* 1 if ua does trans neg & sent short accpt */ } negotiation_state; /* A few functions to manipulate var_recs. *************** *** 204,218 **** mime_info->file_name = ""; mime_info->content_encoding = ""; mime_info->content_language = ""; mime_info->is_pseudo_html = 0; mime_info->level = 0.0; mime_info->level_matched = 0.0; - mime_info->qs = 0.0; - mime_info->quality = 0.0; mime_info->bytes = 0; mime_info->lang_index = -1; mime_info->mime_stars = 0; } /* Initializing the relevant fields of a variant record from the --- 232,252 ---- mime_info->file_name = ""; mime_info->content_encoding = ""; mime_info->content_language = ""; + mime_info->content_charset = ""; + mime_info->description = ""; mime_info->is_pseudo_html = 0; mime_info->level = 0.0; mime_info->level_matched = 0.0; mime_info->bytes = 0; mime_info->lang_index = -1; mime_info->mime_stars = 0; + + mime_info->charset_quality = 1.0; + mime_info->type_quality = 1.0; + mime_info->encoding_quality = 1; + mime_info->lang_quality = 1.0; + mime_info->accept_type_quality = 1.0; } /* Initializing the relevant fields of a variant record from the *************** *** 222,230 **** void set_mime_fields (var_rec *var, accept_rec *mime_info) { var->type_name = mime_info->type_name; ! var->qs = mime_info->quality; ! var->quality = mime_info->quality; /* Initial quality is just qs */ var->level = mime_info->level; var->is_pseudo_html = (!strcmp (var->type_name, "text/html") --- 256,264 ---- void set_mime_fields (var_rec *var, accept_rec *mime_info) { var->type_name = mime_info->type_name; ! var->type_quality = mime_info->quality; var->level = mime_info->level; + var->content_charset = mime_info->charset; var->is_pseudo_html = (!strcmp (var->type_name, "text/html") *************** *** 248,253 **** --- 282,288 ---- result->quality = 1.0; result->max_bytes = 0.0; result->level = 0.0; + result->charset = ""; /* Note that this handles what I gather is the "old format", * *************** *** 282,287 **** --- 317,323 ---- char *parm; char *cp; + char *end; ++accept_line; parm = get_token (p, &accept_line, 1); *************** *** 297,303 **** while (*cp && (isspace(*cp) || *cp == '=')) ++cp; ! if (*cp == '"') ++cp; if (parm[0] == 'q' && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) --- 333,352 ---- while (*cp && (isspace(*cp) || *cp == '=')) ++cp; ! if (*cp == '"') { ! ++cp; ! for (end = cp; *end && ! *end != '\n' && *end != '\r' && *end != '\"'; ! end++) ! ; ! } ! else { ! for (end = cp; *end && !isspace(*end); end++) ! ; ! } ! if (*end) ! *end = '\0'; /* strip ending quote or return */ ! str_tolower(cp); if (parm[0] == 'q' && (parm[1] == '\0' || (parm[1] == 's' && parm[2] == '\0'))) *************** *** 307,323 **** result->max_bytes = atof(cp); else if (parm[0] == 'l' && !strcmp (&parm[1], "evel")) result->level = atof(cp); } if (*accept_line == ',') ++accept_line; return accept_line; } ! ! /***************************************************************** * * Dealing with header lines ... */ array_header *do_header_line (pool *p, char *accept_line) --- 356,381 ---- result->max_bytes = atof(cp); else if (parm[0] == 'l' && !strcmp (&parm[1], "evel")) result->level = atof(cp); + else if (!strcmp(parm, "charset")) + result->charset = cp; } if (*accept_line == ',') ++accept_line; return accept_line; } ! /***************************************************************** * * Dealing with header lines ... + * + * Accept, Accept-Charset, Accept-Language and Accept-Encoding + * are handled by do_header_line() - they all have the same + * basic structure of a list of items of the format + * name; q=N; charset=TEXT + * + * where q is only valid in Accept, Accept-Charset and Accept-Languages, + * and charset is only valid in Accept. */ array_header *do_header_line (pool *p, char *accept_line) *************** *** 345,374 **** (negotiation_state *)pcalloc (r->pool, sizeof (negotiation_state)); accept_rec *elts; table *hdrs = r->headers_in; ! ! int i; new->pool = r->pool; new->r = r; new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename)); new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept")); ! new->accept_encodings = ! do_header_line (r->pool, table_get (hdrs, "Accept-encoding")); new->accept_langs = do_header_line (r->pool, table_get (hdrs, "Accept-language")); new->avail_vars = make_array (r->pool, 40, sizeof (var_rec)); ! /* Now we check for q-values. If they're all 1.0, we assume the ! * client is "broken", and we are allowed to fiddle with the ! * values later. Otherwise, we leave them alone. ! */ ! ! elts = (accept_rec *)new->accepts->elts; ! ! for (i = 0; i < new->accepts->nelts; ++i) ! if (elts[i].quality < 1.0) new->accept_q = 1; return new; } --- 403,462 ---- (negotiation_state *)pcalloc (r->pool, sizeof (negotiation_state)); accept_rec *elts; table *hdrs = r->headers_in; ! int i; ! char *hdr; new->pool = r->pool; new->r = r; new->dir_name = make_dirstr(r->pool, r->filename, count_dirs(r->filename)); new->accepts = do_header_line (r->pool, table_get (hdrs, "Accept")); ! ! hdr = table_get (hdrs, "Accept-encoding"); ! if (hdr) ! new->have_accept_header = 1; ! new->accept_encodings = do_header_line (r->pool, hdr); ! new->accept_langs = do_header_line (r->pool, table_get (hdrs, "Accept-language")); + new->accept_charsets = + do_header_line (r->pool, table_get (hdrs, "Accept-charset")); new->avail_vars = make_array (r->pool, 40, sizeof (var_rec)); ! #ifdef HOLTMAN ! if (table_get(r->headers_in, "Negotiate")) { ! /* Negotiate: header tells us UA does transparent negotiation ! * We have to decide whether we want to ... for now, yes, ! * we do */ ! ! new->ua_can_negotiate = 1; ! if (r->method_number == M_GET) ! new->use_transparent_neg = 1; /* should be configurable */ ! ! /* Check for 'Short Accept', ie either no Accept: header, ! * or just "Accept: * / *" */ ! if (new->accepts->nelts == 0 || ! (new->accepts->nelts == 1 && ! (!strcmp(((accept_rec *)new->accepts->elts)[0].type_name, ! "*/*")))) { ! /* Using short accept header */ ! new->short_accept_headers = 1; ! } ! } ! #endif + if (!new->use_transparent_neg) { + /* Now we check for q-values. If they're all 1.0, we assume the + * client is "broken", and we are allowed to fiddle with the + * values later. Otherwise, we leave them alone. + */ + + elts = (accept_rec *)new->accepts->elts; + + for (i = 0; i < new->accepts->nelts; ++i) + if (elts[i].quality < 1.0) new->accept_q = 1; + } + return new; } *************** *** 449,455 **** */ while (c != EOF && c != '\n' && isspace(c)) ! c = getc(map); ungetc (c, map); --- 537,543 ---- */ while (c != EOF && c != '\n' && isspace(c)) ! c = getc(map); ungetc (c, map); *************** *** 458,464 **** /* Continuation */ while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n') ! *cp++ = c; *cp++ = '\n'; *cp = '\0'; --- 546,552 ---- /* Continuation */ while (cp < buf_end - 2 && (c = getc(map)) != EOF && c != '\n') ! *cp++ = c; *cp++ = '\n'; *cp = '\0'; *************** *** 487,493 **** ++hdr; } else if (*hdr == '(') { ! while (*hdr && *hdr != ')') *hdr++ = ' '; if (*hdr) *hdr++ = ' '; } --- 575,581 ---- ++hdr; } else if (*hdr == '(') { ! while (*hdr && *hdr != ')') *hdr++ = ' '; if (*hdr) *hdr++ = ' '; } *************** *** 547,553 **** strip_paren_comments (body); if (!strncmp (buffer, "uri:", 4)) { ! mime_info.file_name = get_token (neg->pool, &body, 0); } else if (!strncmp (buffer, "content-type:", 13)) { struct accept_rec accept_info; --- 635,641 ---- strip_paren_comments (body); if (!strncmp (buffer, "uri:", 4)) { ! mime_info.file_name = get_token (neg->pool, &body, 0); } else if (!strncmp (buffer, "content-type:", 13)) { struct accept_rec accept_info; *************** *** 566,576 **** mime_info.content_encoding = get_token (neg->pool, &body, 0); str_tolower (mime_info.content_encoding); } ! } else { ! if (mime_info.quality > 0) { ! void *new_var = push_array (neg->avail_vars); ! memcpy (new_var, (void *)&mime_info, sizeof (var_rec)); } clean_var_rec(&mime_info); } --- 654,672 ---- mime_info.content_encoding = get_token (neg->pool, &body, 0); str_tolower (mime_info.content_encoding); } ! else if (!strncmp (buffer, "description:", 12)) { ! mime_info.description = get_token (neg->pool, &body, 0); } + } else { + #ifdef NOTDEF + if (mime_info.quality > 0) + #endif + if (*mime_info.file_name) + { + void *new_var = push_array (neg->avail_vars); + memcpy (new_var, (void *)&mime_info, sizeof (var_rec)); + } + clean_var_rec(&mime_info); } *************** *** 615,621 **** } while ((dir_entry = readdir (dirp))) { ! request_rec *sub_req; /* Do we have a match? */ --- 711,717 ---- } while ((dir_entry = readdir (dirp))) { ! request_rec *sub_req; /* Do we have a match? */ *************** *** 623,629 **** if (strncmp (dir_entry->d_name, filp, prefix_len)) continue; if (dir_entry->d_name[prefix_len] != '.') continue; ! /* Yep. See if it's something which we have access to, and * which has a known type and encoding (as opposed to something * which we'll be slapping default_type on later). */ --- 719,725 ---- if (strncmp (dir_entry->d_name, filp, prefix_len)) continue; if (dir_entry->d_name[prefix_len] != '.') continue; ! /* Yep. See if it's something which we have access to, and * which has a known type and encoding (as opposed to something * which we'll be slapping default_type on later). */ *************** *** 658,666 **** mime_info.sub_req = sub_req; mime_info.file_name = pstrdup(neg->pool, dir_entry->d_name); ! mime_info.content_encoding = sub_req->content_encoding; ! mime_info.content_language = sub_req->content_language; ! get_entry (neg->pool, &accept_info, sub_req->content_type); set_mime_fields (&mime_info, &accept_info); --- 754,768 ---- mime_info.sub_req = sub_req; mime_info.file_name = pstrdup(neg->pool, dir_entry->d_name); ! if (sub_req->content_encoding) { ! mime_info.content_encoding = sub_req->content_encoding; ! str_tolower(mime_info.content_encoding); ! } ! if (sub_req->content_language) { ! mime_info.content_language = sub_req->content_language; ! str_tolower(mime_info.content_language); ! } ! get_entry (neg->pool, &accept_info, sub_req->content_type); set_mime_fields (&mime_info, &accept_info); *************** *** 698,713 **** char *avail_type = avail->type_name; int len = strlen(accept_type); ! if (accept_type[0] == '*') { /* Anything matches star/star */ if (avail->mime_stars < 1) ! avail->mime_stars = 1; ! return 1; } else if ((accept_type[len - 1] == '*') && ! !strncmp (accept_type, avail_type, len - 2)) { if (avail->mime_stars < 2) ! avail->mime_stars = 2; ! return 1; } else if (!strcmp (accept_type, avail_type) || (!strcmp (accept_type, "text/html") --- 800,815 ---- char *avail_type = avail->type_name; int len = strlen(accept_type); ! if (accept_type[0] == '*') { /* Anything matches star/star */ if (avail->mime_stars < 1) ! avail->mime_stars = 1; ! return 1; } else if ((accept_type[len - 1] == '*') && ! !strncmp (accept_type, avail_type, len - 2)) { if (avail->mime_stars < 2) ! avail->mime_stars = 2; ! return 1; } else if (!strcmp (accept_type, avail_type) || (!strcmp (accept_type, "text/html") *************** *** 753,762 **** /* Levels are only comparable between matching media types */ if (var1->is_pseudo_html && !var2->is_pseudo_html) ! return 0; if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name)) ! return 0; /* Take highest level that matched, if either did match. */ --- 855,864 ---- /* Levels are only comparable between matching media types */ if (var1->is_pseudo_html && !var2->is_pseudo_html) ! return 0; if (!var1->is_pseudo_html && strcmp (var1->type_name, var2->type_name)) ! return 0; /* Take highest level that matched, if either did match. */ *************** *** 773,784 **** return 0; } ! /* Finding languages. Note that we only match the substring specified ! * by the Accept: line --- this is to allow "en" to match all subvariants ! * of English. We then do it in the other direction, so that all ! * subvariants of English match "en". * ! * Again, strcmp() is legit because we've ditched case already. */ int find_lang_index (array_header *accept_langs, char *lang) --- 875,897 ---- return 0; } ! /* Finding languages. The main entry point is set_language_quality() ! * which is called for each variant. It sets two elements in the ! * variant record: ! * language_quality - the 'q' value of the 'best' matching language ! * from Accept-Language: header (HTTP/1.1) ! * lang_index - Pre HTTP/1.1 language priority, using ! * position of language on the Accept-Language: ! * header, if present, else LanguagePriority ! * directive order. * ! * When we do the variant checking for best variant, we use language ! * quality first, and if a tie, language_index next (this only ! * applies when _not_ using the network algorithm). If using ! * network algorithm, lang_index is never used. ! * ! * set_language_quality() calls find_lang_index() and find_default_index() ! * to set lang_index. */ int find_lang_index (array_header *accept_langs, char *lang) *************** *** 787,804 **** int i; if (!lang) ! return -1; accs = (accept_rec *)accept_langs->elts; ! for (i = 0; i < accept_langs->nelts; ++i) { ! if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name))) ! return i; ! if (!strncmp (lang, accs[i].type_name, strlen(lang))) ! return i; ! } ! ! return -1; } /* This function returns the priority of a given language --- 900,914 ---- int i; if (!lang) ! return -1; accs = (accept_rec *)accept_langs->elts; ! for (i = 0; i < accept_langs->nelts; ++i) ! if (!strncmp (lang, accs[i].type_name, strlen(accs[i].type_name))) ! return i; ! ! return -1; } /* This function returns the priority of a given language *************** *** 814,820 **** int i; if (!lang) ! return -1; arr = conf->language_priority; nelts = arr->nelts; --- 924,930 ---- int i; if (!lang) ! return -1; arr = conf->language_priority; nelts = arr->nelts; *************** *** 822,1084 **** for (i = 0; i < nelts; ++i) if (!strcasecmp (elts[i], lang)) ! return i; return -1; } ! void find_lang_indexes (negotiation_state *neg) { ! var_rec *var_recs = (var_rec*)neg->avail_vars->elts; int i; ! int found_any = 0; ! neg_dir_config *conf = NULL; int naccept = neg->accept_langs->nelts; if (naccept == 0) ! conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config, ! &negotiation_module); ! for (i = 0; i < neg->avail_vars->nelts; ++i) ! if (var_recs[i].quality > 0) { ! int index; ! if (naccept == 0) /* Client doesn't care */ ! index = find_default_index (conf, ! var_recs[i].content_language); ! else /* Client has Accept-Language */ ! index = find_lang_index (neg->accept_langs, ! var_recs[i].content_language); ! var_recs[i].lang_index = index; ! if (index >= 0) found_any = 1; ! } ! /* If we have any variants in a language acceptable to the client, ! * blow away everything that isn't. ! */ ! ! if (found_any) ! for (i = 0; i < neg->avail_vars->nelts; ++i) ! if (var_recs[i].lang_index < 0) ! var_recs[i].quality = 0; } ! /* Finding content encodings. Note that we assume that the client ! * accepts the trivial encodings. Strcmp() is legit because... aw, hell. */ ! int is_identity_encoding (char *enc) { ! return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit") ! || !strcmp (enc, "binary")); } ! int find_encoding (array_header *accept_encodings, char *enc) { - accept_rec *accs = (accept_rec *)accept_encodings->elts; int i; ! if (is_identity_encoding(enc)) return 1; ! for (i = 0; i < accept_encodings->nelts; ++i) ! if (!strcmp (enc, accs[i].type_name)) ! return 1; ! return 0; } ! void do_encodings (negotiation_state *neg) { - var_rec *var_recs = (var_rec*)neg->avail_vars->elts; int i; ! /* If no Accept-Encoding is present, everything is acceptable */ ! if (!neg->accept_encodings->nelts) ! return; ! /* Lose any variant with an unacceptable content encoding */ ! for (i = 0; i < neg->avail_vars->nelts; ++i) ! if (var_recs[i].quality > 0 ! && !find_encoding (neg->accept_encodings, ! var_recs[i].content_encoding)) ! ! var_recs[i].quality = 0; } ! /* Determining the content length --- if the map didn't tell us, ! * we have to do a stat() and remember for next time. * ! * Grump. For shambhala, even the first stat here may well be ! * redundant (for multiviews) with a stat() done by the sub_req ! * machinery. At some point, that ought to be fixed. */ ! int find_content_length(negotiation_state *neg, var_rec *variant) { ! struct stat statb; ! if (variant->bytes == 0) { ! char *fullname = make_full_path (neg->pool, neg->dir_name, ! variant->file_name); ! ! if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size; } ! return variant->bytes; } ! /* The main event. */ ! ! var_rec *best_match(negotiation_state *neg) { ! int i, j; var_rec *best = NULL; ! float best_quality = 0.0; ! int levcmp; - accept_rec *accept_recs = (accept_rec *)neg->accepts->elts; var_rec *avail_recs = (var_rec *)neg->avail_vars->elts; ! /* Nuke variants which are unsuitable due to a content encoding, ! * or possibly a language, which the client doesn't accept. ! * (If we haven't *got* a variant in a language the client accepts, ! * find_lang_indexes keeps 'em all, so we still wind up serving ! * something...). */ - - do_encodings (neg); - find_lang_indexes (neg); - - for (i = 0; i < neg->accepts->nelts; ++i) { ! accept_rec *type = &accept_recs[i]; ! ! for (j = 0; j < neg->avail_vars->nelts; ++j) { ! ! var_rec *variant = &avail_recs[j]; ! float q, quality = type->quality; ! /* If we've already rejected this variant, don't waste time */ ! ! if (variant->quality == 0.0) continue; ! ! /* If media types don't match, forget it. ! * (This includes the level check). ! */ ! ! if (!mime_match(type, variant)) continue; ! /* If we are allowed to mess with the q-values, ! * make wildcards very low, so we have a low chance ! * of ending up with them if there's something better. ! */ ! ! if (!neg->accept_q && variant->mime_stars == 1) quality = 0.01; ! if (!neg->accept_q && variant->mime_stars == 2) quality = 0.02; ! q = quality * variant->quality; ! /* Check maxbytes */ ! ! if (type->max_bytes > 0 ! && (find_content_length(neg, variant) ! > type->max_bytes)) ! continue; ! ! /* If it lasted this far, consider it --- ! * If better quality than our current best, take it. ! * If equal quality, *maybe* take it. ! * ! * Note that the current http draft specifies no particular ! * behavior for variants which tie in quality; the server ! * can, at its option, return a 300 response listing all ! * of them (and perhaps the others), or choose one of the ! * tied variants by whatever means it likes. This server ! * breaks ties as follows, in order: ! * ! * By perferring non-wildcard entries to those with ! * wildcards. The spec specifically says we should ! * do this, and it makes a lot of sense. ! * ! * By order of languages in Accept-language, to give the ! * client a way to specify a language preference. I'd prefer ! * to give this precedence over media type, but the standard ! * doesn't allow for that. ! * ! * By level preference, as defined by level_cmp above. ! * ! * By order of Accept: header matched, so that the order in ! * which media types are named by the client functions as a ! * preference order, if the client didn't give us explicit ! * quality values. ! * ! * Finally, by content_length, so that among variants which ! * have the same quality, language and content_type (including ! * level) we ship the one that saps the least bandwidth. ! */ ! ! if (q > best_quality ! || (q == best_quality ! && ((variant->mime_stars > best->mime_stars) ! || (variant->lang_index < best->lang_index ! || (variant->lang_index == best->lang_index ! && ((levcmp = level_cmp (variant, best)) == 1 ! || (levcmp == 0 ! && (find_content_length(neg, variant) ! < ! find_content_length(neg, best))))))))) ! { ! best = variant; ! best_quality = q; ! } ! } } ! return best; } ! char *set_vary (pool *p, negotiation_state *neg) { - var_rec *var_recs = (var_rec*)neg->avail_vars->elts; int i; ! int accept_type, accept_enc, accept_lang; ! char *type, *enc, *lang; ! char *last_type, *last_enc, *last_lang; ! accept_type = accept_enc = accept_lang = 0; ! last_type = last_enc = last_lang = NULL; ! /* Go through each variant and check for a differing ! * type, encoding or type. */ ! for (i = 0; i < neg->avail_vars->nelts; ++i) { ! /* Ideally, we wouldn't have to do this, but strcmp(NULL, NULL) ! * isn't legal ! */ ! type = var_recs[i].type_name ? var_recs[i].type_name : ""; ! enc = var_recs[i].content_encoding ? var_recs[i].content_encoding : ""; ! lang = var_recs[i].content_language ? var_recs[i].content_language : ""; ! ! if (!accept_type && last_type && strcmp(last_type, type)) ! accept_type = 1; ! else if (!accept_type && !last_type) last_type = type; ! ! if (!accept_enc && last_enc && strcmp(last_enc, enc)) ! accept_enc = 1; ! else if (!accept_enc && !last_enc) last_enc = enc; ! ! if (!accept_lang && last_lang && strcmp(last_lang, lang)) ! accept_lang = 1; ! else if (!accept_lang && !last_lang) last_lang = lang; ! } ! ! if (!accept_type && !accept_enc && !accept_lang) return NULL; ! else return 2 + pstrcat(p, accept_type ? ", Accept" : "", ! accept_enc ? ", Accept-Encoding" : "", ! accept_lang ? ", Accept-Language" : "", NULL); } /**************************************************************** --- 932,1709 ---- for (i = 0; i < nelts; ++i) if (!strcasecmp (elts[i], lang)) ! return i; return -1; } ! /* set_default_lang_quality() sets the quality we apply to variants ! * which have no language assigned to them. If none of the variants ! * have a language, we are not negotiating on language, so all are ! * acceptable, and we set the default q value to 1.0. However if ! * some of the variants have languages, we set this default to 0.001. ! * The value of this default will be applied to all variants with ! * no explicit language -- which will have the effect of making them ! * acceptable, but only if no variants with an explicit language ! * are acceptable. The default q value set here is assigned to variants ! * with no language type in set_language_quality(). ! */ ! ! void set_default_lang_quality(negotiation_state *neg) ! { ! var_rec *avail_recs = (var_rec *)neg->avail_vars->elts; ! int j; ! ! for (j = 0; j < neg->avail_vars->nelts; ++j) { ! var_rec *variant = &avail_recs[j]; ! if (variant->content_language && *variant->content_language) { ! neg->default_lang_quality = 0.001; ! return; ! } ! } ! ! neg->default_lang_quality = 1.0; ! } ! ! /* Set the language_quality value in the variant record. Also ! * assigns lang_index for back-compat. ! * ! * To find the language_quality value, we look for the 'q' value ! * of the 'best' matching language on the Accept-Language: ! * header. The'best' match is the language on Accept-Language: ! * header which matches the language of this variant either fully, ! * or as far as the prefix marker (-). If two or more languages ! * match, use the longest string from the Accept-Language: header ! * (see HTTP/1.1 [14.4]) ! * ! * If the variant has no language and we have no Accept-Language ! * items, leave the quality at 1.0 and return. ! * ! * If the variant has no language, we use the default as set by ! * set_default_lang_quality() (1.0 if we are not negotiating on ! * language, 0.001 if we are). ! * ! * Following the setting of the language quality, we drop through to ! * set the old 'lang_index'. This is set based on either the order ! * of the languages on the Accept-Language header, or the ! * order on the LanguagePriority directive. This is only used ! * in the negotiation if the language qualities tie. ! */ ! ! void set_language_quality(negotiation_state *neg, var_rec *variant) { ! accept_rec *accs, *best = NULL, *star = NULL; int i; ! char *lang = variant->content_language; ! int prefixlen; ! char *p; int naccept = neg->accept_langs->nelts; + int index; + neg_dir_config *conf; + int longest_lang_range_len = 0; + int len; if (naccept == 0) ! conf = (neg_dir_config *) get_module_config (neg->r->per_dir_config, ! &negotiation_module); ! if (naccept == 0 && (!lang || !*lang)) ! return; /* variant has no assigned language */ ! p = strchr(lang, '-'); /* find prefix part (if any) */ ! if (p) ! prefixlen = p - lang; ! ! if (!lang || !*lang) { ! /* This variant has no content-language, so use the default ! * quality factor for variants with no content-language ! * (previously set by set_default_lang_quality()). */ ! variant->lang_quality = neg->default_lang_quality; ! } ! else if (naccept) { ! float fiddle_q = 0.0; ! accs = (accept_rec *)neg->accept_langs->elts; ! ! for (i = 0; i < neg->accept_langs->nelts; ++i) { ! if (!strcmp(accs[i].type_name, "*")) { ! star = &accs[i]; ! continue; ! } ! ! /* Find language. We match if either the variant language ! * tag exactly matches, or the prefix of the tag up to the ! * '-' character matches the whole of the language in the ! * Accept-Language header */ ! if ((!strcmp (lang, accs[i].type_name) || ! (prefixlen && ! !strncmp(lang, accs[i].type_name, prefixlen) && ! (accs[i].type_name[prefixlen] == '\0'))) && ! ((len = strlen(accs[i].type_name)) > ! longest_lang_range_len)) { ! longest_lang_range_len = len; ! best = &accs[i]; ! } ! ! if (! best) { ! /* The next bit is a fiddle. Some browsers might be ! * configured to send more specific language ranges ! * than desirable. For example, an Accept-Language of ! * en-US should never match variants with languages en ! * or en-GB. But US English speakers might pick en-US ! * as their language choice. So this fiddle checks if ! * the language range has a prefix, and if so, it ! * matches variants which match that prefix with a ! * priority of 0.001. So a request for en-US would ! * match variants of types en and en-GB, but at much ! * lower priority than matches of en-US directly, or ! * of any other language listed on the Accept-Language ! * header ! */ ! if (p = strchr(accs[i].type_name, '-')) { ! int plen = p - accs[i].type_name; ! if (!strncmp(lang, accs[i].type_name, plen)) ! fiddle_q = 0.001; ! } ! } ! ! } ! ! variant->lang_quality = best ? best->quality : ! (star ? star->quality : fiddle_q); ! } ! ! /* Now set the old lang_index field */ ! index = 0; ! if (naccept == 0) /* Client doesn't care */ ! index = find_default_index (conf, ! variant->content_language); ! else /* Client has Accept-Language */ ! index = find_lang_index (neg->accept_langs, ! variant->content_language); ! variant->lang_index = index; ! ! return; } ! /* Determining the content length --- if the map didn't tell us, ! * we have to do a stat() and remember for next time. ! * ! * Grump. For shambhala, even the first stat here may well be ! * redundant (for multiviews) with a stat() done by the sub_req ! * machinery. At some point, that ought to be fixed. */ ! int find_content_length(negotiation_state *neg, var_rec *variant) { ! struct stat statb; ! ! if (variant->bytes == 0) { ! char *fullname = make_full_path (neg->pool, neg->dir_name, ! variant->file_name); ! ! if (stat (fullname, &statb) >= 0) variant->bytes = statb.st_size; ! } ! ! return variant->bytes; } ! /* For a given variant, find the best matching Accept: header ! * and assign the Accept: header's quality value to the ! * accept_type_quality field of the variant, for later use in ! * determining the best matching variant. ! */ ! ! void set_accept_quality(negotiation_state *neg, var_rec *variant) { int i; + accept_rec *accept_recs = (accept_rec *)neg->accepts->elts; + float q = 0.0; ! /* if no Accept: header, leave quality alone (will ! * remain at the default value of 1) */ ! if (!neg->accepts || neg->accepts->nelts == 0) ! return; ! ! /* ! * Go through each of the ranges on the Accept: header, ! * looking for the 'best' match with this variant's ! * content-type. We use the best match's quality ! * value (from the Accept: header) for this variant's ! * accept_type_quality field. ! * ! * The best match is determined like this: ! * type/type is better than type/ * is better than * / * ! * if match is type/type, use the level mime param if available ! */ ! for (i = 0; i < neg->accepts->nelts; ++i) { ! accept_rec *type = &accept_recs[i]; ! int prev_mime_stars; ! ! prev_mime_stars = variant->mime_stars; ! ! if (!mime_match(type, variant)) ! continue; /* didn't match the content type at all */ ! else ! /* did match - see if there were less or more stars than ! * in previous match ! */ ! if (prev_mime_stars == variant->mime_stars) ! continue; /* more stars => not as good a match */ ! ! /* Check maxbytes -- not in HTTP/1.1 or Holtman */ ! ! if (type->max_bytes > 0 ! && (find_content_length(neg, variant) ! > type->max_bytes)) ! continue; ! ! /* If we are allowed to mess with the q-values, ! * make wildcards very low, so we have a low chance ! * of ending up with them if there's something better. ! */ ! ! if (!neg->accept_q && variant->mime_stars == 1) q = 0.01; ! else if (!neg->accept_q && variant->mime_stars == 2) q = 0.02; ! else q = type->quality; ! } ! variant->accept_type_quality = q; ! /* if the _best_ quality we got for this variant was 0.0, ! * eliminate it now */ } ! /* For a given variant, find the 'q' value of the charset given ! * on the Accept-Charset line. If not charsets are listed, ! * assume value of '1'. ! */ ! ! void set_charset_quality(negotiation_state *neg, var_rec *variant) { int i; + accept_rec *accept_recs = (accept_rec *)neg->accept_charsets->elts; + float q = 0.0; + char *charset = variant->content_charset; + + if (!charset) + return; /* variant has no charset */ + + /* if no Accept-Charset: header, leave quality alone (will + * remain at the default value of 1) */ + if (!neg->accept_charsets || neg->accept_charsets->nelts == 0) + return; + + if (!*charset || !strcmp(charset, "iso-8859-1")) + return; /* default charset always ok */ + + + /* + * Go through each of the items on the Accept-Charset: header, + * looking for a match with this variant's charset. If none + * match, charset is unacceptable, so set quality to 0. + */ + for (i = 0; i < neg->accept_charsets->nelts; ++i) { + + accept_rec *type = &accept_recs[i]; + + if (!strcmp(type->type_name, charset)) { + variant->charset_quality = type->quality; + return; + } + } + variant->charset_quality = 0.0; + } + + /* For a given variant, find the best matching Accept: header + * and assign the Accept: header's quality value to the + * accept_type_quality field of the variant, for later use in + * determining the best matching variant. + */ ! /* is_identity_encoding is included for back-compat, but does anyone ! * use 7bit, 8bin or binary in their var files?? ! */ ! int is_identity_encoding (char *enc) ! { ! return (!enc || !enc[0] || !strcmp (enc, "7bit") || !strcmp (enc, "8bit") ! || !strcmp (enc, "binary")); ! } ! ! void set_encoding_quality(negotiation_state *neg, var_rec *variant) ! { ! int i; ! accept_rec *accept_recs = (accept_rec *)neg->accept_encodings->elts; ! char *enc = variant->content_encoding; ! if (!enc || is_identity_encoding(enc)) ! return; ! ! /* if no Accept: header, leave quality alone (will ! * remain at the default value of 1) */ ! if (neg->accept_encodings->nelts == 0) { ! /* If we had an empty Accept-Encoding header, assume that ! * no encodings are acceptable, else all encodings are ok */ ! variant->encoding_quality = neg->have_accept_header ? 0 : 1; ! return; ! } ! ! /* Go through each of the encodings on the Accept-Encoding: header, ! * looking for a match with our encoding ! */ ! for (i = 0; i < neg->accept_encodings->nelts; ++i) { ! char *name = accept_recs[i].type_name; ! ! if (!strcmp(name, enc)) { ! variant->encoding_quality = 1; ! return; ! } ! } ! ! /* Encoding not found on Accept-Encoding: header, so it is ! * _not_ acceptable */ ! variant->encoding_quality = 0; } ! /* Possible results of the network algorithm */ ! enum algorithm_results { ! na_not_applied = -1, /* net algorithm not used */ ! na_choice = 1, /* choose variant */ ! na_list /* list variants */ ! }; ! ! /* ! * This is a heavily-rewritten 'best_match' function. For a start, it ! * now returns an int, which has one of the three values: na_not_applied, ! * na_choice or na_list, which give the result of the network algorithm ! * (if it was not applied, the return value is na_not_applied). ! * The best variable is returned in *pbest. It also has two possible ! * algorithms for determining the best match: the network algorithm, ! * and the standard Apache algorithm. These are split out into ! * separate functions (is_variant_better_na() and is_variant_better()). * ! * Previously, best_match iterated first through the content_types ! * in the Accept: header, then checked each variant, and eliminated ! * those that didn't match the variant's type. We cannot do this because ! * we need full information, including language, charset, etc ! * quality for _every_ variant, for the Alternates: header, ! * and (possibly) the human-readable choice responses or 406 errors. ! * ! * After the 'best' (if any) is determined, the overall result of ! * the negotiation is obtained. If the network algorithm was not ! * in use, the result is na_not_applied. Else the result is ! * na_list if 'short accept header' is in use, else na_list ! * if _no_ best match was found, or na_choice if a best match ! * was found. ! */ ! ! /* Firstly, the negotiation 'network algorithm' from Holtman. ! */ ! ! int is_variant_better_na(negotiation_state *neg, var_rec *variant, var_rec *best, float *p_bestq) ! { ! float bestq = *p_bestq, q; ! ! /* Note: Encoding is not negotiated in the Holtman ! * transparent neg draft, so we ignored it here. But ! * it does mean we could return encodings the UA ! * or proxy cannot handle. Eek. */ ! ! q = variant->accept_type_quality * ! variant->type_quality * ! variant->charset_quality * ! variant->lang_quality; ! ! #ifdef NEG_DEBUG ! fprintf(stderr, "Variant: file=%s type=%s lang=%s acceptq=%1.3f langq=%1.3f typeq=%1.3f q=%1.3f\n", ! variant->file_name ? variant->file_name : "", ! variant->type_name ? variant->type_name : "", ! variant->content_language ? variant->content_language : "", ! variant->accept_type_quality, ! variant->lang_quality, ! variant->type_quality, ! q ! ); ! #endif ! ! if (q > bestq) { ! *p_bestq = q; ! return 1; ! } ! return 0; ! } ! ! /* Negotiation algorithm as used by previous versions of Apache ! * (just about). */ ! float is_variant_better(negotiation_state *neg, var_rec *variant, var_rec *best, float *p_bestq) { ! float bestq = *p_bestq, q; ! int levcmp; ! /* ! * For non-transparent negotiation, server can choose how ! * to handle the negotiation. We'll use the following in ! * order: content-type, language, content-type level, charset, ! * content length. ! * ! * For each check, we have three possible outcomes: ! * This variant is worse than current best: return 0 ! * This variant is better than the current best: ! * assign this variant's q to *p_bestq, and return 1 ! * This variant is just as desirable as the current best: ! * drop through to the next test. ! * ! * This code is written in this long-winded way to allow future ! * customisation, either by the addition of additional ! * checks, or to allow the order of the checks to be determined ! * by configuration options (e.g. we might prefer to check ! * language quality _before_ content type). ! */ ! ! /* First though, eliminate this variant if it is not ! * acceptable by type, charset, encoding or language. ! */ ! ! if (variant->encoding_quality == 0 || ! variant->lang_quality == 0 || ! variant->type_quality == 0 || ! variant->charset_quality == 0 || ! variant->accept_type_quality == 0) ! return 0; /* don't consider unacceptables */ ! ! q = variant->accept_type_quality * variant->type_quality; ! if (q == 0.0 || q < bestq) return 0; ! if (q > bestq || !best) { ! *p_bestq = q; ! return 1; } + + /* language */ + if (variant->lang_quality < best->lang_quality) + return 0; + if (variant->lang_quality > best->lang_quality) { + *p_bestq = q; + return 1; + } + + /* if language qualities were equal, try the LanguagePriority + * stuff */ + if (variant->lang_index > best->lang_index) + return 0; + if (variant->lang_index < best->lang_index) { + *p_bestq = q; + return 1; + } + + /* content-type level (text/html only?) */ + levcmp = level_cmp (variant, best); + if (levcmp == -1) return 0; + if (levcmp == 1) { + *p_bestq = q; + return 1; + } + + /* encoding -- can only be 1 or 0, and if 0 we eliminated this + * variant at the start of this function. However we + * prefer variants with no encoding over those with encoding */ + if (!*best->content_encoding && *variant->content_encoding) + return 0; + if (*best->content_encoding && !*variant->content_encoding) { + *p_bestq = q; + return 1; + } + ! /* charset */ ! if (variant->charset_quality < best->charset_quality) ! return 0; ! if (variant->charset_quality > best->charset_quality) { ! *p_bestq = q; ! return 1; ! } ! ! ! /* content length if all else equal */ ! if (find_content_length(neg, variant) ! >= ! find_content_length(neg, best)) ! return 0; ! ! /* ok, to get here means every thing turned out equal, except ! * we have a shorter content length, so use this variant */ ! *p_bestq = q; ! return 1; } ! int best_match(negotiation_state *neg, var_rec **pbest) { ! int j; var_rec *best = NULL; ! float bestq = 0.0; ! enum algorithm_results algorithm_result = na_not_applied; var_rec *avail_recs = (var_rec *)neg->avail_vars->elts; ! set_default_lang_quality(neg); ! ! /* ! * Find the 'best' variant */ ! for (j = 0; j < neg->avail_vars->nelts; ++j) { ! ! var_rec *variant = &avail_recs[j]; ! ! /* Find all the relevant 'quality' values from the ! * Accept... headers, and store in the variant ! */ ! set_accept_quality(neg, variant); ! set_language_quality(neg, variant); ! set_encoding_quality(neg, variant); ! set_charset_quality(neg, variant); ! ! /* Now find out if this variant is better than the current ! * best, either using the network algorithm, or Apache's ! * internal server-driven algorithm. Presumably other ! * server-driven algorithms are possible, and could be ! * implemented here. ! */ ! ! if (neg->use_transparent_neg) { ! if (is_variant_better_na(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_transparent_neg) { ! if (neg->short_accept_headers) ! algorithm_result = na_list; ! else { ! /* From Holtman, result is: ! * If variant & URI are not neigbors, list_ua or list_os ! * Else ! * If UA can do trans neg ! * IF best q > 0, choice_ua ! * ELSE list_ua ! * ELSE ! * IF best > 0, choose_os ! * ELSE list_os (or forward_os on proxy) ! */ ! ! /* assume variant and URI are neigbors (since URI in ! * var map must be in same directory) */ ! ! algorithm_result = bestq ? na_choice : na_list; ! } ! } ! *pbest = best; ! return algorithm_result; ! } ! /* ! * Sets the Alternates and Vary headers, used if we are going to ! * return 406 Not Acceptable status, a 300 Multiple Choice status, ! * or a Choice response. ! * ! * 'type' is the result of the network algorithm, if applied. ! * We do different things if the network algorithm was not applied ! * (type == na_not_applied): no Alternates header, and Vary: ! * does not include 'negotiate'. ! * ! * We should also add a max-age lifetime for the Alternates header, ! * but how long we we give it? Presumably this should be ! * configurable in the map file. ! */ ! void set_neg_headers(request_rec *r, negotiation_state *neg, int na_result) ! { ! int j; ! var_rec *avail_recs = (var_rec *)neg->avail_vars->elts; ! char *sample_type = NULL; ! char *sample_language = NULL; ! char *sample_encoding = NULL; ! char *sample_charset = NULL; ! int vary_by_type = 0; ! int vary_by_language = 0; ! int vary_by_charset = 0; ! int vary_by_encoding = 0; ! array_header *hdrs; ! ! /* Question: do we output Alternates and Vary headers in the ! * error document of 406 messages? I assume not here. But we ! * definitely need to get these headers output when sending ! * a 300 'error', so if the result of the network algorithm ! * was a List response, set them in err_headers_out. ! */ ! hdrs = (na_result == na_list) ? r->err_headers_out : r->headers_out; ! ! for (j = 0; j < neg->avail_vars->nelts; ++j) { ! ! var_rec *variant = &avail_recs[j]; ! char *rec; ! char qstr[6]; ! long len; ! char lenstr[20]; /* is this long enough? */ ! ! sprintf(qstr, "%1.3f", variant->type_quality); ! ! /* Strip trailing zeros (saves those valuable network bytes) */ ! if (qstr[4] == '0') { ! qstr[4] = '\0'; ! if (qstr[3] == '0') { ! qstr[3] = '\0'; ! if (qstr[2] == '0') { ! qstr[1] = '\0'; ! } ! } ! } ! ! ! rec = pstrcat(r->pool, "{\"", variant->file_name, "\" ", qstr, NULL); ! if (variant->type_name) { ! if (*variant->type_name) ! rec = pstrcat(r->pool, rec, " {type ", ! variant->type_name, "}", NULL); ! if (!sample_type) sample_type = variant->type_name; ! else if (strcmp(sample_type, variant->type_name)) ! vary_by_type = 1; ! } ! if (variant->content_language) { ! if (*variant->content_language) ! rec = pstrcat(r->pool, rec, " {language ", ! variant->content_language, "}", NULL); ! if (!sample_language) sample_language = variant->content_language; ! else if (strcmp(sample_language, variant->content_language)) ! vary_by_language = 1; ! } ! if (variant->content_encoding) { ! if (!sample_encoding) sample_encoding = variant->content_encoding; ! else if (strcmp(sample_encoding, variant->content_encoding)) ! vary_by_encoding = 1; ! } ! if (variant->content_charset) { ! if (*variant->content_charset) ! rec = pstrcat(r->pool, rec, " {charset ", ! variant->content_charset, "}", NULL); ! if (!sample_charset) sample_charset = variant->content_charset; ! else if (strcmp(sample_charset, variant->content_charset)) ! vary_by_charset = 1; ! } ! if ((len = find_content_length(neg, variant)) != 0) { ! sprintf(lenstr, "%ld", len); ! rec = pstrcat(r->pool, rec, " {length ", lenstr, "}", NULL); ! } ! ! rec = pstrcat(r->pool, rec, "}", NULL); ! ! if (na_result != na_not_applied) ! table_merge(hdrs, "Alternates", rec); } ! if (na_result != na_not_applied) ! table_merge(hdrs, "Vary", "negotiate"); ! if (vary_by_type) ! table_merge(hdrs, "Vary", "accept"); ! if (vary_by_language) ! table_merge(hdrs, "Vary", "accept-language"); ! if (vary_by_charset) ! table_merge(hdrs, "Vary", "accept-charset"); ! if (vary_by_encoding && na_result == na_not_applied) ! table_merge(hdrs, "Vary", "accept-encoding"); } ! /********************************************************************** ! * ! * Return an HTML list of variants. This is output as part of the ! * 300 or 406 status body. ! */ ! ! char *make_variant_list (request_rec *r, negotiation_state *neg) { int i; ! char *t; ! ! t = pstrdup(r->pool, "Available variants:\n<ul>\n"); ! for (i = 0; i < neg->avail_vars->nelts; ++i) { ! var_rec *variant = &((var_rec *)neg->avail_vars->elts)[i]; ! char *filename = variant->file_name ? variant->file_name : ""; ! char *content_type = variant->type_name ? variant->type_name : ""; ! char *content_language = ! variant->content_language ? variant->content_language : ""; ! char *description = variant->description ? variant->description : ""; ! ! /* The format isn't very neat, and it would be nice to make ! * the tags human readable (eg replace 'language en' with ! * 'English'). */ ! t = pstrcat(r->pool, t, "<li><a href=\"", filename, "\">", ! filename, "</a> ", description, ! " type ", content_type, ! *content_language ? " language " : "", content_language, ! "\n", ! NULL); ! } ! t = pstrcat(r->pool, t, "</ul>\n", NULL); ! ! return t; ! } ! ! void store_variant_list (request_rec *r, negotiation_state *neg) ! { ! table_set (r->notes, "variant-list", make_variant_list (r, neg)); ! } ! ! /* Called if we got a "Choice" response from the network algorithm. ! * It checks the result of the chosen variant to see if it ! * is itself negotiated (if so, return error VARIANT_ALSO_VARIES). ! * Otherwise, add the appropriate headers to the current response. ! */ ! int setup_choice_response(request_rec *r, negotiation_state *neg, var_rec *variant) ! { ! request_rec *sub_req; ! char *sub_vary; ! ! if (!variant->sub_req) { ! sub_req = sub_req_lookup_file(variant->file_name, r); ! if (sub_req->status != 200 && sub_req->status != 300) ! return sub_req->status; ! variant->sub_req = sub_req; ! } ! else ! sub_req = variant->sub_req; ! ! /* The network algorithm told us to return a "Choice" ! * response. This is the normal variant response, with ! * some extra headers. First, ensure that the chosen ! * variant did not itself return a "List" or "Choice" response. ! * If not, set the appropriate headers, and fall through to ! * the normal variant handling */ ! if ((sub_req->status == 300) || ! (table_get(sub_req->headers_out, "Alternates")) || ! (table_get(sub_req->headers_out, "Content-Location"))) ! return VARIANT_ALSO_VARIES; ! ! if ((sub_vary = table_get(sub_req->headers_out, "Vary")) != NULL) ! table_set(r->headers_out, "Variant-Vary", sub_vary); ! table_set(r->headers_out, "Content-Location", variant->file_name); ! set_neg_headers(r, neg, na_choice); /* add Alternates and Vary */ ! /* to do: add Expires */ ! ! return 0; } /**************************************************************** *************** *** 1091,1116 **** negotiation_state *neg = parse_accept_headers (r); var_rec *best; int res; ! char *vary, *udir; if ((res = read_type_map (neg, r->filename))) return res; maybe_add_default_encodings(neg, 0); ! if (!(best = best_match(neg))) { log_reason ("no acceptable variant", r->filename, r); return NOT_ACCEPTABLE; } /* Make sure caching works - Vary should handle HTTP/1.1, but for * HTTP/1.0, we can't allow caching at all. NB that we merge the * header in case some other module negotiates on something else. */ if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001)) r->no_cache = 1; ! if ((vary = set_vary(r->pool, neg))) ! table_merge(r->err_headers_out, "Vary", vary); udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri)); udir = escape_uri(r->pool, udir); --- 1716,1769 ---- negotiation_state *neg = parse_accept_headers (r); var_rec *best; int res; + int na_result; ! char *udir; if ((res = read_type_map (neg, r->filename))) return res; maybe_add_default_encodings(neg, 0); ! na_result = best_match(neg, &best); ! ! /* na_result is one of ! * na_not_applied: we didn't use the network algorithm ! * na_choice: return a "Choice" response ! * na_list: return a "List" response (no variant chosen) ! */ ! ! if (na_result == na_list) { ! set_neg_headers(r, neg, na_list); ! store_variant_list (r, neg); ! return MULTIPLE_CHOICES; ! } ! ! if (!best) { log_reason ("no acceptable variant", r->filename, r); + + set_neg_headers(r, neg, na_result); + store_variant_list (r, neg); return NOT_ACCEPTABLE; } + if (na_result == na_choice) { + if ((res = setup_choice_response(r, neg, best)) != 0) + return res; + + /* Run the request. Should we check the output status + * for REDIRECT?? */ + return run_sub_req(best->sub_req); + } + /* Make sure caching works - Vary should handle HTTP/1.1, but for * HTTP/1.0, we can't allow caching at all. NB that we merge the * header in case some other module negotiates on something else. */ if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001)) r->no_cache = 1; ! ! if (na_result == na_not_applied) ! set_neg_headers(r, neg, na_not_applied); udir = make_dirstr (r->pool, r->uri, count_dirs (r->uri)); udir = escape_uri(r->pool, udir); *************** *** 1123,1130 **** negotiation_state *neg; var_rec *best; request_rec *sub_req; - char *vary; int res; if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI)) return DECLINED; --- 1776,1783 ---- negotiation_state *neg; var_rec *best; request_rec *sub_req; int res; + int na_result; /* result of network algorithm */ if (r->finfo.st_mode != 0 || !(allow_options (r) & OPT_MULTI)) return DECLINED; *************** *** 1134,1156 **** if ((res = read_types_multi (neg))) return res; maybe_add_default_encodings(neg, ! r->method_number != M_GET ! || r->args || r->path_info); if (neg->avail_vars->nelts == 0) return DECLINED; ! if (!(best = best_match(neg))) { log_reason ("no acceptable variant", r->filename, r); return NOT_ACCEPTABLE; } if (! (sub_req = best->sub_req)) { /* We got this out of a map file, so we don't actually have ! * a sub_req structure yet. Get one now. ! */ sub_req = sub_req_lookup_file (best->file_name, r); ! if (sub_req->status != 200) return sub_req->status; } /* BLETCH --- don't multi-resolve non-ordinary files */ --- 1787,1830 ---- if ((res = read_types_multi (neg))) return res; maybe_add_default_encodings(neg, ! r->method_number != M_GET ! || r->args || r->path_info); if (neg->avail_vars->nelts == 0) return DECLINED; ! na_result = best_match(neg, &best); ! if (na_result == na_list) { ! /* ! * Network algorithm tols us to output a "List" response. ! * This is output at a 300 status code, which we will ! * return. The list of variants will be stored in r->notes ! * under the name "variants-list". ! */ ! set_neg_headers(r, neg, na_list); /* set Alternates: and Vary: */ ! ! store_variant_list (r, neg); ! return MULTIPLE_CHOICES; ! } ! ! if (!best) { log_reason ("no acceptable variant", r->filename, r); + + set_neg_headers (r, neg, na_result); + store_variant_list (r, neg); return NOT_ACCEPTABLE; } + if (na_result == na_choice) + if ((res = setup_choice_response(r, neg, best)) != 0) + return res; + if (! (sub_req = best->sub_req)) { /* We got this out of a map file, so we don't actually have ! * a sub_req structure yet. Get one now. ! */ sub_req = sub_req_lookup_file (best->file_name, r); ! if (sub_req->status != 200) return sub_req->status; } /* BLETCH --- don't multi-resolve non-ordinary files */ *************** *** 1161,1168 **** if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001)) r->no_cache = 1; ! if ((vary = set_vary(r->pool, neg))) ! table_merge(r->err_headers_out, "Vary", vary); r->filename = sub_req->filename; r->handler = sub_req->handler; --- 1835,1843 ---- if (!do_cache_negotiated_docs(r->server) && (r->proto_num < 1001)) r->no_cache = 1; ! ! if (na_result == na_not_applied) ! set_neg_headers(r, neg, na_not_applied); r->filename = sub_req->filename; r->handler = sub_req->handler; 1.65 +14 -1 apache/src/CHANGES Index: CHANGES =================================================================== RCS file: /export/home/cvs/apache/src/CHANGES,v retrieving revision 1.64 retrieving revision 1.65 diff -C3 -r1.64 -r1.65 *** CHANGES 1996/09/17 14:58:30 1.64 --- CHANGES 1996/09/24 12:45:00 1.65 *************** *** 1,6 **** ! $Id: CHANGES,v 1.64 1996/09/17 14:58:30 chuck Exp $ Changes with Apache 1.2b1: *) Netscape 2.x keepalive fix, using BrowserMatch/mod_browser. [Chuck Murcko] --- 1,19 ---- ! $Id: CHANGES,v 1.65 1996/09/24 12:45:00 mjc Exp $ Changes with Apache 1.2b1: + + *) Negotiation updated to implement all aspects of HTTP/1.1, including + charset and encoding negotiation. Now server-driven negotiation + can return a list of variants (the MULTIPLE_CHOICE response). + Some code included for transparent negotiation (not compiled + in by default). [Paul Sutton] + + *) IP Virtual Hosts now work where the vhost IP address is the main + IP address of the host and the port s not the default port. + [Paul Sutton] + + *) Domain names on 'allow' and 'deny' lines are now case-insensitive. + [Paul Sutton] *) Netscape 2.x keepalive fix, using BrowserMatch/mod_browser. [Chuck Murcko]