diff options
| author | Stefan Eissing <icing@apache.org> | 2022-04-13 07:40:17 +0000 |
|---|---|---|
| committer | Stefan Eissing <icing@apache.org> | 2022-04-13 07:40:17 +0000 |
| commit | fbb84e00fa53ca34b97f9acc4d024e512d4e6f23 (patch) | |
| tree | 47249fbbc06ebbf64e767683f870c0d7dd14213f /modules/http | |
| parent | d150ca6f4e93622150fd4867e7f7c1eec0026acb (diff) | |
| download | httpd-fbb84e00fa53ca34b97f9acc4d024e512d4e6f23.tar.gz | |
Merge PR 311:
*) 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.
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899799 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'modules/http')
| -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 |
3 files changed, 451 insertions, 7 deletions
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; +} |
