You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2022/04/13 07:40:18 UTC

svn commit: r1899799 - in /httpd/httpd/trunk: changes-entries/core_request_buckets.txt include/ap_mmn.h include/http_protocol.h include/mod_core.h modules/http/http_core.c modules/http/http_filters.c modules/http/http_protocol.c server/protocol.c

Author: icing
Date: Wed Apr 13 07:40:17 2022
New Revision: 1899799

URL: http://svn.apache.org/viewvc?rev=1899799&view=rev
Log:
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.


Added:
    httpd/httpd/trunk/changes-entries/core_request_buckets.txt
Modified:
    httpd/httpd/trunk/include/ap_mmn.h
    httpd/httpd/trunk/include/http_protocol.h
    httpd/httpd/trunk/include/mod_core.h
    httpd/httpd/trunk/modules/http/http_core.c
    httpd/httpd/trunk/modules/http/http_filters.c
    httpd/httpd/trunk/modules/http/http_protocol.c
    httpd/httpd/trunk/server/protocol.c

Added: httpd/httpd/trunk/changes-entries/core_request_buckets.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/core_request_buckets.txt?rev=1899799&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/core_request_buckets.txt (added)
+++ httpd/httpd/trunk/changes-entries/core_request_buckets.txt Wed Apr 13 07:40:17 2022
@@ -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]

Modified: httpd/httpd/trunk/include/ap_mmn.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/ap_mmn.h?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/include/ap_mmn.h (original)
+++ httpd/httpd/trunk/include/ap_mmn.h Wed Apr 13 07:40:17 2022
@@ -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()
  */
 

Modified: httpd/httpd/trunk/include/http_protocol.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/http_protocol.h?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/include/http_protocol.h (original)
+++ httpd/httpd/trunk/include/http_protocol.h Wed Apr 13 07:40:17 2022
@@ -65,19 +65,33 @@ AP_DECLARE(request_rec *) ap_create_requ
  * @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.

Modified: httpd/httpd/trunk/include/mod_core.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/include/mod_core.h?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/include/mod_core.h (original)
+++ httpd/httpd/trunk/include/mod_core.h Wed Apr 13 07:40:17 2022
@@ -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
                             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);

Modified: httpd/httpd/trunk/modules/http/http_core.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_core.c?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http/http_core.c (original)
+++ httpd/httpd/trunk/modules/http/http_core.c Wed Apr 13 07:40:17 2022
@@ -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_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);

Modified: httpd/httpd/trunk/modules/http/http_filters.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_filters.c?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http/http_filters.c (original)
+++ httpd/httpd/trunk/modules/http/http_filters.c Wed Apr 13 07:40:17 2022
@@ -1856,12 +1856,12 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
                 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_
                     /* 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_
                         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_
             /* 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_bucke
     }
     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

Modified: httpd/httpd/trunk/modules/http/http_protocol.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http/http_protocol.c?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http/http_protocol.c (original)
+++ httpd/httpd/trunk/modules/http/http_protocol.c Wed Apr 13 07:40:17 2022
@@ -1636,3 +1636,205 @@ AP_DECLARE(void) ap_h1_add_end_chunk(apr
         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;
+}

Modified: httpd/httpd/trunk/server/protocol.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/server/protocol.c?rev=1899799&r1=1899798&r2=1899799&view=diff
==============================================================================
--- httpd/httpd/trunk/server/protocol.c (original)
+++ httpd/httpd/trunk/server/protocol.c Wed Apr 13 07:40:17 2022
@@ -682,359 +682,13 @@ static int field_name_len(const char *fi
     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)
-            ; 
-    }
-
-    /* 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';
+    const char *method, *uri, *protocol;
 
-    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_
     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)
+{
+    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)
 {
-    /* dummy, for now */
-    (void)r;
-    (void)method;
-    (void)uri;
-    (void)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 *c
 
     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 *c
                               "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 *c
     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 *c
                 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)) {