diff options
-rw-r--r-- | changes-entries/core_request_buckets.txt | 10 | ||||
-rw-r--r-- | include/ap_mmn.h | 2 | ||||
-rw-r--r-- | include/http_protocol.h | 24 | ||||
-rw-r--r-- | include/mod_core.h | 5 | ||||
-rw-r--r-- | modules/http/http_core.c | 63 | ||||
-rw-r--r-- | modules/http/http_filters.c | 193 | ||||
-rw-r--r-- | modules/http/http_protocol.c | 202 | ||||
-rw-r--r-- | server/protocol.c | 675 |
8 files changed, 743 insertions, 431 deletions
diff --git a/changes-entries/core_request_buckets.txt b/changes-entries/core_request_buckets.txt new file mode 100644 index 0000000000..7e6bd920c4 --- /dev/null +++ b/changes-entries/core_request_buckets.txt @@ -0,0 +1,10 @@ + *) core/mod_http: use REQUEST meta buckets and a new HTTP/1.x specific + input filter to separate the handling for HTTP requests from the + handling of HTTP/1.x request parsing and checks. + A new HTTP1_REQUEST_IN filter installs itself on http/1.1 connections + before a request is being read. It generates either a REQUEST meta + bucket on success or an ERROR bucket with the proposed response status. + The core connection processing, relying on ap_read_request(), now expects + a REQUEST or ERROR bucket from the input filters and is agnostic to + specific HTTP versions and how they bring requests into the server. + [Stefan Eissing] diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 25fa16e963..7b6753cbe4 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -708,7 +708,7 @@ * 20211221.6 (2.5.1-dev) Add new meta buckets request/response/headers * Add field `body_indeterminate` in request_rec * Add new http/1.x formatting helpers - * Add ap_assign_request() + * Add ap_assign_request_line() * 20211221.7 (2.5.1-dev) Add ap_h1_append_header() */ diff --git a/include/http_protocol.h b/include/http_protocol.h index a2113d99d3..4bfe4a8559 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -65,19 +65,33 @@ AP_DECLARE(request_rec *) ap_create_request(conn_rec *c); * @param c The current connection * @return The new request_rec */ -request_rec *ap_read_request(conn_rec *c); +AP_DECLARE(request_rec *) ap_read_request(conn_rec *c); /** - * Assign the method, uri and protocol to the request. + * Assign the method, uri and protocol (in HTTP/1.x the + * items from the first line) to the request. * @param r The current request * @param method the HTTP method * @param uri the request uri * @param protocol the request protocol * @return 1 on success, 0 on failure */ -AP_DECLARE(int) ap_assign_request(request_rec *r, - const char *method, const char *uri, - const char *protocol); +AP_DECLARE(int) ap_assign_request_line(request_rec *r, + const char *method, const char *uri, + const char *protocol); + +/** + * Parse a HTTP/1.x request line, validate and return the components + * @param r The current request + * @param line the line to parse + * @param pmethod the parsed method on success + * @param puri the parsed uri on success + * @param pprotocol the parsed protocol on success + * @return 1 on success, 0 on failure + */ +AP_DECLARE(int) ap_h1_tokenize_request_line( + request_rec *r, const char *line, + const char **pmethod, const char **puri, const char **pprotocol); /** * Parse and validate the request line. diff --git a/include/mod_core.h b/include/mod_core.h index 0c795ceeee..f9cc0611f4 100644 --- a/include/mod_core.h +++ b/include/mod_core.h @@ -41,6 +41,7 @@ extern "C" { /* Handles for core filters */ AP_DECLARE_DATA extern ap_filter_rec_t *ap_http_input_filter_handle; +AP_DECLARE_DATA extern ap_filter_rec_t *ap_h1_request_in_filter_handle; AP_DECLARE_DATA extern ap_filter_rec_t *ap_h1_body_in_filter_handle; AP_DECLARE_DATA extern ap_filter_rec_t *ap_http_header_filter_handle; AP_DECLARE_DATA extern ap_filter_rec_t *ap_chunk_filter_handle; @@ -54,6 +55,10 @@ apr_status_t ap_http_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes); +apr_status_t ap_h1_request_in_filter(ap_filter_t *f, apr_bucket_brigade *bb, + ap_input_mode_t mode, apr_read_type_e block, + apr_off_t readbytes); + apr_status_t ap_h1_body_in_filter(ap_filter_t *f, apr_bucket_brigade *b, ap_input_mode_t mode, apr_read_type_e block, apr_off_t readbytes); diff --git a/modules/http/http_core.c b/modules/http/http_core.c index 92ab08911b..3b94270554 100644 --- a/modules/http/http_core.c +++ b/modules/http/http_core.c @@ -24,6 +24,7 @@ #include "http_config.h" #include "http_connection.h" #include "http_core.h" +#include "http_log.h" #include "http_protocol.h" /* For index_of_response(). Grump. */ #include "http_request.h" @@ -36,6 +37,7 @@ /* Handles for core filters */ AP_DECLARE_DATA ap_filter_rec_t *ap_http_input_filter_handle; +AP_DECLARE_DATA ap_filter_rec_t *ap_h1_request_in_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_h1_body_in_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_http_header_filter_handle; AP_DECLARE_DATA ap_filter_rec_t *ap_h1_response_out_filter_handle; @@ -269,15 +271,66 @@ static int http_create_request(request_rec *r) return OK; } -static void http_pre_read_request(request_rec *r, conn_rec *c) +static void h1_pre_read_request(request_rec *r, conn_rec *c) { if (!r->main && !r->prev && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) { + if (r->proxyreq == PROXYREQ_NONE) { + ap_add_input_filter_handle(ap_h1_request_in_filter_handle, + NULL, r, r->connection); + } ap_add_output_filter_handle(ap_h1_response_out_filter_handle, NULL, r, r->connection); } } +static int h1_post_read_request(request_rec *r) +{ + const char *tenc; + + if (!r->main && !r->prev && r->proto_num <= HTTP_VERSION(1,1)) { + if (r->proto_num >= HTTP_VERSION(1,0)) { + tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); + if (tenc) { + r->body_indeterminate = 1; + + /* https://tools.ietf.org/html/rfc7230 + * Section 3.3.3.3: "If a Transfer-Encoding header field is + * present in a request and the chunked transfer coding is not + * the final encoding ...; the server MUST respond with the 400 + * (Bad Request) status code and then close the connection". + */ + if (!ap_is_chunked(r->pool, tenc)) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539) + "client sent unknown Transfer-Encoding " + "(%s): %s", tenc, r->uri); + return HTTP_BAD_REQUEST; + } + + /* https://tools.ietf.org/html/rfc7230 + * Section 3.3.3.3: "If a message is received with both a + * Transfer-Encoding and a Content-Length header field, the + * Transfer-Encoding overrides the Content-Length. ... A sender + * MUST remove the received Content-Length field". + */ + if (apr_table_get(r->headers_in, "Content-Length")) { + apr_table_unset(r->headers_in, "Content-Length"); + + /* Don't reuse this connection anyway to avoid confusion with + * intermediaries and request/reponse spltting. + */ + r->connection->keepalive = AP_CONN_CLOSE; + } + } + } + /* HTTP1_BODY_IN takes care of chunked encoding and content-length. + */ + ap_add_input_filter_handle(ap_h1_body_in_filter_handle, + NULL, r, r->connection); + } + return OK; +} + static int http_send_options(request_rec *r) { if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') && @@ -308,11 +361,17 @@ static void register_hooks(apr_pool_t *p) ap_hook_map_to_storage(http_send_options,NULL,NULL,APR_HOOK_MIDDLE); ap_hook_http_scheme(http_scheme,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_default_port(http_port,NULL,NULL,APR_HOOK_REALLY_LAST); + ap_hook_create_request(http_create_request, NULL, NULL, APR_HOOK_REALLY_LAST); - ap_hook_pre_read_request(http_pre_read_request, NULL, NULL, APR_HOOK_REALLY_LAST); + ap_hook_pre_read_request(h1_pre_read_request, NULL, NULL, APR_HOOK_REALLY_LAST); + ap_hook_post_read_request(h1_post_read_request, NULL, NULL, APR_HOOK_REALLY_FIRST); + ap_http_input_filter_handle = ap_register_input_filter("HTTP_IN", ap_http_filter, NULL, AP_FTYPE_PROTOCOL); + ap_h1_request_in_filter_handle = + ap_register_input_filter("HTTP1_REQUEST_IN", ap_h1_request_in_filter, + NULL, AP_FTYPE_PROTOCOL); ap_h1_body_in_filter_handle = ap_register_input_filter("HTTP1_BODY_IN", ap_h1_body_in_filter, NULL, AP_FTYPE_TRANSCODE); diff --git a/modules/http/http_filters.c b/modules/http/http_filters.c index 35fad461f6..42a89cbea4 100644 --- a/modules/http/http_filters.c +++ b/modules/http/http_filters.c @@ -1856,12 +1856,12 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f, ap_bucket_response *resp = e->data; ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, - "ap_http1_response_out_filter seeing response bucket status=%d", + "ap_h1_response_out_filter seeing response bucket status=%d", resp->status); if (strict && resp->status < 100) { /* error, not a valid http status */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10386) - "ap_http1_response_out_filter seeing headers " + "ap_h1_response_out_filter seeing headers " "status=%d in strict mode", resp->status); rv = AP_FILTER_ERROR; @@ -1871,7 +1871,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f, /* already sent the final response for the request. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10387) - "ap_http1_response_out_filter seeing headers " + "ap_h1_response_out_filter seeing headers " "status=%d after final response already sent", resp->status); rv = AP_FILTER_ERROR; @@ -1926,7 +1926,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f, rv = ap_pass_brigade(f->next, b); apr_brigade_cleanup(b); ap_log_rerror(APLOG_MARK, APLOG_TRACE2, rv, r, - "ap_http1_response_out_filter passed response" + "ap_h1_response_out_filter passed response" ", add CHUNK filter"); if (APR_SUCCESS != rv) { apr_brigade_cleanup(ctx->tmpbb); @@ -1950,7 +1950,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_h1_response_out_filter(ap_filter_t *f, /* data buckets before seeing the final response are in error. */ ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10390) - "ap_http1_response_out_filter seeing data before headers, %ld bytes ", + "ap_h1_response_out_filter seeing data before headers, %ld bytes ", (long)e->length); rv = AP_FILTER_ERROR; goto cleanup; @@ -2080,3 +2080,186 @@ static apr_bucket *create_trailers_bucket(request_rec *r, apr_bucket_alloc_t *bu } return NULL; } + +typedef struct h1_request_ctx { + enum + { + REQ_LINE, /* reading 1st request line */ + REQ_HEADERS, /* reading header lines */ + REQ_BODY, /* reading body follows, terminal */ + REQ_ERROR, /* failed, terminal */ + } state; + + request_rec *r; + char *request_line; + const char *method; + const char *uri; + const char *protocol; +} h1_request_ctx; + +static apr_status_t read_request_line(h1_request_ctx *ctx, apr_bucket_brigade *bb) +{ + apr_size_t len; + int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES; + core_server_config *conf = ap_get_core_module_config(ctx->r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + apr_status_t rv; + + /* Read past empty lines until we get a real request line, + * a read error, the connection closes (EOF), or we timeout. + * + * We skip empty lines because browsers have to tack a CRLF on to the end + * of POSTs to support old CERN webservers. But note that we may not + * have flushed any previous response completely to the client yet. + * We delay the flush as long as possible so that we can improve + * performance for clients that are pipelining requests. If a request + * is pipelined then we won't block during the (implicit) read() below. + * If the requests aren't pipelined, then the client is still waiting + * for the final buffer flush from us, and we will block in the implicit + * read(). B_SAFEREAD ensures that the BUFF layer flushes if it will + * have to block during a read. + */ + do { + /* ensure ap_rgetline allocates memory each time thru the loop + * if there are empty lines + */ + ctx->request_line = NULL; + len = 0; + rv = ap_rgetline(&ctx->request_line, (apr_size_t)(ctx->r->server->limit_req_line + 2), + &len, ctx->r, strict ? AP_GETLINE_CRLF : 0, bb); + + if (rv != APR_SUCCESS) { + return rv; + } + else if (len > 0) { + /* got the line in ctx->r->the_request */ + return APR_SUCCESS; + } + } while (--num_blank_lines >= 0); + /* too many blank lines */ + return APR_EINVAL; +} + +static void sanitize_brigade(apr_bucket_brigade *bb) +{ + apr_bucket *e, *next; + + for (e = APR_BRIGADE_FIRST(bb); + e != APR_BRIGADE_SENTINEL(bb); + e = next) + { + next = APR_BUCKET_NEXT(e); + if (!APR_BUCKET_IS_METADATA(e) && e->length == 0) { + apr_bucket_delete(e); + } + } +} + +apr_status_t ap_h1_request_in_filter(ap_filter_t *f, + apr_bucket_brigade *bb, + ap_input_mode_t mode, + apr_read_type_e block, + apr_off_t readbytes) +{ + request_rec *r = f->r; + apr_bucket *e; + h1_request_ctx *ctx = f->ctx; + apr_status_t rv = APR_SUCCESS; + int http_status = HTTP_OK; + + /* just get out of the way for things we don't want to handle. */ + if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) { + return ap_get_brigade(f->next, bb, mode, block, readbytes); + } + + if (!ctx) { + f->ctx = ctx = apr_pcalloc(r->pool, sizeof(*ctx)); + ctx->r = r; + ctx->state = REQ_LINE; + } + + /* This filter needs to get out of the way of read_request_line() */ + ap_remove_input_filter(f); + + while (APR_SUCCESS == rv) { + switch (ctx->state) { + case REQ_LINE: + if ((rv = read_request_line(ctx, bb)) != APR_SUCCESS) { + /* certain failures are answered with a HTTP error bucket + * and are terminal for parsing a request */ + ctx->method = ctx->uri = "-"; + ctx->protocol = "HTTP/1.0"; + if (APR_STATUS_IS_ENOSPC(rv)) { + http_status = HTTP_REQUEST_URI_TOO_LARGE; + } + else if (APR_STATUS_IS_TIMEUP(rv)) { + http_status = HTTP_REQUEST_TIME_OUT; + } + else if (APR_STATUS_IS_BADARG(rv)) { + http_status = HTTP_BAD_REQUEST; + } + else if (APR_STATUS_IS_EINVAL(rv)) { + http_status = HTTP_BAD_REQUEST; + } + goto cleanup; + } + + if (!ap_h1_tokenize_request_line(r, ctx->request_line, + &ctx->method, &ctx->uri, &ctx->protocol)) { + http_status = HTTP_BAD_REQUEST; + goto cleanup; + } + /* got the request line and it looked to contain what we need */ + ctx->state = REQ_HEADERS; + break; + + case REQ_HEADERS: + ap_get_mime_headers_core(r, bb); + if (r->status != HTTP_OK) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567) + "request failed: error reading the headers"); + http_status = r->status; + goto cleanup; + } + /* clear the brigade, as ap_get_mime_headers_core() leaves the last + * empty line in there, insert the REQUEST bucket and return */ + apr_brigade_cleanup(bb); + e = ap_bucket_request_createn(ctx->method, ctx->uri, + ctx->protocol, r->headers_in, + r->pool, r->connection->bucket_alloc); + /* reading may leave 0 length data buckets in the brigade, + * get rid of those. */ + sanitize_brigade(bb); + APR_BRIGADE_INSERT_HEAD(bb, e); + ctx->state = REQ_BODY; + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, + "http1 request and headers parsed: %s %s %s", + ctx->method, ctx->uri, ctx->protocol); + goto cleanup; + + case REQ_BODY: + /* we should not come here */ + AP_DEBUG_ASSERT(0); + rv = ap_get_brigade(f->next, bb, mode, block, readbytes); + goto cleanup; + + case REQ_ERROR: + default: + rv = APR_EINVAL; + goto cleanup; + } + } /* while(APR_SUCCESS == rv) */ + +cleanup: + if (http_status != HTTP_OK) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, + "failed reading request line, returning error bucket %d", http_status); + apr_brigade_cleanup(bb); + e = ap_bucket_error_create(http_status, NULL, r->pool, + f->c->bucket_alloc); + APR_BRIGADE_INSERT_TAIL(bb, e); + ctx->state = REQ_ERROR; + return APR_SUCCESS; + } + return rv; +}
\ No newline at end of file diff --git a/modules/http/http_protocol.c b/modules/http/http_protocol.c index 9335670052..e324bbcbb1 100644 --- a/modules/http/http_protocol.c +++ b/modules/http/http_protocol.c @@ -1636,3 +1636,205 @@ AP_DECLARE(void) ap_h1_add_end_chunk(apr_bucket_brigade *b, if (tmp) APR_BRIGADE_CONCAT(b, tmp); } } + +typedef enum { + rrl_none, rrl_badprotocol, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, + rrl_missinguri, rrl_baduri, rrl_trailingtext, +} rrl_error; + +/* get the length of a name for logging, but no more than 80 bytes */ +#define LOG_NAME_MAX_LEN 80 +static int log_name_len(const char *name) +{ + apr_size_t len = strlen(name); + return (len > LOG_NAME_MAX_LEN)? LOG_NAME_MAX_LEN : (int)len; +} + +static void rrl_log_error(request_rec *r, rrl_error error, const char *etoken) +{ + switch (error) { + case rrl_none: + break; + case rrl_badprotocol: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418) + "HTTP Request Line; Unrecognized protocol '%.*s' " + "(perhaps whitespace was injected?)", + log_name_len(etoken), etoken); + break; + case rrl_badmethod: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445) + "HTTP Request Line; Invalid method token: '%.*s'", + log_name_len(etoken), etoken); + break; + case rrl_badwhitespace: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447) + "HTTP Request Line; Invalid whitespace"); + break; + case rrl_excesswhitespace: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448) + "HTTP Request Line; Excess whitespace " + "(disallowed by HttpProtocolOptions Strict)"); + break; + case rrl_missinguri: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446) + "HTTP Request Line; Missing URI"); + case rrl_baduri: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454) + "HTTP Request Line; URI incorrectly encoded: '%.*s'", + log_name_len(etoken), etoken); + break; + case rrl_trailingtext: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449) + "HTTP Request Line; Extraneous text found '%.*s' " + "(perhaps whitespace was injected?)", + log_name_len(etoken), etoken); + break; + } +} + +/* remember the first error we encountered during tokenization */ +#define RRL_ERROR(e, et, y, yt) \ + do { \ + if (e == rrl_none) {\ + e = y; et = yt;\ + }\ + } while (0) + +static rrl_error tokenize_request_line( + char *line, int strict, + const char **pmethod, const char **puri, const char **pprotocol, + const char **perror_token) +{ + char *method, *protocol, *uri, *ll; + rrl_error e = rrl_none; + char *etoken = NULL; + apr_size_t len = 0; + + method = line; + /* If there is whitespace before a method, skip it and mark in error */ + if (apr_isspace(*method)) { + RRL_ERROR(e, etoken, rrl_badwhitespace, method); + for ( ; apr_isspace(*method); ++method) + ; + } + + /* Scan the method up to the next whitespace, ensure it contains only + * valid http-token characters, otherwise mark in error + */ + if (strict) { + ll = (char*) ap_scan_http_token(method); + } + else { + ll = (char*) ap_scan_vchar_obstext(method); + } + + if ((ll == method) || (*ll && !apr_isspace(*ll))) { + RRL_ERROR(e, etoken, rrl_badmethod, ll); + ll = strpbrk(ll, "\t\n\v\f\r "); + } + + /* Verify method terminated with a single SP, or mark as specific error */ + if (!ll) { + RRL_ERROR(e, etoken, rrl_missinguri, NULL); + protocol = uri = ""; + goto done; + } + else if (strict && ll[0] && apr_isspace(ll[1])) { + RRL_ERROR(e, etoken, rrl_excesswhitespace, ll); + } + + /* 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 != ' ') + RRL_ERROR(e, etoken, rrl_badwhitespace, uri); + *ll = '\0'; + + if (!*uri) + RRL_ERROR(e, etoken, rrl_missinguri, NULL); + + /* 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))) { + RRL_ERROR(e, etoken, rrl_baduri, ll); + ll = strpbrk(ll, "\t\n\v\f\r "); + } + + /* Verify URI terminated with a single SP, or mark as specific error */ + if (!ll) { + protocol = ""; + goto done; + } + else if (strict && ll[0] && apr_isspace(ll[1])) { + RRL_ERROR(e, etoken, rrl_excesswhitespace, ll); + } + + /* Advance protocol pointer over leading whitespace, NUL terminate the uri + * If non-SP whitespace is encountered, mark as specific error + */ + for (protocol = ll; apr_isspace(*protocol); ++protocol) + if (*protocol != ' ') + RRL_ERROR(e, etoken, rrl_badwhitespace, protocol); + *ll = '\0'; + + /* Scan the protocol up to the next whitespace, validation comes later */ + if (!(ll = (char*) ap_scan_vchar_obstext(protocol))) { + len = strlen(protocol); + goto done; + } + len = ll - protocol; + + /* Advance over trailing whitespace, if found mark in error, + * determine if trailing text is found, unconditionally mark in error, + * finally NUL terminate the protocol string + */ + if (*ll && !apr_isspace(*ll)) { + RRL_ERROR(e, etoken, rrl_badprotocol, ll); + } + else if (strict && *ll) { + RRL_ERROR(e, etoken, rrl_excesswhitespace, ll); + } + else { + for ( ; apr_isspace(*ll); ++ll) + if (*ll != ' ') { + RRL_ERROR(e, etoken, rrl_badwhitespace, ll); + break; + } + if (*ll) + RRL_ERROR(e, etoken, rrl_trailingtext, ll); + } + *((char *)protocol + len) = '\0'; + +done: + *pmethod = method; + *puri = uri; + *pprotocol = protocol; + *perror_token = etoken; + return e; +} + +AP_DECLARE(int) ap_h1_tokenize_request_line( + request_rec *r, const char *line, + const char **pmethod, const char **puri, const char **pprotocol) +{ + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + rrl_error error; + const char *error_token; + + ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, + "ap_tokenize_request_line: '%s'", line); + error = tokenize_request_line(apr_pstrdup(r->pool, line), strict, pmethod, + puri, pprotocol, &error_token); + ap_log_rerror(APLOG_MARK, APLOG_TRACE4, 0, r, + "ap_tokenize_request: error=%d, method=%s, uri=%s, protocol=%s", + error, *pmethod, *puri, *pprotocol); + if (error != rrl_none) { + rrl_log_error(r, error, error_token); + return 0; + } + return 1; +} diff --git a/server/protocol.c b/server/protocol.c index a11ae6cce2..8d9b4fd169 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -682,359 +682,13 @@ static int field_name_len(const char *field) return end - field; } -static const char m_invalid_str[] = "-"; - -static int read_request_line(request_rec *r, apr_bucket_brigade *bb) -{ - apr_size_t len; - int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES; - core_server_config *conf = ap_get_core_module_config(r->server->module_config); - int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); - - /* Read past empty lines until we get a real request line, - * a read error, the connection closes (EOF), or we timeout. - * - * We skip empty lines because browsers have to tack a CRLF on to the end - * of POSTs to support old CERN webservers. But note that we may not - * have flushed any previous response completely to the client yet. - * We delay the flush as long as possible so that we can improve - * performance for clients that are pipelining requests. If a request - * is pipelined then we won't block during the (implicit) read() below. - * If the requests aren't pipelined, then the client is still waiting - * for the final buffer flush from us, and we will block in the implicit - * read(). B_SAFEREAD ensures that the BUFF layer flushes if it will - * have to block during a read. - */ - - do { - apr_status_t rv; - - /* ensure ap_rgetline allocates memory each time thru the loop - * if there are empty lines - */ - r->the_request = NULL; - rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2), - &len, r, strict ? AP_GETLINE_CRLF : 0, bb); - - if (rv != APR_SUCCESS) { - r->request_time = apr_time_now(); - - /* Fall through with an invalid (non NULL) request */ - r->method = m_invalid_str; - r->method_number = M_INVALID; - r->uri = r->unparsed_uri = apr_pstrdup(r->pool, "-"); - - /* ap_rgetline returns APR_ENOSPC if it fills up the - * buffer before finding the end-of-line. This is only going to - * happen if it exceeds the configured limit for a request-line. - */ - if (APR_STATUS_IS_ENOSPC(rv)) { - r->status = HTTP_REQUEST_URI_TOO_LARGE; - } - else if (APR_STATUS_IS_TIMEUP(rv)) { - r->status = HTTP_REQUEST_TIME_OUT; - } - else if (APR_STATUS_IS_BADARG(rv)) { - r->status = HTTP_BAD_REQUEST; - } - else if (APR_STATUS_IS_EINVAL(rv)) { - r->status = HTTP_BAD_REQUEST; - } - r->proto_num = HTTP_VERSION(1,0); - r->protocol = "HTTP/1.0"; - return 0; - } - } while ((len <= 0) && (--num_blank_lines >= 0)); - - /* Set r->request_time before any logging, mod_unique_id needs it. */ - r->request_time = apr_time_now(); - - if (APLOGrtrace5(r)) { - ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r, - "Request received from client: %s", - ap_escape_logitem(r->pool, r->the_request)); - } - - return 1; -} - AP_DECLARE(int) ap_parse_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); - enum { - rrl_none, rrl_badmethod, rrl_badwhitespace, rrl_excesswhitespace, - rrl_missinguri, rrl_baduri, rrl_badprotocol, rrl_trailingtext, - rrl_badmethod09, rrl_reject09 - } deferred_error = rrl_none; - apr_size_t len = 0; - char *uri, *ll; - - r->method = r->the_request; - - /* If there is whitespace before a method, skip it and mark in error */ - if (apr_isspace(*r->method)) { - deferred_error = rrl_badwhitespace; - for ( ; apr_isspace(*r->method); ++r->method) - ; - } + const char *method, *uri, *protocol; - /* Scan the method up to the next whitespace, ensure it contains only - * valid http-token characters, otherwise mark in error - */ - if (strict) { - ll = (char*) ap_scan_http_token(r->method); - } - else { - ll = (char*) ap_scan_vchar_obstext(r->method); - } - - if (((ll == r->method) || (*ll && !apr_isspace(*ll))) - && deferred_error == rrl_none) { - deferred_error = rrl_badmethod; - ll = strpbrk(ll, "\t\n\v\f\r "); - } - - /* Verify method terminated with a single SP, or mark as specific error */ - if (!ll) { - if (deferred_error == rrl_none) - deferred_error = rrl_missinguri; - r->protocol = uri = ""; - goto rrl_done; - } - else if (strict && ll[0] && apr_isspace(ll[1]) - && deferred_error == rrl_none) { - deferred_error = rrl_excesswhitespace; - } - - /* 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) - deferred_error = rrl_badwhitespace; - *ll = '\0'; - - if (!*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))) { - deferred_error = rrl_baduri; - ll = strpbrk(ll, "\t\n\v\f\r "); - } - - /* Verify URI terminated with a single SP, or mark as specific error */ - if (!ll) { - r->protocol = ""; - goto rrl_done; - } - else if (strict && ll[0] && apr_isspace(ll[1]) - && deferred_error == rrl_none) { - deferred_error = rrl_excesswhitespace; - } - - /* Advance protocol pointer over leading whitespace, NUL terminate the uri - * If non-SP whitespace is encountered, mark as specific error - */ - for (r->protocol = ll; apr_isspace(*r->protocol); ++r->protocol) - if (*r->protocol != ' ' && deferred_error == rrl_none) - deferred_error = rrl_badwhitespace; - *ll = '\0'; - - /* Scan the protocol up to the next whitespace, validation comes later */ - if (!(ll = (char*) ap_scan_vchar_obstext(r->protocol))) { - len = strlen(r->protocol); - goto rrl_done; - } - len = ll - r->protocol; - - /* Advance over trailing whitespace, if found mark in error, - * determine if trailing text is found, unconditionally mark in error, - * finally NUL terminate the protocol string - */ - if (*ll && !apr_isspace(*ll)) { - deferred_error = rrl_badprotocol; - } - else if (strict && *ll) { - deferred_error = rrl_excesswhitespace; - } - else { - for ( ; apr_isspace(*ll); ++ll) - if (*ll != ' ' && deferred_error == rrl_none) - deferred_error = rrl_badwhitespace; - if (*ll && deferred_error == rrl_none) - deferred_error = rrl_trailingtext; - } - *((char *)r->protocol + len) = '\0'; - -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->protocol ? " " : NULL, r->protocol, NULL); - - if (len == 8 - && r->protocol[0] == 'H' && r->protocol[1] == 'T' - && r->protocol[2] == 'T' && r->protocol[3] == 'P' - && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) - && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) - && r->protocol[5] != '0') { - r->assbackwards = 0; - r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); - } - else if (len == 8 - && (r->protocol[0] == 'H' || r->protocol[0] == 'h') - && (r->protocol[1] == 'T' || r->protocol[1] == 't') - && (r->protocol[2] == 'T' || r->protocol[2] == 't') - && (r->protocol[3] == 'P' || r->protocol[3] == 'p') - && r->protocol[4] == '/' && apr_isdigit(r->protocol[5]) - && r->protocol[6] == '.' && apr_isdigit(r->protocol[7]) - && r->protocol[5] != '0') { - r->assbackwards = 0; - r->proto_num = HTTP_VERSION(r->protocol[5] - '0', r->protocol[7] - '0'); - if (strict && deferred_error == rrl_none) - deferred_error = rrl_badprotocol; - else - memcpy((char*)r->protocol, "HTTP", 4); - } - else if (r->protocol[0]) { - r->proto_num = HTTP_VERSION(0, 9); - /* Defer setting the r->protocol string till error msg is composed */ - if (deferred_error == rrl_none) - deferred_error = rrl_badprotocol; - } - else { - r->assbackwards = 1; - r->protocol = "HTTP/0.9"; - 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); - if (r->status == HTTP_OK - && (r->parsed_uri.path != NULL) - && (r->parsed_uri.path[0] != '/') - && (r->method_number != M_OPTIONS - || strcmp(r->parsed_uri.path, "*") != 0)) { - /* Invalid request-target per RFC 7230 section 5.3 */ - r->status = HTTP_BAD_REQUEST; - } - - /* 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) - deferred_error = rrl_reject09; - else if (strict && (r->method_number != M_GET || r->header_only)) - deferred_error = rrl_badmethod09; - } - - /* Now that the method, uri and protocol are all processed, - * we can safely resume any deferred error reporting - */ - if (deferred_error != rrl_none) { - if (deferred_error == rrl_badmethod) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03445) - "HTTP Request Line; Invalid method token: '%.*s'", - field_name_len(r->method), r->method); - else if (deferred_error == rrl_badmethod09) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03444) - "HTTP Request Line; Invalid method token: '%.*s'" - " (only GET is allowed for HTTP/0.9 requests)", - field_name_len(r->method), r->method); - else if (deferred_error == rrl_missinguri) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03446) - "HTTP Request Line; Missing URI"); - else if (deferred_error == rrl_baduri) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03454) - "HTTP Request Line; URI incorrectly encoded: '%.*s'", - field_name_len(r->unparsed_uri), r->unparsed_uri); - else if (deferred_error == rrl_badwhitespace) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03447) - "HTTP Request Line; Invalid whitespace"); - else if (deferred_error == rrl_excesswhitespace) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03448) - "HTTP Request Line; Excess whitespace " - "(disallowed by HttpProtocolOptions Strict)"); - else if (deferred_error == rrl_trailingtext) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03449) - "HTTP Request Line; Extraneous text found '%.*s' " - "(perhaps whitespace was injected?)", - field_name_len(ll), ll); - else if (deferred_error == rrl_reject09) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02401) - "HTTP Request Line; Rejected HTTP/0.9 request"); - else if (deferred_error == rrl_badprotocol) - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418) - "HTTP Request Line; Unrecognized protocol '%.*s' " - "(perhaps whitespace was injected?)", - field_name_len(r->protocol), r->protocol); - r->status = HTTP_BAD_REQUEST; - goto rrl_failed; - } - - if (conf->http_methods == AP_HTTP_METHODS_REGISTERED - && r->method_number == M_INVALID) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) - "HTTP Request Line; Unrecognized HTTP method: '%.*s' " - "(disallowed by RegisteredMethods)", - field_name_len(r->method), r->method); - r->status = HTTP_NOT_IMPLEMENTED; - /* This can't happen in an HTTP/0.9 request, we verified GET above */ - return 0; - } - - if (r->status != HTTP_OK) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03450) - "HTTP Request Line; Unable to parse URI: '%.*s'", - field_name_len(r->uri), r->uri); - goto rrl_failed; - } - - if (strict) { - if (r->parsed_uri.fragment) { - /* RFC3986 3.5: no fragment */ - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421) - "HTTP Request Line; URI must not contain a fragment"); - r->status = HTTP_BAD_REQUEST; - goto rrl_failed; - } - if (r->parsed_uri.user || r->parsed_uri.password) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422) - "HTTP Request Line; URI must not contain a " - "username/password"); - r->status = HTTP_BAD_REQUEST; - goto rrl_failed; - } - } - - return 1; - -rrl_failed: - 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"; - } - return 0; + return ap_h1_tokenize_request_line(r, r->the_request, + &method, &uri, &protocol) + && ap_assign_request_line(r, method, uri, protocol); } AP_DECLARE(int) ap_check_request_header(request_rec *r) @@ -1466,22 +1120,196 @@ static void apply_server_config(request_rec *r) r->per_dir_config = r->server->lookup_defaults; } -AP_DECLARE(int) ap_assign_request(request_rec *r, - const char *method, const char *uri, - const char *protocol) +typedef enum { + http_error_none, + http_error_badprotocol, + http_error_reject09, + http_error_badmethod09, +} http_error; + +static http_error r_assign_protocol(request_rec *r, + const char *protocol, + int strict) { - /* dummy, for now */ - (void)r; - (void)method; - (void)uri; - (void)protocol; + int proto_num; + http_error error = http_error_none; + + if (protocol[0] == 'H' && protocol[1] == 'T' + && protocol[2] == 'T' && protocol[3] == 'P' + && protocol[4] == '/' && apr_isdigit(protocol[5]) + && protocol[6] == '.' && apr_isdigit(protocol[7]) + && !protocol[8] && protocol[5] != '0') { + r->assbackwards = 0; + proto_num = HTTP_VERSION(protocol[5] - '0', protocol[7] - '0'); + } + else if ((protocol[0] == 'H' || protocol[0] == 'h') + && (protocol[1] == 'T' || protocol[1] == 't') + && (protocol[2] == 'T' || protocol[2] == 't') + && (protocol[3] == 'P' || protocol[3] == 'p') + && protocol[4] == '/' && apr_isdigit(protocol[5]) + && protocol[6] == '.' && apr_isdigit(protocol[7]) + && !protocol[8] && protocol[5] != '0') { + r->assbackwards = 0; + proto_num = HTTP_VERSION(protocol[5] - '0', protocol[7] - '0'); + if (strict && error == http_error_none) + error = http_error_badprotocol; + else + protocol = apr_psprintf(r->pool, "HTTP/%d.%d", HTTP_VERSION_MAJOR(proto_num), + HTTP_VERSION_MINOR(proto_num)); + } + else if (protocol[0]) { + proto_num = HTTP_VERSION(0, 9); + if (error == http_error_none) + error = http_error_badprotocol; + } + else { + r->assbackwards = 1; + protocol = "HTTP/0.9"; + proto_num = HTTP_VERSION(0, 9); + } + r->protocol = protocol; + r->proto_num = proto_num; + return error; +} + +AP_DECLARE(int) ap_assign_request_line(request_rec *r, + const char *method, const char *uri, + const char *protocol) +{ + core_server_config *conf = ap_get_core_module_config(r->server->module_config); + int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE); + http_error error = r_assign_protocol(r, protocol, strict); + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "assigned protocol: %s, error=%d", r->protocol, error); + + /* Determine the method_number and parse the uri prior to invoking error + * handling, such that these fields are available for substitution + */ + r->method = method; + r->method_number = ap_method_number_of(r->method); + if (r->method_number == M_GET && r->method[0] == 'H') + r->header_only = 1; + + /* 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->protocol ? " " : NULL, r->protocol, NULL); + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "assigned request_line: %s, error=%d", r->the_request, error); + + ap_parse_uri(r, uri); + if (r->status == HTTP_OK + && (r->parsed_uri.path != NULL) + && (r->parsed_uri.path[0] != '/') + && (r->method_number != M_OPTIONS + || strcmp(r->parsed_uri.path, "*") != 0)) { + /* Invalid request-target per RFC 7230 section 5.3 */ + r->status = HTTP_BAD_REQUEST; + } + + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "parsed uri: r->status=%d, error=%d", r->status, error); + /* With the request understood, we can consider HTTP/0.9 specific errors */ + if (r->proto_num == HTTP_VERSION(0, 9) && error == http_error_none) { + if (conf->http09_enable == AP_HTTP09_DISABLE) + error = http_error_reject09; + else if (strict && (r->method_number != M_GET || r->header_only)) + error = http_error_badmethod09; + } + + /* Now that the method, uri and protocol are all processed, + * we can safely resume any deferred error reporting + */ + if (error != http_error_none) { + switch (error) { + case http_error_badprotocol: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(10388) + "HTTP Request: Unrecognized protocol '%.*s' " + "(perhaps whitespace was injected?)", + field_name_len(r->protocol), r->protocol); + break; + case http_error_reject09: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02401) + "HTTP Request: Rejected HTTP/0.9 request"); + break; + case http_error_badmethod09: + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03444) + "HTTP Request: Invalid method token: '%.*s'" + " (only GET is allowed for HTTP/0.9 requests)", + field_name_len(r->method), r->method); + break; + default: + break; + } + r->status = HTTP_BAD_REQUEST; + goto failed; + } + + if (conf->http_methods == AP_HTTP_METHODS_REGISTERED + && r->method_number == M_INVALID) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02423) + "HTTP Request Line; Unrecognized HTTP method: '%.*s' " + "(disallowed by RegisteredMethods)", + field_name_len(r->method), r->method); + r->status = HTTP_NOT_IMPLEMENTED; + /* This can't happen in an HTTP/0.9 request, we verified GET above */ + goto failed; + } + + if (r->status != HTTP_OK) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03450) + "HTTP Request Line; Unable to parse URI: '%.*s'", + field_name_len(r->uri), r->uri); + goto failed; + } + + if (strict) { + if (r->parsed_uri.fragment) { + /* RFC3986 3.5: no fragment */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02421) + "HTTP Request Line; URI must not contain a fragment"); + r->status = HTTP_BAD_REQUEST; + goto failed; + } + if (r->parsed_uri.user || r->parsed_uri.password) { + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02422) + "HTTP Request Line; URI must not contain a " + "username/password"); + r->status = HTTP_BAD_REQUEST; + goto failed; + } + } + return 1; + +failed: + if (error != http_error_none && 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"; + } return 0; + } -request_rec *ap_read_request(conn_rec *conn) +AP_DECLARE(request_rec *) ap_read_request(conn_rec *conn) { int access_status; apr_bucket_brigade *tmp_bb; + apr_bucket *e, *bdata = NULL; + ap_bucket_request *breq = NULL; + const char *method, *uri, *protocol; + apr_table_t *headers; + apr_status_t rv; request_rec *r = ap_create_request(conn); @@ -1490,8 +1318,72 @@ 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)) { + r->request_time = apr_time_now(); + rv = ap_get_brigade(r->proto_input_filters, tmp_bb, AP_MODE_READBYTES, APR_BLOCK_READ, 0); + if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(tmp_bb)) { + /* Not worth dying with. */ + conn->keepalive = AP_CONN_CLOSE; + apr_pool_destroy(r->pool); + goto ignore; + } + + for (e = APR_BRIGADE_FIRST(tmp_bb); + e != APR_BRIGADE_SENTINEL(tmp_bb); + e = APR_BUCKET_NEXT(e)) + { + if (AP_BUCKET_IS_REQUEST(e)) { + if (!breq) breq = e->data; + } + else if (AP_BUCKET_IS_ERROR(e)) { + access_status = ((ap_bucket_error *)(e->data))->status; + goto die_unusable_input; + } + else if (!APR_BUCKET_IS_METADATA(e) && e->length != 0) { + if (!bdata) bdata = e;; + break; + } + } + + if (bdata) { + /* Since processing of a request body depends on knowing the request, we + * cannot handle any data here. For example, chunked-encoding filters are + * added after the request is read, so any data buckets here will not + * have been de-chunked. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10391) + "request failed: seeing DATA bucket(len=%d) of request " + "body, too early to process", (int)bdata->length); + access_status = HTTP_INTERNAL_SERVER_ERROR; + goto die_unusable_input; + } + else if (!breq) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10389) + "request failed: neither request bucket nor error at start of input"); + access_status = HTTP_INTERNAL_SERVER_ERROR; + goto die_unusable_input; + } + + if (apr_pool_is_ancestor(r->pool, breq->pool)) { + method = breq->method; + uri = breq->uri; + protocol = breq->protocol; + headers = breq->headers; + } + else { + method = apr_pstrdup(r->pool, breq->method); + uri = apr_pstrdup(r->pool, breq->uri); + protocol = apr_pstrdup(r->pool, breq->protocol); + headers = breq->headers? apr_table_clone(r->pool, breq->headers) : NULL; + } + + if (headers) { + r->headers_in = headers; + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, + "checking request: %s %s %s", + method, uri, protocol); + + if (!ap_assign_request_line(r, method, uri, protocol)) { apr_brigade_cleanup(tmp_bb); switch (r->status) { case HTTP_REQUEST_URI_TOO_LARGE: @@ -1503,7 +1395,7 @@ request_rec *ap_read_request(conn_rec *conn) "request failed: client's request-line exceeds LimitRequestLine (longer than %d)", r->server->limit_req_line); } - else if (r->method == m_invalid_str) { + else if (!strcmp("-", r->method)) { ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00566) "request failed: malformed request line"); } @@ -1531,18 +1423,7 @@ request_rec *ap_read_request(conn_rec *conn) apply_server_config(r); if (!r->assbackwards) { - const char *tenc, *clen; - - ap_get_mime_headers_core(r, tmp_bb); - apr_brigade_cleanup(tmp_bb); - if (r->status != HTTP_OK) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00567) - "request failed: error reading the headers"); - access_status = r->status; - goto die_unusable_input; - } - - clen = apr_table_get(r->headers_in, "Content-Length"); + const char *clen = apr_table_get(r->headers_in, "Content-Length"); if (clen) { apr_off_t cl; @@ -1554,56 +1435,14 @@ request_rec *ap_read_request(conn_rec *conn) goto die_unusable_input; } } - - tenc = apr_table_get(r->headers_in, "Transfer-Encoding"); - if (tenc) { - /* https://tools.ietf.org/html/rfc7230 - * Section 3.3.3.3: "If a Transfer-Encoding header field is - * present in a request and the chunked transfer coding is not - * the final encoding ...; the server MUST respond with the 400 - * (Bad Request) status code and then close the connection". - */ - if (!ap_is_chunked(r->pool, tenc)) { - ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02539) - "client sent unknown Transfer-Encoding " - "(%s): %s", tenc, r->uri); - access_status = HTTP_BAD_REQUEST; - goto die_unusable_input; - } - - /* https://tools.ietf.org/html/rfc7230 - * Section 3.3.3.3: "If a message is received with both a - * Transfer-Encoding and a Content-Length header field, the - * Transfer-Encoding overrides the Content-Length. ... A sender - * MUST remove the received Content-Length field". - */ - if (clen) { - apr_table_unset(r->headers_in, "Content-Length"); - - /* Don't reuse this connection anyway to avoid confusion with - * intermediaries and request/reponse spltting. - */ - conn->keepalive = AP_CONN_CLOSE; - } - } } /* - * Add the HTTP_IN filter here to ensure that ap_discard_request_body - * called by ap_die and by ap_send_error_response works correctly on - * status codes that do not cause the connection to be dropped and - * in situations where the connection should be kept alive. + * Add HTTP_IN here to ensure that content and HEADERS buckets are processed + * accordingly, e.g. populating r->trailers_in for example. */ ap_add_input_filter_handle(ap_http_input_filter_handle, NULL, r, r->connection); - if (r->proto_num <= HTTP_VERSION(1,1)) { - ap_add_input_filter_handle(ap_h1_body_in_filter_handle, - NULL, r, r->connection); - if (r->proto_num >= HTTP_VERSION(1,0) - && apr_table_get(r->headers_in, "Transfer-Encoding")) { - r->body_indeterminate = 1; - } - } /* Validate Host/Expect headers and select vhost. */ if (!ap_check_request_header(r)) { |