summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changes-entries/core_request_buckets.txt10
-rw-r--r--include/ap_mmn.h2
-rw-r--r--include/http_protocol.h24
-rw-r--r--include/mod_core.h5
-rw-r--r--modules/http/http_core.c63
-rw-r--r--modules/http/http_filters.c193
-rw-r--r--modules/http/http_protocol.c202
-rw-r--r--server/protocol.c675
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)) {