fielding 99/02/08 07:12:22
Modified: src ApacheCore.def CHANGES src/include ap_mmn.h httpd.h src/main util.c src/modules/standard mod_negotiation.c src/support httpd.exp Added: src/test test_parser.c Log: Added ap_find_list_item() and ap_get_list_item() to util.c for parsing an HTTP header field value to extract the next list item, taking into account the possible presence of nested comments, quoted-pairs, and quoted-strings. ap_get_list_item() also removes insignificant whitespace and lowercases non-quoted tokens. Work around a bug in Lynx regarding its sending "Negotiate: trans" even though it doesn't understand TCN. PR: 2065 Revision Changes Path 1.10 +2 -0 apache-1.3/src/ApacheCore.def Index: ApacheCore.def =================================================================== RCS file: /home/cvs/apache-1.3/src/ApacheCore.def,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- ApacheCore.def 1999/02/03 16:22:27 1.9 +++ ApacheCore.def 1999/02/08 15:12:15 1.10 @@ -327,4 +327,6 @@ ap_find_opaque_token @320 ap_MD5Encode @321 ap_validate_password @322 + ap_find_list_item @323 + ap_get_list_item @324 1.1239 +9 -0 apache-1.3/src/CHANGES Index: CHANGES =================================================================== RCS file: /home/cvs/apache-1.3/src/CHANGES,v retrieving revision 1.1238 retrieving revision 1.1239 diff -u -r1.1238 -r1.1239 --- CHANGES 1999/02/07 20:53:44 1.1238 +++ CHANGES 1999/02/08 15:12:16 1.1239 @@ -1,5 +1,14 @@ Changes with Apache 1.3.5 + *) Work around a bug in Lynx regarding its sending "Negotiate: trans" + even though it doesn't understand TCN. [Koen Holtman, Roy Fielding] + + *) Added ap_find_list_item() and ap_get_list_item() to util.c for parsing + an HTTP header field value to extract the next list item, taking into + account the possible presence of nested comments, quoted-pairs, + and quoted-strings. ap_get_list_item() also removes insignificant + whitespace and lowercases non-quoted tokens. [Roy Fielding] PR#2065 + *) proxy: The various calls to ap_proxyerror() can return HTTP/1.1 status code different from 500. This allows the proxy to, e.g., return "403 Forbidden" for ProxyBlock'ed URL's. [Martin Kraemer] 1.26 +4 -3 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.25 retrieving revision 1.26 diff -u -r1.25 -r1.26 --- ap_mmn.h 1999/02/03 16:22:31 1.25 +++ ap_mmn.h 1999/02/08 15:12:18 1.26 @@ -203,13 +203,14 @@ * scan_script_header -> ap_scan_script_header_err * - reordered entries in request_rec that were waiting * for a non-binary-compatible release. - * 19990108-1 - add ap_find_opaque_token() for things like ETags + * 19990108.1 - add ap_find_opaque_token() for things like ETags * (1.3.5-dev) which can contain opaque quoted strings, and * ap_MD5Encode() for MD5 password handling. - * 19990108-2 - add ap_validate_password() and change ap_MD5Encode() + * 19990108.2 - add ap_validate_password() and change ap_MD5Encode() * (1.3.5-dev) to use a stronger algorithm (which is incompatible * with the one introduced [but not released] with * 19990108-1). + * 19990108.3 - add ap_find_list_item() and ap_get_list_item() */ #define MODULE_MAGIC_COOKIE 0x41503133UL /* "AP13" */ @@ -217,7 +218,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 19990108 #endif -#define MODULE_MAGIC_NUMBER_MINOR 2 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */ #define MODULE_MAGIC_NUMBER MODULE_MAGIC_NUMBER_MAJOR /* backward compat */ /* Useful for testing for features. */ 1.267 +2 -0 apache-1.3/src/include/httpd.h Index: httpd.h =================================================================== RCS file: /home/cvs/apache-1.3/src/include/httpd.h,v retrieving revision 1.266 retrieving revision 1.267 diff -u -r1.266 -r1.267 --- httpd.h 1999/02/05 09:14:56 1.266 +++ httpd.h 1999/02/08 15:12:18 1.267 @@ -932,6 +932,8 @@ API_EXPORT(char *) ap_getword_conf(pool *p, const char **line); API_EXPORT(char *) ap_getword_conf_nc(pool *p, char **line); +API_EXPORT(const char *) ap_find_list_item(const char **field, int *len); +API_EXPORT(char *) ap_get_list_item(pool *p, const char **field); API_EXPORT(char *) ap_get_token(pool *p, const char **accept_line, int accept_white); API_EXPORT(int) ap_find_token(pool *p, const char *line, const char *tok); API_EXPORT(int) ap_find_opaque_token(pool *p, const char *line, 1.148 +152 -0 apache-1.3/src/main/util.c Index: util.c =================================================================== RCS file: /home/cvs/apache-1.3/src/main/util.c,v retrieving revision 1.147 retrieving revision 1.148 diff -u -r1.147 -r1.148 --- util.c 1999/01/27 12:16:02 1.147 +++ util.c 1999/02/08 15:12:19 1.148 @@ -978,6 +978,158 @@ } } +/* Find an HTTP header field list item, as separated by a comma. + * The return value is a pointer to the beginning of the non-empty list item + * within the original string (or NULL if there is none) and the address + * of field is shifted to the next non-comma, non-whitespace character. + * len is the length of the item excluding any beginning whitespace. + */ +API_EXPORT(const char *) ap_find_list_item(const char **field, int *len) +{ + const char *ptr = *field; + const char *token; + int in_qpair, in_qstr, in_com; + + /* Find first non-comma, non-whitespace byte */ + + while (*ptr == ',' || ap_isspace(*ptr)) + ++ptr; + + token = ptr; + + /* Find the end of this item, skipping over dead bits */ + + for (in_qpair = in_qstr = in_com = 0; + *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); + ++ptr) { + + if (in_qpair) { + in_qpair = 0; + } + else { + switch (*ptr) { + case '\\': in_qpair = 1; /* quoted-pair */ + break; + case '"' : if (!in_com) /* quoted string delim */ + in_qstr = !in_qstr; + break; + case '(' : if (!in_qstr) /* comment (may nest) */ + ++in_com; + break; + case ')' : if (in_com) /* end comment */ + --in_com; + break; + default : break; + } + } + } + + if ((*len = (ptr - token)) == 0) { + *field = ptr; + return NULL; + } + + /* Advance field pointer to the next non-comma, non-white byte */ + + while (*ptr == ',' || ap_isspace(*ptr)) + ++ptr; + + *field = ptr; + return token; +} + +/* Retrieve an HTTP header field list item, as separated by a comma, + * while stripping insignificant whitespace/comments and lowercasing anything + * not in a quoted string. The return value is a new string containing + * the converted list item (empty if it was all comments or NULL if none) + * and the address of field is shifted to the next non-comma, non-whitespace. + */ +API_EXPORT(char *) ap_get_list_item(pool *p, const char **field) +{ + const char *tok_start, *ptr; + char *token, *pos; + int addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0, tok_len = 0; + + /* Find the beginning and maximum length of the list item so that + * we can allocate a buffer for the new string and reset the field. + */ + if ((tok_start = ap_find_list_item(field, &tok_len)) == NULL) { + return NULL; + } + token = ap_palloc(p, tok_len + 1); + + /* Scan the token again, but this time copy only the good bytes. + * We skip extra whitespace and any whitespace around a '=' or ';', + * strip comments, and lowercase normal characters not within a + * quoted-string or quoted-pair. The result may be an empty string. + */ + for (ptr = tok_start, pos = token; + *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); + ++ptr) { + + if (in_qpair) { + in_qpair = 0; + if (!in_com) + *pos++ = *ptr; + } + else { + switch (*ptr) { + case '\\': in_qpair = 1; + if (in_com) + break; + if (addspace == 1) + *pos++ = ' '; + *pos++ = *ptr; + addspace = 0; + break; + case '"' : if (in_com) + break; + in_qstr = !in_qstr; + if (addspace == 1) + *pos++ = ' '; + *pos++ = *ptr; + addspace = 0; + break; + case '(' : if (in_qstr) + *pos++ = *ptr; + else + ++in_com; + break; + case ')' : if (in_com) + --in_com; + else + *pos++ = *ptr; + break; + case ' ' : + case '\t': if (in_com || addspace) + break; + if (in_qstr) + *pos++ = *ptr; + else + addspace = 1; + break; + case '=' : + case ';' : if (in_com) + break; + if (!in_qstr) + addspace = -1; + *pos++ = *ptr; + break; + default : if (in_com) + break; + if (addspace == 1) + *pos++ = ' '; + *pos++ = in_qstr ? *ptr : ap_tolower(*ptr); + addspace = 0; + break; + } + } + } + *pos = '\0'; + + return token; +} + /* Retrieve a token, spacing over it and returning a pointer to * the first non-white byte afterwards. Note that these tokens * are delimited by semis and commas; and can also be delimited 1.97 +64 -64 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.96 retrieving revision 1.97 diff -u -r1.96 -r1.97 --- mod_negotiation.c 1999/02/06 14:36:27 1.96 +++ mod_negotiation.c 1999/02/08 15:12:20 1.97 @@ -511,78 +511,78 @@ static void parse_negotiate_header(request_rec *r, negotiation_state *neg) { const char *negotiate = ap_table_get(r->headers_in, "Negotiate"); + char *tok; - if (negotiate) { - /* Negotiate: header tells us UA does transparent negotiation */ - - /* sending Alternates on non-transparent resources is allowed, - * and may even be useful, but we don't for now, also - * because it could clash with an Alternates header set by - * a sub- or super- request on a transparent resource. + /* First, default to no TCN, no Alternates, and the original Apache + * negotiation algorithm with fiddles for broken browser configs. + * + * To save network bandwidth, we do not configure to send an + * Alternates header to the user agent by default. User + * agents that want an Alternates header for agent-driven + * negotiation will have to request it by sending an + * appropriate Negotiate header. + */ + neg->ua_supports_trans = 0; + neg->send_alternates = 0; + neg->may_choose = 1; + neg->use_rvsa = 0; + neg->dont_fiddle_headers = 0; + + if (!negotiate) + return; + + if (strcmp(negotiate, "trans") == 0) { + /* Lynx 2.7 and 2.8 send 'negotiate: trans' even though they + * do not support transparent content negotiation, so for Lynx we + * ignore the negotiate header when its contents are exactly "trans". + * If future versions of Lynx ever need to say 'negotiate: trans', + * they can send the equivalent 'negotiate: trans, trans' instead + * to avoid triggering the workaround below. */ + const char *ua = ap_table_get(r->headers_in, "User-Agent"); + + if (ua && (strncmp(ua, "Lynx", 4) == 0)) + return; + } - while (*negotiate) { - char *tok = ap_get_token(neg->pool, &negotiate, 1); - char *cp; + neg->may_choose = 0; /* An empty Negotiate would require 300 response */ - for (cp = tok; (*cp && !ap_isspace(*cp) && *cp != '='); ++cp) { - *cp = ap_tolower(*cp); + while ((tok = ap_get_list_item(neg->pool, &negotiate)) != NULL) { + + if (strcmp(tok, "trans") == 0 || + strcmp(tok, "vlist") == 0 || + strcmp(tok, "guess-small") == 0 || + ap_isdigit(tok[0]) || + strcmp(tok, "*") == 0) { + + /* The user agent supports transparent negotiation */ + neg->ua_supports_trans = 1; + + /* Send-alternates could be configurable, but note + * that it must be 1 if we have 'vlist' in the + * negotiate header. + */ + neg->send_alternates = 1; + + if (strcmp(tok, "1.0") == 0) { + /* we may use the RVSA/1.0 algorithm, configure for it */ + neg->may_choose = 1; + neg->use_rvsa = 1; + neg->dont_fiddle_headers = 1; } - *cp = 0; - - if (strcmp(tok, "trans") == 0 || - strcmp(tok, "vlist") == 0 || - strcmp(tok, "guess-small") == 0 || - ap_isdigit(tok[0]) || - strcmp(tok, "*") == 0) { - - /* The user agent supports transparent negotiation */ - neg->ua_supports_trans = 1; - - /* Send-alternates could be configurable, but note - * that it must be 1 if we have 'vlist' in the - * negotiate header. + else if (tok[0] == '*') { + /* we may use any variant selection algorithm, configure + * to use the Apache algorithm */ - 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; - } - else if (strcmp(tok, "*") == 0) { - /* we may use any variant selection algorithm, configure - * to use the Apache algorithm - */ - neg->may_choose = 1; - - /* We disable header fiddles on the assumption that a - * client sending Negotiate knows how to send correct - * headers which don't need fiddling. - */ - neg->dont_fiddle_headers = 1; - } + neg->may_choose = 1; + + /* We disable header fiddles on the assumption that a + * client sending Negotiate knows how to send correct + * headers which don't need fiddling. + */ + neg->dont_fiddle_headers = 1; } - - if (*negotiate) - negotiate++; /* skip over , */ } - } - - if (!neg->ua_supports_trans) { - /* User agent does not support transparent negotiation, - * configure to do server-driven negotiation with the Apache - * algorithm. - */ - neg->may_choose = 1; - - /* To save network bandwidth, we do not configure to send an - * Alternates header to the user agent in this case. User - * agents which want an Alternates header for agent-driven - * negotiation will have to request it by sending an - * appropriate Negotiate header. - */ } #ifdef NEG_DEBUG 1.14 +2 -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.13 retrieving revision 1.14 diff -u -r1.13 -r1.14 --- httpd.exp 1999/02/03 16:22:34 1.13 +++ httpd.exp 1999/02/08 15:12:21 1.14 @@ -106,6 +106,7 @@ ap_find_command_in_modules ap_find_last_token ap_find_linked_module +ap_find_list_item ap_find_module_name ap_find_opaque_token ap_find_path_info @@ -117,6 +118,7 @@ ap_get_basic_auth_pw ap_get_client_block ap_get_gmtoff +ap_get_list_item ap_get_local_host ap_get_remote_host ap_get_remote_logname 1.1 apache-1.3/src/test/test_parser.c Index: test_parser.c =================================================================== /* This program tests the ap_get_list_item routine in ../main/util_date.c. * * The defines in this sample compile line are specific to Roy's system. * They should match whatever was used to compile Apache first. * gcc -g -O2 -I../os/unix -I../include -o test_parser \ -DSOLARIS2=250 -Wall -DALLOC_DEBUG -DPOOL_DEBUG \ ../main/alloc.o ../main/buff.o ../main/util.o \ ../main/http_log.o ../ap/libap.a \ -lsocket -lnsl test_parser.c * * Roy Fielding, 1999 */ #include <stdio.h> #include <stdlib.h> #include "httpd.h" #include "alloc.h" /* * Dummy a bunch of stuff just to get a compile */ uid_t ap_user_id; gid_t ap_group_id; void *ap_dummy_mutex = &ap_dummy_mutex; char *ap_server_argv0; API_EXPORT(void) ap_block_alarms(void) { ; } API_EXPORT(void) ap_unblock_alarms(void) { ; } API_EXPORT(void) ap_log_error(const char *file, int line, int level, const request_rec *r, const char *fmt, ...) { ; } int main (void) { ap_pool *p; const char *field; char *newstr; char instr[512]; p = ap_init_alloc(); while (gets(instr)) { printf(" [%s] ==\n", instr); field = instr; while ((newstr = ap_get_list_item(p, &field)) != NULL) printf(" <%s> ..\n", newstr); } exit(0); }