fielding 99/01/03 04:04:41
Modified: src ApacheCore.def CHANGES src/include alloc.h ap_mmn.h src/main alloc.c src/modules/standard mod_negotiation.c src/support httpd.exp Log: Major overhaul of mod_negotiation.c, part 2. - properly handle "identity" within Accept-Encoding. - allow encoded variants in RVSA negotiation and let them appear in the Alternates field using the non-standard "encoding" tag-list. - fixed both negotiation algorithms so that an explicitly accepted encoding is preferred over no encoding if "identity" is not included within Accept-Encoding. - added ap_array_pstrcat() to alloc.c for efficient concatenation of large substring sequences. Bumped MMN. - replaced O(n^2) memory hogs in mod_negotiation with ap_array_pstrcat. Revision Changes Path 1.5 +1 -0 apache-1.3/src/ApacheCore.def Index: ApacheCore.def =================================================================== RCS file: /home/cvs/apache-1.3/src/ApacheCore.def,v retrieving revision 1.4 retrieving revision 1.5 diff -u -r1.4 -r1.5 --- ApacheCore.def 1998/12/29 09:46:12 1.4 +++ ApacheCore.def 1999/01/03 12:04:34 1.5 @@ -322,4 +322,5 @@ ap_single_module_configure @315 ap_single_module_init @316 ap_make_etag @317 + ap_array_pstrcat @318 1.1196 +13 -1 apache-1.3/src/CHANGES Index: CHANGES =================================================================== RCS file: /home/cvs/apache-1.3/src/CHANGES,v retrieving revision 1.1195 retrieving revision 1.1196 diff -u -r1.1195 -r1.1196 --- CHANGES 1999/01/02 14:12:18 1.1195 +++ CHANGES 1999/01/03 12:04:34 1.1196 @@ -50,7 +50,19 @@ module's token was being added first before the Apache token. This has been fixed. [Jim Jagielski] - *) Major overhaul of mod_negotiation.c. + *) Major overhaul of mod_negotiation.c, part 2. + - properly handle "identity" within Accept-Encoding. + - allow encoded variants in RVSA negotiation and let them appear in + the Alternates field using the non-standard "encoding" tag-list. + - fixed both negotiation algorithms so that an explicitly accepted + encoding is preferred over no encoding if "identity" is not + included within Accept-Encoding. + - added ap_array_pstrcat() to alloc.c for efficient concatenation + of large substring sequences. + - replaced O(n^2) memory hogs in mod_negotiation with ap_array_pstrcat. + [Roy Fielding] + + *) Major overhaul of mod_negotiation.c, part 1. - cleanups to mod_negotiation comments and code structure - made compliant with HTTP/1.1 proposed standard (rfc2068) and added support for everything in the upcoming HTTP/1.1 1.66 +9 -0 apache-1.3/src/include/alloc.h Index: alloc.h =================================================================== RCS file: /home/cvs/apache-1.3/src/include/alloc.h,v retrieving revision 1.65 retrieving revision 1.66 diff -u -r1.65 -r1.66 --- alloc.h 1999/01/01 19:04:38 1.65 +++ alloc.h 1999/01/03 12:04:36 1.66 @@ -149,6 +149,15 @@ API_EXPORT(array_header *) ap_append_arrays(pool *, const array_header *, const array_header *); +/* ap_array_pstrcat generates a new string from the pool containing + * the concatenated sequence of substrings referenced as elements within + * the array. The string will be empty if all substrings are empty or null, + * or if there are no elements in the array. + * If sep is non-NUL, it will be inserted between elements as a separator. + */ +API_EXPORT(char *) ap_array_pstrcat(pool *p, const array_header *arr, + const char sep); + /* copy_array copies the *entire* array. copy_array_hdr just copies * the header, and arranges for the elements to be copied if (and only * if) the code subsequently does a push or arraycat. 1.18 +3 -2 apache-1.3/src/include/ap_mmn.h Index: ap_mmn.h =================================================================== RCS file: /home/cvs/apache-1.3/src/include/ap_mmn.h,v retrieving revision 1.17 retrieving revision 1.18 diff -u -r1.17 -r1.18 --- ap_mmn.h 1999/01/01 20:27:47 1.17 +++ ap_mmn.h 1999/01/03 12:04:36 1.18 @@ -193,13 +193,14 @@ * 19981229 - mod_negotiation overhaul -- added ap_make_etag() * and added vlist_validator to request_rec. * 19990101 - renamed macro escape_uri() to ap_escape_uri() - * - Added MODULE_MAGIC_COOKIE to identify module structures + * - added MODULE_MAGIC_COOKIE to identify module structs + * 19990103 (1.3.4-dev) - added ap_array_pstrcat() */ #define MODULE_MAGIC_COOKIE 0x41503133UL /* "AP13" */ #ifndef MODULE_MAGIC_NUMBER_MAJOR -#define MODULE_MAGIC_NUMBER_MAJOR 19990101 +#define MODULE_MAGIC_NUMBER_MAJOR 19990103 #endif #define MODULE_MAGIC_NUMBER_MINOR 0 /* 0...n */ #define MODULE_MAGIC_NUMBER MODULE_MAGIC_NUMBER_MAJOR /* backward compat */ 1.105 +54 -0 apache-1.3/src/main/alloc.c Index: alloc.c =================================================================== RCS file: /home/cvs/apache-1.3/src/main/alloc.c,v retrieving revision 1.104 retrieving revision 1.105 diff -u -r1.104 -r1.105 --- alloc.c 1999/01/01 19:04:47 1.104 +++ alloc.c 1999/01/03 12:04:37 1.105 @@ -1047,6 +1047,60 @@ return res; } +/* ap_array_pstrcat generates a new string from the pool containing + * the concatenated sequence of substrings referenced as elements within + * the array. The string will be empty if all substrings are empty or null, + * or if there are no elements in the array. + * If sep is non-NUL, it will be inserted between elements as a separator. + */ +API_EXPORT(char *) ap_array_pstrcat(pool *p, const array_header *arr, + const char sep) +{ + char *cp, *res, **strpp; + int i, len; + + if (arr->nelts <= 0 || arr->elts == NULL) /* Empty table? */ + return (char *) ap_pcalloc(p, 1); + + /* Pass one --- find length of required string */ + + len = 0; + for (i = 0, strpp = (char **) arr->elts; ; ++strpp) { + if (strpp && *strpp != NULL) { + len += strlen(*strpp); + } + if (++i >= arr->nelts) + break; + if (sep) + ++len; + } + + /* Allocate the required string */ + + res = (char *) ap_palloc(p, len + 1); + cp = res; + + /* Pass two --- copy the argument strings into the result space */ + + for (i = 0, strpp = (char **) arr->elts; ; ++strpp) { + if (strpp && *strpp != NULL) { + len = strlen(*strpp); + memcpy(cp, *strpp, len); + cp += len; + } + if (++i >= arr->nelts) + break; + if (sep) + *cp++ = sep; + } + + *cp = '\0'; + + /* Return the result string */ + + return res; +} + /***************************************************************** * 1.92 +186 -190 apache-1.3/src/modules/standard/mod_negotiation.c Index: mod_negotiation.c =================================================================== RCS file: /home/cvs/apache-1.3/src/modules/standard/mod_negotiation.c,v retrieving revision 1.91 retrieving revision 1.92 diff -u -r1.91 -r1.92 --- mod_negotiation.c 1999/01/01 19:05:11 1.91 +++ mod_negotiation.c 1999/01/03 12:04:38 1.92 @@ -80,17 +80,6 @@ module MODULE_VAR_EXPORT negotiation_module; -static char *merge_string_array(pool *p, array_header *arr, char *sep) -{ - int i; - char *t = ""; - - for (i = 0; i < arr->nelts; i++) { - t = ap_pstrcat(p, t, (i ? sep : ""), ((char **) arr->elts)[i], NULL); - } - return t; -} - static void *create_neg_dir_config(pool *p, char *dummy) { neg_dir_config *new = (neg_dir_config *) ap_palloc(p, sizeof(neg_dir_config)); @@ -1653,61 +1642,42 @@ /* * set_encoding_quality determines whether the encoding for a particular - * variant is acceptable for the user-agent. There are no q-values on - * encodings, so this is set as an integer value variant->encoding_quality. + * variant is acceptable for the user-agent. * * The rules for encoding are that if the user-agent does not supply - * any Accept-Encoding header, then all encodings are allowed. If - * there is an empty Accept-Encoding header, then no encodings are + * any Accept-Encoding header, then all encodings are allowed but a + * variant with no encoding should be preferred. + * If there is an empty Accept-Encoding header, then no encodings are * acceptable. If there is a non-empty Accept-Encoding header, then - * any of the listed encodings are acceptable, as well as no encoding. - * - * In the later case, we assume that it is preferable to return a - * suitable encoded variant in preference to an unencoded variant. - * - * The variant with the higher value should be preferred over variants - * with lower values. The values used are 0 if this variant is - * unacceptable (if it is encoded but the user-agent does not accept - * this encoding or any encodings), 1 if this variant is acceptable to - * the user-agent either because this variant is unencoded or the - * user-agent does not give an Accept-Encoding header, or 2 if this - * variant is encoding and the user-agent specifically asks for this - * encoding on its Accept-Encoding header. The effect of this is to - * prefer encoded variants when they user-agent explicitly says that - * the encoding is acceptable, otherwise encoded and unencoded - * variants get the same encoding_quality. - */ - -/* For a given variant, find the encoding quality using the - * Accept-Encoding header. + * any of the listed encodings are acceptable, as well as no encoding + * unless the "identity" encoding is specifically excluded. */ - static void set_encoding_quality(negotiation_state *neg, var_rec *variant) { - int i; accept_rec *accept_recs; const char *enc = variant->content_encoding; accept_rec *star = NULL; - - if (!enc || is_identity_encoding(enc)) { - return; - } + float value_if_not_found = 0.0f; + int i; if (!neg->accept_encodings) { /* We had no Accept-Encoding header, assume that all - * encodings are acceptable with a quality of 1. - * - * XXX: This assumption is unsafe, and the only thing which - * prevents it from causing serious harm is that the Apache - * negotiation algorithm (currently) always prefers unencoded - * variants over encoded ones, while the RVSA/1.0 algorithm - * (currently) ignores encoded variants entirely. -kh + * encodings are acceptable with a low quality, + * but we prefer no encoding if available. */ + if (!enc || is_identity_encoding(enc)) + variant->encoding_quality = 1.0f; + else + variant->encoding_quality = 0.5f; - variant->encoding_quality = 1; return; } + if (!enc || is_identity_encoding(enc)) { + enc = "identity"; + value_if_not_found = 0.0001f; + } + accept_recs = (accept_rec *) neg->accept_encodings->elts; /* Go through each of the encodings on the Accept-Encoding: header, @@ -1741,9 +1711,9 @@ } /* Encoding not found on Accept-Encoding: header, so it is - * _not_ acceptable + * _not_ acceptable unless it is the identity (no encoding) */ - variant->encoding_quality = 0; + variant->encoding_quality = value_if_not_found; } /************************************************************* @@ -1783,14 +1753,10 @@ float bestq = *p_bestq, q; /* TCN does not cover negotiation on content-encoding. For now, - * we ignore all variants which have a content-encoding, i.e. we - * return 0 for them to signify that they are never better than - * anything. - * - * XXX Todo: improve on this, e.g. by adding a second negotiation - * phase on content encoding if the RVSA is used. + * we ignore the encoding unless it was explicitly excluded. */ - if (variant->content_encoding) return 0; + if (variant->encoding_quality == 0.0f) + return 0; q = variant->mime_type_quality * variant->source_quality * @@ -1812,7 +1778,7 @@ (variant->file_name ? variant->file_name : ""), (variant->mime_name ? variant->mime_name : ""), (variant->content_languages - ? merge_string_array(neg->pool, variant->content_languages, ",") + ? ap_array_pstrcat(neg->pool, variant->content_languages, ',') : ""), variant->source_quality, variant->mime_type_quality, @@ -1830,6 +1796,13 @@ return 1; } if (q == bestq) { + /* If the best variant's encoding is of lesser quality than + * this variant, then we prefer this variant + */ + if (variant->encoding_quality > best->encoding_quality) { + *p_bestq = q; + return 1; + } /* If the best variant's charset is ISO-8859-1 and this variant has * the same charset quality, then we prefer this variant */ @@ -1880,11 +1853,11 @@ * acceptable by type, charset, encoding or language. */ - if (variant->encoding_quality == 0 || - variant->lang_quality == 0 || - variant->source_quality == 0 || - variant->charset_quality == 0 || - variant->mime_type_quality == 0) { + if (variant->encoding_quality == 0.0f || + variant->lang_quality == 0.0f || + variant->source_quality == 0.0f || + variant->charset_quality == 0.0f || + variant->mime_type_quality == 0.0f) { return 0; /* don't consider unacceptables */ } @@ -1933,30 +1906,6 @@ return 1; } - /* XXX: The encoding negotiation code below knows about - * Accept-Encoding q values, but has the same strange tie-breaking - * as the version above if the qualities are equal. - */ - - /* Prefer the highest value for encoding_quality. If they are - * equal, prefer the variant without any encoding. - */ - if (variant->encoding_quality < best->encoding_quality) { - return 0; - } - if (variant->encoding_quality > best->encoding_quality) { - *p_bestq = q; - return 1; - } - - if (best->content_encoding == NULL && variant->content_encoding) { - return 0; - } - if (best->content_encoding && variant->content_encoding == NULL) { - *p_bestq = q; - return 1; - } - /* charset */ if (variant->charset_quality < best->charset_quality) { return 0; @@ -1979,6 +1928,16 @@ return 1; } + /* Prefer the highest value for encoding_quality. + */ + if (variant->encoding_quality < best->encoding_quality) { + return 0; + } + if (variant->encoding_quality > best->encoding_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; @@ -2093,10 +2052,16 @@ { table *hdrs; var_rec *avail_recs = (var_rec *) neg->avail_vars->elts; - char *sample_type = NULL; - char *sample_language = NULL; + const char *sample_type = NULL; + const char *sample_language = NULL; const char *sample_encoding = NULL; - char *sample_charset = NULL; + const char *sample_charset = NULL; + char *lang; + char *qstr; + char *lenstr; + long len; + array_header *arr; + int max_vlist_array = (neg->avail_vars->nelts * 21); int first_variant = 1; int vary_by_type = 0; int vary_by_language = 0; @@ -2104,25 +2069,77 @@ int vary_by_encoding = 0; int j; - /* Put headers into err_headers_out, new send_http_header() - * outputs both headers_out and err_headers_out. - */ + /* In order to avoid O(n^2) memory copies in building Alternates, + * we preallocate a table with the maximum substrings possible, + * fill it with the variant list, and then concatenate the entire array. + * Note that if you change the number of substrings pushed, you also + * need to change the calculation of max_vlist_array above. + */ + if (neg->send_alternates && neg->avail_vars->nelts) + arr = ap_make_array(r->pool, max_vlist_array, sizeof(char *)); + else + arr = NULL; - /* NB that we merge the headers in case some other module - * negotiates on something else. + /* Put headers into err_headers_out, since send_http_header() + * outputs both headers_out and err_headers_out. */ - hdrs = r->err_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[22]; /* enough for 2^64 */ - char *lang; + + if (variant->content_languages && variant->content_languages->nelts) { + lang = ap_array_pstrcat(r->pool, variant->content_languages, ','); + } + else { + lang = NULL; + } + + /* Calculate Vary by looking for any difference between variants */ + + if (first_variant) { + sample_type = variant->mime_type; + sample_charset = variant->content_charset; + sample_language = lang; + sample_encoding = variant->content_encoding; + } + else { + if (!vary_by_type && + strcmp(sample_type ? sample_type : "", + variant->mime_type ? variant->mime_type : "")) { + vary_by_type = 1; + } + if (!vary_by_charset && + strcmp(sample_charset ? sample_charset : "", + variant->content_charset ? + variant->content_charset : "")) { + vary_by_charset = 1; + } + if (!vary_by_language && + strcmp(sample_language ? sample_language : "", + lang ? lang : "")) { + vary_by_language = 1; + } + if (!vary_by_encoding && + strcmp(sample_encoding ? sample_encoding : "", + variant->content_encoding ? + variant->content_encoding : "")) { + vary_by_encoding = 1; + } + } + first_variant = 0; + + if (!neg->send_alternates) + continue; + + /* Generate the string components for this Alternates entry */ + + *((const char **) ap_push_array(arr)) = "{\""; + *((const char **) ap_push_array(arr)) = variant->file_name; + *((const char **) ap_push_array(arr)) = "\" "; - ap_snprintf(qstr, sizeof(qstr), "%1.3f", variant->source_quality); + qstr = (char *) ap_palloc(r->pool, 6); + ap_snprintf(qstr, 6, "%1.3f", variant->source_quality); /* Strip trailing zeros (saves those valuable network bytes) */ if (qstr[4] == '0') { @@ -2134,65 +2151,35 @@ } } } + *((const char **) ap_push_array(arr)) = qstr; - /* XXX: iteratively concatenating rec is a poor design, O(n^2) mem! */ - - rec = ap_pstrcat(r->pool, "{\"", variant->file_name, "\" ", qstr, NULL); - - if (first_variant) { - sample_type = variant->mime_type; - } - else if (strcmp(sample_type ? sample_type : "", - variant->mime_type ? variant->mime_type : "")) { - vary_by_type = 1; - } if (variant->mime_type && *variant->mime_type) { - rec = ap_pstrcat(r->pool, rec, " {type ", - variant->mime_type, "}", NULL); + *((const char **) ap_push_array(arr)) = " {type "; + *((const char **) ap_push_array(arr)) = variant->mime_type; + *((const char **) ap_push_array(arr)) = "}"; } - - if (variant->content_languages && variant->content_languages->nelts) { - lang = merge_string_array(r->pool, - variant->content_languages, ","); - rec = ap_pstrcat(r->pool, rec, " {language ", lang, "}", NULL); - } - else { - lang = NULL; - } - if (first_variant) { - sample_language = lang; - } - else if (strcmp(sample_language ? sample_language : "", - lang ? lang : "")) { - vary_by_language = 1; - } - - if (first_variant) { - sample_encoding = variant->content_encoding; - } - else if (strcmp(sample_encoding ? sample_encoding : "", - variant->content_encoding ? - variant->content_encoding : "")) { - vary_by_encoding = 1; - } - - if (first_variant) { - sample_charset = variant->content_charset; - } - else if (strcmp(sample_charset ? sample_charset : "", - variant->content_charset ? - variant->content_charset : "")) { - vary_by_charset = 1; - } if (variant->content_charset && *variant->content_charset) { - rec = ap_pstrcat(r->pool, rec, " {charset ", - variant->content_charset, "}", NULL); + *((const char **) ap_push_array(arr)) = " {charset "; + *((const char **) ap_push_array(arr)) = variant->content_charset; + *((const char **) ap_push_array(arr)) = "}"; + } + if (lang) { + *((const char **) ap_push_array(arr)) = " {language "; + *((const char **) ap_push_array(arr)) = lang; + *((const char **) ap_push_array(arr)) = "}"; + } + if (variant->content_encoding && *variant->content_encoding) { + /* Strictly speaking, this is non-standard, but so is TCN */ + + *((const char **) ap_push_array(arr)) = " {encoding "; + *((const char **) ap_push_array(arr)) = variant->content_encoding; + *((const char **) ap_push_array(arr)) = "}"; } /* Note that the Alternates specification (in rfc2295) does * not require that we include {length x}, so we could omit it * if determining the length is too expensive. We currently - * always include it though. + * always include it though. 22 bytes is enough for 2^64. * * If the variant is a CGI script, find_content_length would * return the length of the script, not the output it @@ -2204,30 +2191,26 @@ * (without breaking things if the type map specifies a * content-length, which currently leads to the correct result). */ - if (neg->send_alternates - && !(variant->sub_req && variant->sub_req->handler) + if (!(variant->sub_req && variant->sub_req->handler) && (len = find_content_length(neg, variant)) != 0) { - ap_snprintf(lenstr, sizeof(lenstr), "%ld", len); - rec = ap_pstrcat(r->pool, rec, " {length ", lenstr, "}", NULL); + lenstr = (char *) ap_palloc(r->pool, 22); + ap_snprintf(lenstr, 22, "%ld", len); + *((const char **) ap_push_array(arr)) = " {length "; + *((const char **) ap_push_array(arr)) = lenstr; + *((const char **) ap_push_array(arr)) = "}"; } - rec = ap_pstrcat(r->pool, rec, "}", NULL); - - if (neg->send_alternates) { - - /* We only list the variant in the Alternates header if it - * has no content encoding, as there is no standard way of - * saying in the Alternates header that a variant is - * available in a content-encoded form (only). - */ - if (!variant->content_encoding) - ap_table_mergen(hdrs, "Alternates", rec); - } - - first_variant = 0; + *((const char **) ap_push_array(arr)) = "}"; + *((const char **) ap_push_array(arr)) = ", "; /* trimmed below */ } + if (neg->send_alternates && neg->avail_vars->nelts) { + arr->nelts--; /* remove last comma */ + ap_table_mergen(hdrs, "Alternates", + ap_array_pstrcat(r->pool, arr, '\0')); + } + /* Theoretically the negotiation result _always_ has a dependence on * the contents of the Accept header because we do 'mxb=' * processing in set_accept_quality(). However, variations in mxb @@ -2259,14 +2242,20 @@ * choice response or 406 status body. */ -/* XXX: this is disgusting, this has O(n^2) behaviour! -djg */ - static char *make_variant_list(request_rec *r, negotiation_state *neg) { + array_header *arr; int i; - char *t; + int max_vlist_array = (neg->avail_vars->nelts * 15) + 2; + + /* In order to avoid O(n^2) memory copies in building the list, + * we preallocate a table with the maximum substrings possible, + * fill it with the variant list, and then concatenate the entire array. + */ + arr = ap_make_array(r->pool, max_vlist_array, sizeof(char *)); + + *((const char **) ap_push_array(arr)) = "Available variants:\n<ul>\n"; - t = ap_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 : ""; @@ -2274,32 +2263,39 @@ 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'). + * the tags human readable (eg replace 'language en' with 'English'). + * Note that if you change the number of substrings pushed, you also + * need to change the calculation of max_vlist_array above. */ - t = ap_pstrcat(r->pool, t, "<li><a href=\"", filename, "\">", - filename, "</a> ", description, NULL); + *((const char **) ap_push_array(arr)) = "<li><a href=\""; + *((const char **) ap_push_array(arr)) = filename; + *((const char **) ap_push_array(arr)) = "\">"; + *((const char **) ap_push_array(arr)) = filename; + *((const char **) ap_push_array(arr)) = "</a> "; + *((const char **) ap_push_array(arr)) = description; + if (variant->mime_type && *variant->mime_type) { - t = ap_pstrcat(r->pool, t, ", type ", variant->mime_type, NULL); + *((const char **) ap_push_array(arr)) = ", type "; + *((const char **) ap_push_array(arr)) = variant->mime_type; } if (languages && languages->nelts) { - t = ap_pstrcat(r->pool, t, ", language ", - merge_string_array(r->pool, languages, ", "), - NULL); + *((const char **) ap_push_array(arr)) = ", language "; + *((const char **) ap_push_array(arr)) = ap_array_pstrcat(r->pool, + languages, ','); } if (variant->content_charset && *variant->content_charset) { - t = ap_pstrcat(r->pool, t, ", charset ", variant->content_charset, - NULL); + *((const char **) ap_push_array(arr)) = ", charset "; + *((const char **) ap_push_array(arr)) = variant->content_charset; } if (variant->content_encoding) { - t = ap_pstrcat(r->pool, t, ", encoding ", - variant->content_encoding, NULL); + *((const char **) ap_push_array(arr)) = ", encoding "; + *((const char **) ap_push_array(arr)) = variant->content_encoding; } - t = ap_pstrcat(r->pool, t, "\n", NULL); + *((const char **) ap_push_array(arr)) = "\n"; } - t = ap_pstrcat(r->pool, t, "</ul>\n", NULL); + *((const char **) ap_push_array(arr)) = "</ul>\n"; - return t; + return ap_array_pstrcat(r->pool, arr, '\0'); } static void store_variant_list(request_rec *r, negotiation_state *neg) 1.9 +1 -0 apache-1.3/src/support/httpd.exp Index: httpd.exp =================================================================== RCS file: /home/cvs/apache-1.3/src/support/httpd.exp,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- httpd.exp 1998/12/29 09:46:19 1.8 +++ httpd.exp 1999/01/03 12:04:40 1.9 @@ -13,6 +13,7 @@ ap_allow_overrides ap_append_arrays ap_array_cat +ap_array_pstrcat ap_auth_name ap_auth_type ap_basic_http_header