On Thu, Jun 18, 2020 at 6:03 PM Stefan Eissing <stefan.eiss...@greenbytes.de> wrote: > > ap_parse_request_line() for example, checks the initial HTTP/1.1 request line > *and* > the method names, uri, header_only and other request_rec fields. > > We can either copy the latter into mod_http2 and maintain it in two places or > have a core function for it to be invoked by mod_http and mod_http2. That > seems > to be the design decision to make.
How about this attached patch? ap_parse_request_line() will only parse the request line (extract the fields, which mod_h2 doesn't need), and ap_check_request_line() will do the validation. Since ap_parse_request_line() becomes local to "protocol.c" only, it can be unexported (not in this patch to avoid a MAJOR bump and thus ease backport). Thoughts?
Index: include/ap_mmn.h =================================================================== --- include/ap_mmn.h (revision 1878986) +++ include/ap_mmn.h (working copy) @@ -632,6 +632,7 @@ * 20200420.1 (2.5.1-dev) Add ap_filter_adopt_brigade() * 20200420.2 (2.5.1-dev) Add ap_proxy_worker_can_upgrade() * 20200420.3 (2.5.1-dev) Add ap_parse_strict_length() + * 20200420.4 (2.5.1-dev) Add ap_check_request_line() */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ @@ -639,7 +640,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20200420 #endif -#define MODULE_MAGIC_NUMBER_MINOR 3 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 4 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a Index: include/http_protocol.h =================================================================== --- include/http_protocol.h (revision 1878986) +++ include/http_protocol.h (working copy) @@ -68,13 +68,21 @@ AP_DECLARE(request_rec *) ap_create_request(conn_r request_rec *ap_read_request(conn_rec *c); /** - * Parse and validate the request line. + * Parse the request line. * @param r The current request * @return 1 on success, 0 on failure + * TODO: unexport, used in trunk's protocol.c only */ AP_DECLARE(int) ap_parse_request_line(request_rec *r); /** + * Validate the request line. + * @param r The current request + * @return 1 on success, 0 on failure + */ +AP_DECLARE(int) ap_check_request_line(request_rec *r); + +/** * Validate the request header and select vhost. * @param r The current request * @return 1 on success, 0 on failure Index: modules/http2/h2_request.c =================================================================== --- modules/http2/h2_request.c (revision 1878986) +++ modules/http2/h2_request.c (working copy) @@ -278,8 +278,14 @@ request_rec *h2_request_create_rec(const h2_reques /* Time to populate r with the data we have. */ r->request_time = req->request_time; - r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0", - req->method, req->path ? req->path : ""); + r->proto_num = HTTP_VERSION(2, 0); + r->protocol = apr_pstrdup(r->pool, "HTTP/2.0"); + r->method = apr_pstrdup(r->pool, req->method ? req->method : ""); + r->unparsed_uri = apr_pstrdup(r->pool, req->path ? req->path : ""); + r->the_request = apr_pstrcat(r->pool, + r->method, " ", + r->unparsed_uri, " ", + r->protocol, NULL); r->headers_in = apr_table_clone(r->pool, req->headers); /* Start with r->hostname = NULL, ap_check_request_header() will get it @@ -288,7 +294,7 @@ request_rec *h2_request_create_rec(const h2_reques r->hostname = NULL; /* Validate HTTP/1 request and select vhost. */ - if (!ap_parse_request_line(r) || !ap_check_request_header(r)) { + if (!ap_check_request_line(r) || !ap_check_request_header(r)) { /* we may have switched to another server still */ r->per_dir_config = r->server->lookup_defaults; access_status = r->status; Index: server/protocol.c =================================================================== --- server/protocol.c (revision 1878986) +++ server/protocol.c (working copy) @@ -610,7 +610,16 @@ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, { int status = HTTP_OK; - r->unparsed_uri = apr_pstrdup(r->pool, uri); + if (uri) { + r->unparsed_uri = apr_pstrdup(r->pool, uri); + } + else { + uri = r->unparsed_uri; + if (!uri) { + r->status = HTTP_INTERNAL_SERVER_ERROR; + return; + } + } /* http://issues.apache.org/bugzilla/show_bug.cgi?id=31875 * http://issues.apache.org/bugzilla/show_bug.cgi?id=28450 @@ -751,7 +760,7 @@ AP_DECLARE(int) ap_parse_request_line(request_rec rrl_badmethod09, rrl_reject09 } deferred_error = rrl_none; apr_size_t len = 0; - char *uri, *ll; + char *ll; r->method = r->the_request; @@ -782,7 +791,7 @@ AP_DECLARE(int) ap_parse_request_line(request_rec if (!ll) { if (deferred_error == rrl_none) deferred_error = rrl_missinguri; - r->protocol = uri = ""; + r->protocol = r->unparsed_uri = ""; goto rrl_done; } else if (strict && ll[0] && apr_isspace(ll[1]) @@ -793,19 +802,20 @@ AP_DECLARE(int) ap_parse_request_line(request_rec /* Advance uri pointer over leading whitespace, NUL terminate the method * If non-SP whitespace is encountered, mark as specific error */ - for (uri = ll; apr_isspace(*uri); ++uri) - if (*uri != ' ' && deferred_error == rrl_none) + r->unparsed_uri = ll; + for (; apr_isspace(*r->unparsed_uri); ++r->unparsed_uri) + if (*r->unparsed_uri != ' ' && deferred_error == rrl_none) deferred_error = rrl_badwhitespace; *ll = '\0'; - - if (!*uri && deferred_error == rrl_none) + + if (!*r->unparsed_uri && deferred_error == rrl_none) deferred_error = rrl_missinguri; /* Scan the URI up to the next whitespace, ensure it contains no raw * control characters, otherwise mark in error */ - ll = (char*) ap_scan_vchar_obstext(uri); - if (ll == uri || (*ll && !apr_isspace(*ll))) { + ll = (char*) ap_scan_vchar_obstext(r->unparsed_uri); + if (ll == r->unparsed_uri || (*ll && !apr_isspace(*ll))) { deferred_error = rrl_baduri; ll = strpbrk(ll, "\t\n\v\f\r "); } @@ -858,7 +868,8 @@ rrl_done: /* For internal integrity and palloc efficiency, reconstruct the_request * in one palloc, using only single SP characters, per spec. */ - r->the_request = apr_pstrcat(r->pool, r->method, *uri ? " " : NULL, uri, + r->the_request = apr_pstrcat(r->pool, r->method, + *r->unparsed_uri ? " " : NULL, r->unparsed_uri, *r->protocol ? " " : NULL, r->protocol, NULL); if (len == 8 @@ -897,15 +908,6 @@ rrl_done: r->proto_num = HTTP_VERSION(0, 9); } - /* Determine the method_number and parse the uri prior to invoking error - * handling, such that these fields are available for substitution - */ - r->method_number = ap_method_number_of(r->method); - if (r->method_number == M_GET && r->method[0] == 'H') - r->header_only = 1; - - ap_parse_uri(r, uri); - /* With the request understood, we can consider HTTP/0.9 specific errors */ if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) { if (conf->http09_enable == AP_HTTP09_DISABLE) @@ -954,10 +956,41 @@ rrl_done: "HTTP Request Line; Unrecognized protocol '%.*s' " "(perhaps whitespace was injected?)", field_name_len(r->protocol), r->protocol); + + if (r->proto_num == HTTP_VERSION(0, 9)) { + /* Send all parsing and protocol error response with 1.x behavior, + * and reserve 505 errors for actual HTTP protocols presented. + * As called out in RFC7230 3.5, any errors parsing the protocol + * from the request line are nearly always misencoded HTTP/1.x + * requests. Only a valid 0.9 request with no parsing errors + * at all may be treated as a simple request, if allowed. + */ + r->assbackwards = 0; + r->connection->keepalive = AP_CONN_CLOSE; + r->proto_num = HTTP_VERSION(1, 0); + r->protocol = "HTTP/1.0"; + } r->status = HTTP_BAD_REQUEST; - goto rrl_failed; + return 0; } + return 1; +} + +AP_DECLARE(int) ap_check_request_line(request_rec *r) +{ + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + + /* Determine the method_number and parse the uri prior to invoking error + * handling, such that these fields are available for substitution + */ + r->method_number = ap_method_number_of(r->method); + if (r->method_number == M_GET && r->method[0] == 'H') + r->header_only = 1; + + ap_parse_uri(r, NULL); + if (conf->http_methods == AP_HTTP_METHODS_REGISTERED && r->method_number == M_INVALID) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) @@ -1453,7 +1486,9 @@ request_rec *ap_read_request(conn_rec *conn) ap_run_pre_read_request(r, conn); /* Get the request... */ - if (!read_request_line(r, tmp_bb) || !ap_parse_request_line(r)) { + if (!read_request_line(r, tmp_bb) + || !ap_parse_request_line(r) + || !ap_check_request_line(r)) { apr_brigade_cleanup(tmp_bb); switch (r->status) { case HTTP_REQUEST_URI_TOO_LARGE: