[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

testing and review requested for Wheezy update of apache2



On 2016-12-28 15:44:25, Stefan Fritsch wrote:
> Hi Ola,
>
> On Friday, 23 December 2016 23:56:45 CET Ola Lundqvist wrote:
>> the Debian LTS team would like to fix the security issues which are
>> currently open in the Wheezy version of apache2:
>> https://security-tracker.debian.org/tracker/CVE-2016-8743
>> 
>> Would you like to take care of this yourself?
>
> The fix for that is very invasive and may well break some things. I would wait 
> with a backport until the fix has seen more exposure, both upstream and in 
> stretch (the fix will migrate from sid in a few days). 
>
> Also, there is some work upstream to get the changes backported to 2.2 in a 
> separate 2.2.x-merge-http-strict branch [1]. But it has not landed in the 
> 2.2.x branch, yet.
>
> I will share with you any insights I get from backporting the changes to 
> jessie. But it is somewhat unlikely that I will have time to do the backport 
> to wheezy myself.

Hi!

Thanks for the response. We'll be happy to take on the update for
wheezy, and hopefully our work will be useful for Jessie as well...

I started looking at the 2.2.x backport, which has now landed in the
[2.2.32 release][1]. I unfortunately don't think we can use the release
directly, because it is 10 minor releases away from the version
currently in Wheezy (2.2.22), but I'd be glad to be proven wrong... It
may be better to just follow upstream here as well, since their 2.2
branch is designed for LTS support. But they do fix way more stuff than
just security issues in there...

[1]: http://www.apache.org/dist/httpd/Announcement2.2.html

The patch is far from trivial and it's a bit all over the place. I've
gathered it into a single patch from two upstream commits ([r1777405][]
and [r1777999][]), taken from the [2.2.32 tag][].

[r1777405]: http://svn.apache.org/viewvc?view=revision&revision=1777405
[r1777999]: http://svn.apache.org/viewvc?view=revision&revision=1777999
[2.2.32 tag]: http://svn.apache.org/viewvc/httpd/httpd/tags/2.2.32/?view=log

I hammered at the code until the patch actually builds and I tested it
summarily in a wheezy VM, but i'm not sure where or what to test in
there... There's a webserver and "it works!" but that's about it for
now. I still need to review the new patch and compare it with upstream
to see if I really followed the spirit of the patch - so far I just made
sure the chunks apply cleanly...

Attached is a debdiff of the resulting package, which has been uploaded
to the [usual location][].

[usual location]: https://people.debian.org/~anarcat/debian/wheezy-lts/

I would need people to start testing the package at this point, not
necessarily in production considering how big the change is, but your
comfort level will vary with the severity and complexity of services. :)

Pointers on how to reproduce actual security issues would be welcome,
along with code reviews.

I am especially concerned by the proxy changes in the 2.2.x branch we
don't have in our code base: since the vulnerability implies mostly
proxy chains, this could mean we can't fix the issue properly without a
full backport of all those changes..

In fact, I wouldn't feel completely confident in this upload unless we
ship 2.2.32. Apache's codebase really reflects its name ("a patchy
server") so it's quite hard to figure out what exactly is going on in
there. They are also quite liberal in the changes they do - the patch
basically rewrites request handling completely!

Thanks for any feedback,

A.

-- 
Il est sage de nous réconcilier avec notre adolescence ; haїr, mépriser,
nier ou simplement oublier l’adolescent que nous fûmes est en soi une
attitude adolescente.
                        - Daniel Pennac, Comme un roman
diff -Nru apache2-2.2.22/debian/changelog apache2-2.2.22/debian/changelog
--- apache2-2.2.22/debian/changelog	2016-07-20 01:04:30.000000000 -0400
+++ apache2-2.2.22/debian/changelog	2017-01-17 10:53:56.000000000 -0500
@@ -1,3 +1,16 @@
+apache2 (2.2.22-13+deb7u8) UNRELEASED; urgency=high
+
+  * Non-maintainer upload by the LTS Security Team.
+  * Security: CVE-2016-8743:
+    Enforce HTTP request grammar corresponding to RFC7230 for request lines
+    and request headers, to prevent response splitting and cache pollution by
+    malicious clients or downstream proxies.
+  * The stricter HTTP enforcement may cause compatibility problems with
+    non-conforming clients. Fine-tuning is possible with the new
+    HttpProtocolOptions directive.
+
+ -- Antoine Beaupré <anarcat@debian.org>  Tue, 17 Jan 2017 10:53:56 -0500
+
 apache2 (2.2.22-13+deb7u7) wheezy-security; urgency=high
 
   * Non-maintainer upload.
diff -Nru apache2-2.2.22/debian/patches/CVE-2016-8743.patch apache2-2.2.22/debian/patches/CVE-2016-8743.patch
--- apache2-2.2.22/debian/patches/CVE-2016-8743.patch	1969-12-31 19:00:00.000000000 -0500
+++ apache2-2.2.22/debian/patches/CVE-2016-8743.patch	2017-01-17 10:53:56.000000000 -0500
@@ -0,0 +1,2153 @@
+Description: fixes for CVE-2016-8743
+ those are r1777405 and r1777999 from the 2.2.23 tag of the
+ upstream repository:
+
+ http://svn.apache.org/viewvc?view=revision&revision=1777405
+ http://svn.apache.org/viewvc?view=revision&revision=1777999
+
+--- a/docs/manual/mod/core.html
++++ b/docs/manual/mod/core.html
+@@ -19,3 +19,93 @@ Content-type: text/html; charset=UTF-8
+ URI: core.html.tr.utf8
+ Content-Language: tr
+ Content-type: text/html; charset=UTF-8
++<directivesynopsis>
++<name>HttpProtocolOptions</name>
++<description>Modify restrictions on HTTP Request Messages</description>
++<syntax>HttpProtocolOptions [Strict|Unsafe] [RegisteredMethods|LenientMethods]
++ [Allow0.9|Require1.0]</syntax>
++<default>HttpProtocolOptions Strict LenientMethods Allow0.9</default>
++<contextlist><context>server config</context>
++<context>virtual host</context></contextlist>
++<compatibility>2.2.32 or 2.4.24 and later</compatibility>
++
++<usage>
++    <p>This directive changes the rules applied to the HTTP Request Line
++    (<a href="https://tools.ietf.org/html/rfc7230#section-3.1.1";
++      >RFC 7230 &sect;3.1.1</a>) and the HTTP Request Header Fields
++    (<a href="https://tools.ietf.org/html/rfc7230#section-3.2";
++      >RFC 7230 &sect;3.2</a>), which are now applied by default or using
++    the <code>Strict</code> option. Due to legacy modules, applications or
++    custom user-agents which must be deperecated the <code>Unsafe</code>
++    option has been added to revert to the legacy behaviors. These rules
++    are applied prior to request processing, so must be configured at the
++    global or default (first) matching virtual host section, by IP/port
++    interface (and not by name) to be honored.</p>
++
++    <p>Prior to the introduction of this directive, the Apache HTTP Server
++    request message parsers were tolerant of a number of forms of input
++    which did not conform to the protocol.
++    <a href="https://tools.ietf.org/html/rfc7230#section-9.4";
++      >RFC 7230 &sect;9.4 Request Splitting</a> and
++    <a href="https://tools.ietf.org/html/rfc7230#section-9.5";
++      >&sect;9.5 Response Smuggling</a> call out only two of the potential
++    risks of accepting non-conformant request messages, while
++    <a href="https://tools.ietf.org/html/rfc7230#section-3.5";
++         >RFC 7230 &sect;3.5</a> "Message Parsing Robustness" identify the
++    risks of accepting obscure whitespace and request message formatting. 
++    As of the introduction of this directive, all grammer rules of the
++    specification are enforced in the default <code>Strict</code> operating
++    mode, and the strict whitespace suggested by section 3.5 is enforced
++    and cannot be relaxed.</p>
++
++    <p>Users are strongly cautioned against toggling the <code>Unsafe</code>
++    mode of operation, particularly on outward-facing, publicly accessible
++    server deployments.  If an interface is required for faulty monitoring
++    or other custom service consumers running on an intranet, users should
++    toggle the Unsafe option only on a specific virtual host configured
++    to service their internal private network.</p>
++
++    <p>Reviewing the messages logged to the <directive>ErrorLog</directive>,
++    configured with <directive>LogLevel</directive> <code>debug</code> level,
++    can help identify such faulty requests along with their origin.
++    Users should pay particular attention to the 400 responses in the access
++    log for invalid requests which were unexpectedly rejected.</p>
++
++    <p><a href="https://tools.ietf.org/html/rfc7231#section-4.1";
++         >RFC 7231 &sect;4.1</a> "Request Methods" "Overview" requires that
++    origin servers shall respond with an error when an unsupported method
++    is encountered in the request line. This already happens when the
++    <code>LenientMethods</code> option is used, but administrators may wish
++    to toggle the <code>RegisteredMethods</code> option and register any
++    non-standard methods using the <directive>RegisterHttpMethod</directive>
++    directive, particularly if the <code>Unsafe</code> option has been toggled.
++    The <code>RegisteredMethods</code> option should <strong>not</strong>
++    be toggled for forward proxy hosts, as the methods supported by the
++    origin servers are unknown to the proxy server.</p>
++
++    <p><a href="https://tools.ietf.org/html/rfc2616#section-19.6";
++         >RFC 2616 &sect;19.6</a> "Compatibility With Previous Versions" had
++    encouraged HTTP servers to support legacy HTTP/0.9 requests. RFC 7230
++    superceeds this with "The expectation to support HTTP/0.9 requests has
++    been removed" and offers additional comments in 
++    <a href="https://tools.ietf.org/html/rfc7230#appendix-A";
++      >RFC 7230 Appendix A</a>. The <code>Require1.0</code> option allows
++    the user to remove support of the default <code>Allow0.9</code> option's
++    behavior.</p>
++</usage>
++</directivesynopsis>
++
++<directivesynopsis>
++<name>RegisterHttpMethod</name>
++<description>Register non-standard HTTP methods</description>
++<syntax>RegisterHttpMethod <var>method</var> [<var>method</var> [...]]</syntax>
++<contextlist><context>server config</context></contextlist>
++<usage>
++<p>HTTP Methods that are not conforming to the relvant RFCs are normally
++rejected by request processing in Apache HTTPD. To avoid this, modules
++can register non-standard HTTP methods they support.
++The <directive>RegisterHttpMethod</directive> allows to register such
++methods manually. This can be useful for if such methods are forwared
++for external processing, e.g. to a CGI script.</p>
++</usage>
++</directivesynopsis>
+--- a/server/gen_test_char.c
++++ b/server/gen_test_char.c
+@@ -16,11 +16,11 @@
+ 
+ #ifdef CROSS_COMPILE
+ 
++#include <ctype.h>
+ #define apr_isalnum(c) (isalnum(((unsigned char)(c))))
+ #define apr_isalpha(c) (isalpha(((unsigned char)(c))))
+ #define apr_iscntrl(c) (iscntrl(((unsigned char)(c))))
+ #define apr_isprint(c) (isprint(((unsigned char)(c))))
+-#include <ctype.h>
+ #define APR_HAVE_STDIO_H 1
+ #define APR_HAVE_STRING_H 1
+ 
+@@ -51,11 +51,13 @@
+ #define T_HTTP_TOKEN_STOP     (0x08)
+ #define T_ESCAPE_LOGITEM      (0x10)
+ #define T_ESCAPE_FORENSIC     (0x20)
++#define T_HTTP_CTRLS          (0x80)
++#define T_VCHAR_OBSTEXT      (0x100)
+ 
+ int main(int argc, char *argv[])
+ {
+     unsigned c;
+-    unsigned char flags;
++    unsigned short flags;
+ 
+     printf("/* this file is automatically generated by gen_test_char, "
+            "do not edit */\n"
+@@ -65,18 +67,22 @@ int main(int argc, char *argv[])
+            "#define T_HTTP_TOKEN_STOP      (%u)\n"
+            "#define T_ESCAPE_LOGITEM       (%u)\n"
+            "#define T_ESCAPE_FORENSIC      (%u)\n"
++           "#define T_HTTP_CTRLS           (%u)\n"
++           "#define T_VCHAR_OBSTEXT        (%u)\n"
+            "\n"
+-           "static const unsigned char test_char_table[256] = {",
++           "static const unsigned short test_char_table[256] = {",
+            T_ESCAPE_SHELL_CMD,
+            T_ESCAPE_PATH_SEGMENT,
+            T_OS_ESCAPE_PATH,
+            T_HTTP_TOKEN_STOP,
+            T_ESCAPE_LOGITEM,
+-           T_ESCAPE_FORENSIC);
++           T_ESCAPE_FORENSIC,
++           T_HTTP_CTRLS,
++           T_VCHAR_OBSTEXT);
+ 
+     for (c = 0; c < 256; ++c) {
+         flags = 0;
+-        if (c % 20 == 0)
++        if (c % 8 == 0)
+             printf("\n    ");
+ 
+         /* escape_shell_cmd */
+@@ -104,15 +110,36 @@ int main(int argc, char *argv[])
+             flags |= T_ESCAPE_PATH_SEGMENT;
+         }
+ 
+-        if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:@&=/~", c)) {
++        if (!apr_isalnum(c) && !strchr("$-_.+!*'(),:;@&=/~", c)) {
+             flags |= T_OS_ESCAPE_PATH;
+         }
+ 
+-        /* these are the "tspecials" (RFC2068) or "separators" (RFC2616) */
+-        if (c && (apr_iscntrl(c) || strchr(" \t()<>@,;:\\\"/[]?={}", c))) {
++        /* Stop for any non-'token' character, including ctrls, obs-text,
++         * and "tspecials" (RFC2068) a.k.a. "separators" (RFC2616), which
++         * is easer to express as characters remaining in the ASCII token set
++         */
++        if (!c || !(apr_isalnum(c) || strchr("!#$%&'*+-.^_`|~", c))) {
+             flags |= T_HTTP_TOKEN_STOP;
+         }
+ 
++        /* Catch CTRLs other than VCHAR, HT and SP, and obs-text (RFC7230 3.2)
++         * This includes only the C0 plane, not C1 (which is obs-text itself.)
++         * XXX: We should verify that all ASCII C0 ctrls/DEL corresponding to
++         * the current EBCDIC translation are captured, and ASCII C1 ctrls
++         * corresponding are all permitted (as they fall under obs-text rule)
++         */
++        if (!c || (apr_iscntrl(c) && c != '\t')) {
++            flags |= T_HTTP_CTRLS;
++        }
++
++        /* From RFC3986, the specific sets of gen-delims, sub-delims (2.2),
++         * and unreserved (2.3) that are possible somewhere within a URI.
++         * Spec requires all others to be %XX encoded, including obs-text.
++         */
++        if (c && !apr_iscntrl(c) && c != ' ') {
++            flags |= T_VCHAR_OBSTEXT;
++        }
++
+         /* For logging, escape all control characters,
+          * double quotes (because they delimit the request in the log file)
+          * backslashes (because we use backslash for escaping)
+@@ -130,7 +157,7 @@ int main(int argc, char *argv[])
+             flags |= T_ESCAPE_FORENSIC;
+         }
+ 
+-        printf("%u%c", flags, (c < 255) ? ',' : ' ');
++        printf("0x%03x%c", flags, (c < 255) ? ',' : ' ');
+     }
+ 
+     printf("\n};\n");
+--- a/server/util.c
++++ b/server/util.c
+@@ -71,7 +71,7 @@
+  * char in here and get it to work, because if char is signed then it
+  * will first be sign extended.
+  */
+-#define TEST_CHAR(c, f)        (test_char_table[(unsigned)(c)] & (f))
++#define TEST_CHAR(c, f)        (test_char_table[(unsigned char)(c)] & (f))
+ 
+ /* Win32/NetWare/OS2 need to check for both forward and back slashes
+  * in ap_getparents() and ap_escape_url.
+@@ -1390,6 +1390,36 @@ AP_DECLARE(int) ap_find_list_item(apr_po
+     return good;
+ }
+ 
++/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP
++ * (as used in header values, for example, in RFC 7230 section 3.2)
++ * returning the pointer to the first non-HT ASCII ctrl character.
++ */
++AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr)
++{
++    for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ;
++
++    return ptr;
++}
++
++/* Scan a string for HTTP token characters, returning the pointer to
++ * the first non-token character.
++ */
++AP_DECLARE(const char *) ap_scan_http_token(const char *ptr)
++{
++    for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ;
++
++    return ptr;
++}
++
++/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+)
++ * and return a pointer to the first ctrl/space character encountered.
++ */
++AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr)
++{
++    for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ;
++
++    return ptr;
++}
+ 
+ /* Retrieve a token, spacing over it and returning a pointer to
+  * the first non-white byte afterwards.  Note that these tokens
+--- a/server/core.c
++++ b/server/core.c
+@@ -546,6 +546,15 @@ static void *merge_core_server_configs(a
+                            ? virt->merge_trailers
+                            : base->merge_trailers;
+ 
++    if (virt->http09_enable != AP_HTTP09_UNSET)
++        conf->http09_enable = virt->http09_enable;
++
++    if (virt->http_conformance != AP_HTTP_CONFORMANCE_UNSET)
++        conf->http_conformance = virt->http_conformance;
++
++    if (virt->http_methods != AP_HTTP_METHODS_UNSET)
++        conf->http_methods = virt->http_methods;
++
+     return conf;
+ }
+ 
+@@ -3241,6 +3250,57 @@ static const char *add_ct_output_filters
+ 
+     return NULL;
+ }
++
++static const char *set_http_protocol_options(cmd_parms *cmd, void *dummy,
++                                             const char *arg)
++{
++    core_server_config *conf = ap_get_module_config(cmd->server->module_config,
++                                                    &core_module);
++    if (strcasecmp(arg, "allow0.9") == 0)
++        conf->http09_enable |= AP_HTTP09_ENABLE;
++    else if (strcasecmp(arg, "require1.0") == 0)
++        conf->http09_enable |= AP_HTTP09_DISABLE;
++    else if (strcasecmp(arg, "strict") == 0)
++        conf->http_conformance |= AP_HTTP_CONFORMANCE_STRICT;
++    else if (strcasecmp(arg, "unsafe") == 0)
++        conf->http_conformance |= AP_HTTP_CONFORMANCE_UNSAFE;
++    else if (strcasecmp(arg, "registeredmethods") == 0)
++        conf->http_methods |= AP_HTTP_METHODS_REGISTERED;
++    else if (strcasecmp(arg, "lenientmethods") == 0)
++        conf->http_methods |= AP_HTTP_METHODS_LENIENT;
++    else
++        return "HttpProtocolOptions accepts "
++               "'Unsafe' or 'Strict' (default), "
++               "'RegisteredMethods' or 'LenientMethods' (default), and "
++               "'Require1.0' or 'Allow0.9' (default)";
++
++    if ((conf->http09_enable & AP_HTTP09_ENABLE)
++            && (conf->http09_enable & AP_HTTP09_DISABLE))
++        return "HttpProtocolOptions 'Allow0.9' and 'Require1.0'"
++               " are mutually exclusive";
++
++    if ((conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT)
++            && (conf->http_conformance & AP_HTTP_CONFORMANCE_UNSAFE))
++        return "HttpProtocolOptions 'Strict' and 'Unsafe'"
++               " are mutually exclusive";
++
++    if ((conf->http_methods & AP_HTTP_METHODS_REGISTERED)
++            && (conf->http_methods & AP_HTTP_METHODS_LENIENT))
++        return "HttpProtocolOptions 'RegisteredMethods' and 'LenientMethods'"
++               " are mutually exclusive";
++
++    return NULL;
++}
++
++static const char *set_http_method(cmd_parms *cmd, void *conf, const char *arg)
++{
++    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
++    if (err != NULL)
++        return err;
++    ap_method_register(cmd->pool, arg);
++    return NULL;
++}
++
+ /*
+  * Insert filters requested by the AddOutputFilterByType
+  * configuration directive. We cannot add filters based
+@@ -3550,6 +3610,12 @@ AP_INIT_FLAG("Suexec", unixd_set_suexec,
+ #endif
+ AP_INIT_FLAG("MergeTrailers", set_merge_trailers, NULL, RSRC_CONF,
+               "merge request trailers into request headers or not"),
++AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CONF,
++                "'Allow0.9' or 'Require1.0' (default); "
++                "'RegisteredMethods' or 'LenientMethods' (default); "
++                "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules"),
++AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF,
++                "Registers non-standard HTTP methods"),
+ { NULL }
+ };
+ 
+--- a/server/protocol.c
++++ b/server/protocol.c
+@@ -183,12 +183,13 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt
+     return (mtime > now) ? now : mtime;
+ }
+ 
+-/* Min # of bytes to allocate when reading a request line */
+-#define MIN_LINE_ALLOC 80
+-
+ /* Get a line of protocol input, including any continuation lines
+  * caused by MIME folding (or broken clients) if fold != 0, and place it
+  * in the buffer s, of size n bytes, without the ending newline.
++ * 
++ * Pulls from r->proto_input_filters instead of r->input_filters for
++ * stricter protocol adherence and better input filter behavior during
++ * chunked trailer processing (for http).
+  *
+  * If s is NULL, ap_rgetline_core will allocate necessary memory from r->pool.
+  *
+@@ -198,7 +199,7 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt
+  * APR_ENOSPC is returned if there is not enough buffer space.
+  * Other errors may be returned on other errors.
+  *
+- * The LF is *not* returned in the buffer.  Therefore, a *read of 0
++ * The [CR]LF are *not* returned in the buffer.  Therefore, a *read of 0
+  * indicates that an empty line was read.
+  *
+  * Notes: Because the buffer uses 1 char for NUL, the most we can return is
+@@ -209,32 +210,35 @@ AP_DECLARE(apr_time_t) ap_rationalize_mt
+  */
+ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n,
+                                           apr_size_t *read, request_rec *r,
+-                                          int fold, apr_bucket_brigade *bb)
++                                          int flags, apr_bucket_brigade *bb)
+ {
+     apr_status_t rv;
+     apr_bucket *e;
+     apr_size_t bytes_handled = 0, current_alloc = 0;
+     char *pos, *last_char = *s;
+     int do_alloc = (*s == NULL), saw_eos = 0;
++    int fold = flags & AP_GETLINE_FOLD;
++    int crlf = flags & AP_GETLINE_CRLF;
+ 
+     /*
+      * Initialize last_char as otherwise a random value will be compared
+      * against APR_ASCII_LF at the end of the loop if bb only contains
+      * zero-length buckets.
+      */
+-    if (last_char) {
++    if (last_char)
+         *last_char = '\0';
+-    }
+ 
+     for (;;) {
+         apr_brigade_cleanup(bb);
+-        rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE,
++        rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_GETLINE,
+                             APR_BLOCK_READ, 0);
+         if (rv != APR_SUCCESS) {
+             return rv;
+         }
+ 
+-        /* Something horribly wrong happened.  Someone didn't block! */
++        /* Something horribly wrong happened.  Someone didn't block! 
++         * (this also happens at the end of each keepalive connection)
++         */
+         if (APR_BRIGADE_EMPTY(bb)) {
+             return APR_EGENERAL;
+         }
+@@ -285,9 +289,6 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
+                 /* We'll assume the common case where one bucket is enough. */
+                 if (!*s) {
+                     current_alloc = len;
+-                    if (current_alloc < MIN_LINE_ALLOC) {
+-                        current_alloc = MIN_LINE_ALLOC;
+-                    }
+                     *s = apr_palloc(r->pool, current_alloc);
+                 }
+                 else if (bytes_handled + len > current_alloc) {
+@@ -323,6 +324,13 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
+         }
+     }
+ 
++    if (crlf && (last_char <= *s || last_char[-1] != APR_ASCII_CR)) {
++        *last_char = '\0';
++        bytes_handled = last_char - *s;
++        *read = bytes_handled;
++        return APR_EINVAL;
++    }
++
+     /* Now NUL-terminate the string at the end of the line;
+      * if the last-but-one character is a CR, terminate there */
+     if (last_char > *s && last_char[-1] == APR_ASCII_CR) {
+@@ -345,7 +353,7 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
+             apr_brigade_cleanup(bb);
+ 
+             /* We only care about the first byte. */
+-            rv = ap_get_brigade(r->input_filters, bb, AP_MODE_SPECULATIVE,
++            rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_SPECULATIVE,
+                                 APR_BLOCK_READ, 1);
+             if (rv != APR_SUCCESS) {
+                 return rv;
+@@ -396,7 +404,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_cor
+                      */
+                     if (do_alloc) {
+                         tmp = NULL;
+-                    } else {
++                    }
++                    else {
+                         /* We're null terminated. */
+                         tmp = last_char;
+                     }
+@@ -461,7 +470,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(cha
+ }
+ #endif
+ 
+-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold)
++AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags)
+ {
+     char *tmp_s = s;
+     apr_status_t rv;
+@@ -469,7 +478,7 @@ AP_DECLARE(int) ap_getline(char *s, int
+     apr_bucket_brigade *tmp_bb;
+ 
+     tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+-    rv = ap_rgetline(&tmp_s, n, &len, r, fold, tmp_bb);
++    rv = ap_rgetline(&tmp_s, n, &len, r, flags, tmp_bb);
+     apr_brigade_destroy(tmp_bb);
+ 
+     /* Map the out-of-space condition to the old API. */
+@@ -552,25 +561,36 @@ AP_CORE_DECLARE(void) ap_parse_uri(reque
+     }
+ }
+ 
+-static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
++/* get the length of the field name for logging, but no more than 80 bytes */
++#define LOG_NAME_MAX_LEN 80
++static int field_name_len(const char *field)
+ {
+-    const char *ll;
+-    const char *uri;
+-    const char *pro;
++    const char *end = ap_strchr_c(field, ':');
++    if (end == NULL || end - field > LOG_NAME_MAX_LEN)
++        return LOG_NAME_MAX_LEN;
++    return end - field;
++}
+ 
+-#if 0
+-    conn_rec *conn = r->connection;
+-#endif
+-    int major = 1, minor = 0;   /* Assume HTTP/1.0 if non-"HTTP" protocol */
+-    char http[5];
++static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
++{
++    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;
++    char *ll;
++    char *uri;
+     apr_size_t len;
++    core_server_config *conf =
++        (core_server_config *)ap_get_module_config(r->server->module_config,
++                                                   &core_module);
++    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+     int num_blank_lines = 0;
+     int max_blank_lines = r->server->limit_req_fields;
+ 
+     if (max_blank_lines <= 0) {
+         max_blank_lines = DEFAULT_LIMIT_REQUEST_FIELDS;
+     }
+-
+     /* Read past empty lines until we get a real request line,
+      * a read error, the connection closes (EOF), or we timeout.
+      *
+@@ -594,7 +614,7 @@ static int read_request_line(request_rec
+          */
+         r->the_request = NULL;
+         rv = ap_rgetline(&(r->the_request), (apr_size_t)(r->server->limit_req_line + 2),
+-                         &len, r, 0, bb);
++                         &len, r, strict ? AP_GETLINE_CRLF : 0, bb);
+ 
+         if (rv != APR_SUCCESS) {
+             r->request_time = apr_time_now();
+@@ -604,7 +624,7 @@ static int read_request_line(request_rec
+              * happen if it exceeds the configured limit for a request-line.
+              */
+             if (rv == APR_ENOSPC) {
+-                r->status    = HTTP_REQUEST_URI_TOO_LARGE;
++                r->status = HTTP_REQUEST_URI_TOO_LARGE;
+                 r->proto_num = HTTP_VERSION(1,0);
+                 r->protocol  = apr_pstrdup(r->pool, "HTTP/1.0");
+             }
+@@ -618,21 +638,155 @@ static int read_request_line(request_rec
+     /* we've probably got something to do, ignore graceful restart requests */
+ 
+     r->request_time = apr_time_now();
+-    ll = r->the_request;
+-    r->method = ap_getword_white(r->pool, &ll);
++    r->method = r->the_request;
+ 
+-#if 0
+-/* XXX If we want to keep track of the Method, the protocol module should do
+- * it.  That support isn't in the scoreboard yet.  Hopefully next week
+- * sometime.   rbb */
+-    ap_update_connection_status(AP_CHILD_THREAD_FROM_ID(conn->id), "Method",
+-                                r->method);
+-#endif
++    /* 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 = "";
++        len = 0;
++        goto rrl_done;
++    }
++    else if (strict && ll[0] && apr_isspace(ll[1])
++             && deferred_error == rrl_none) {
++        deferred_error = rrl_excesswhitespace; 
++    }
+ 
+-    uri = ap_getword_white(r->pool, &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 != ' ' && deferred_error == rrl_none)
++            deferred_error = rrl_badwhitespace; 
++    *ll = '\0';
++
++    if (!*uri && deferred_error == rrl_none)
++        deferred_error = rrl_missinguri;
+ 
+-    /* Provide quick information about the request method as soon as known */
++    /* 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 = "";
++        len = 0;
++        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 integrety 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  = apr_pstrdup(r->pool, "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 subsitution
++     */
+     r->method_number = ap_method_number_of(r->method);
+     if (r->method_number == M_GET && r->method[0] == 'H') {
+         r->header_only = 1;
+@@ -640,44 +794,123 @@ static int read_request_line(request_rec
+ 
+     ap_parse_uri(r, uri);
+ 
+-    if (ll[0]) {
+-        r->assbackwards = 0;
+-        pro = ll;
+-        len = strlen(ll);
+-    } else {
+-        r->assbackwards = 1;
+-        pro = "HTTP/0.9";
+-        len = 8;
++    /* 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;
+     }
+-    r->protocol = apr_pstrmemdup(r->pool, pro, len);
+ 
+-    /* XXX ap_update_connection_status(conn->id, "Protocol", r->protocol); */
++    /* 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,
++                          "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,
++                          "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,
++                          "HTTP Request Line; Missing URI");
++        else if (deferred_error == rrl_baduri)
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                          "HTTP Request Line; URI incorrectly encoded: '%.*s'",
++                          field_name_len(r->uri), r->uri);
++        else if (deferred_error == rrl_badwhitespace)
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                          "HTTP Request Line; Invalid whitespace");
++        else if (deferred_error == rrl_excesswhitespace)
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                          "HTTP Request Line; Excess whitespace "
++                          "(disallowed by HttpProtocolOptions Strict");
++        else if (deferred_error == rrl_trailingtext)
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                          "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,
++                          "HTTP Request Line; Rejected HTTP/0.9 request");
++        else if (deferred_error == rrl_badprotocol)
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                          "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;
++    }
+ 
+-    /* Avoid sscanf in the common case */
+-    if (len == 8
+-        && pro[0] == 'H' && pro[1] == 'T' && pro[2] == 'T' && pro[3] == 'P'
+-        && pro[4] == '/' && apr_isdigit(pro[5]) && pro[6] == '.'
+-        && apr_isdigit(pro[7])) {
+-        r->proto_num = HTTP_VERSION(pro[5] - '0', pro[7] - '0');
+-    }
+-    else if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor)
+-             && (strcasecmp("http", http) == 0)
+-             && (minor < HTTP_VERSION(1, 0)) ) /* don't allow HTTP/0.1000 */
+-        r->proto_num = HTTP_VERSION(major, minor);
+-    else
+-        r->proto_num = HTTP_VERSION(1, 0);
++    if (conf->http_methods == AP_HTTP_METHODS_REGISTERED
++            && r->method_number == M_INVALID) {
++        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                      "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,
++                      "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,
++                          "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,
++                          "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  = apr_pstrdup(r->pool, "HTTP/1.0");
++    }
++    return 0;
+ }
+ 
+-/* get the length of the field name for logging, but no more than 80 bytes */
+-#define LOG_NAME_MAX_LEN 80
+-static int field_name_len(const char *field)
++static int table_do_fn_check_lengths(void *r_, const char *key,
++                                     const char *value)
+ {
+-    const char *end = ap_strchr_c(field, ':');
+-    if (end == NULL || end - field > LOG_NAME_MAX_LEN)
+-        return LOG_NAME_MAX_LEN;
+-    return end - field;
++    request_rec *r = r_;
++    if (value == NULL || r->server->limit_req_fieldsize >= strlen(value) )
++        return 1;
++
++    r->status = HTTP_BAD_REQUEST;
++    apr_table_setn(r->notes, "error-notes",
++                   "Size of a request header field exceeds server limit.");
++    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, "Request "
++                  "header exceeds LimitRequestFieldSize after merging: %.*s",
++                  field_name_len(key), key);
++    return 0;
+ }
+ 
+ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb)
+@@ -690,6 +923,10 @@ AP_DECLARE(void) ap_get_mime_headers_cor
+     apr_size_t len;
+     int fields_read = 0;
+     char *tmp_field;
++    core_server_config *conf =
++        (core_server_config *)ap_get_module_config(r->server->module_config,
++                                                   &core_module);
++    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ 
+     /*
+      * Read header lines until we get the empty separator line, a read error,
+@@ -697,11 +934,10 @@ AP_DECLARE(void) ap_get_mime_headers_cor
+      */
+     while(1) {
+         apr_status_t rv;
+-        int folded = 0;
+ 
+         field = NULL;
+         rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2,
+-                         &len, r, 0, bb);
++                         &len, r, strict ? AP_GETLINE_CRLF : 0, bb);
+ 
+         if (rv != APR_SUCCESS) {
+             if (APR_STATUS_IS_TIMEUP(rv)) {
+@@ -717,145 +953,219 @@ AP_DECLARE(void) ap_get_mime_headers_cor
+              * finding the end-of-line.  This is only going to happen if it
+              * exceeds the configured limit for a field size.
+              */
+-            if (rv == APR_ENOSPC && field) {
+-                /* insure ap_escape_html will terminate correctly */
+-                field[len - 1] = '\0';
++            if (rv == APR_ENOSPC) {
+                 apr_table_setn(r->notes, "error-notes",
+-                               apr_psprintf(r->pool,
+-                                           "Size of a request header field "
+-                                           "exceeds server limit.<br />\n"
+-                                           "<pre>\n%.*s\n</pre>/n",
+-                                           field_name_len(field), 
+-                                           ap_escape_html(r->pool, field)));
+-                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, 
+-                              "Request header exceeds LimitRequestFieldSize: "
+-                              "%.*s", field_name_len(field), field);
+-            }
++                               "Size of a request header field "
++                               "exceeds server limit.");
++                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
++                              "Request header exceeds LimitRequestFieldSize%s"
++                              "%.*s",
++                              (field && *field) ? ": " : "",
++                              (field) ? field_name_len(field) : 0,
++                              (field) ? field : "");            }
+             return;
+         }
+ 
+-        if (last_field != NULL) {
+-            if ((len > 0) && ((*field == '\t') || *field == ' ')) {
+-                /* This line is a continuation of the preceding line(s),
+-                 * so append it to the line that we've set aside.
+-                 * Note: this uses a power-of-two allocator to avoid
+-                 * doing O(n) allocs and using O(n^2) space for
+-                 * continuations that span many many lines.
+-                 */
+-                apr_size_t fold_len = last_len + len + 1; /* trailing null */
++        /* For all header values, and all obs-fold lines, the presence of
++         * additional whitespace is a no-op, so collapse trailing whitespace
++         * to save buffer allocation and optimize copy operations.
++         * Do not remove the last single whitespace under any condition.
++         */
++        while (len > 1 && (field[len-1] == '\t' || field[len-1] == ' ')) {
++            field[--len] = '\0';
++        } 
+ 
+-                if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) {
+-                    r->status = HTTP_BAD_REQUEST;
+-                    /* report what we have accumulated so far before the
+-                     * overflow (last_field) as the field with the problem
+-                     */
+-                    apr_table_setn(r->notes, "error-notes",
+-                                   apr_psprintf(r->pool,
+-                                               "Size of a request header field "
+-                                               "after folding "
+-                                               "exceeds server limit.<br />\n"
+-                                               "<pre>\n%.*s\n</pre>\n",
+-                                               field_name_len(last_field),
+-                                               ap_escape_html(r->pool, last_field)));
+-                    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+-                                  "Request header exceeds LimitRequestFieldSize "
+-                                  "after folding: %.*s",
+-                                  field_name_len(last_field), last_field);
+-                    return;
+-                }
++        if (*field == '\t' || *field == ' ') {
++
++            /* Append any newly-read obs-fold line onto the preceding
++             * last_field line we are processing
++             */
++            apr_size_t fold_len;
++
++            if (last_field == NULL) {
++                r->status = HTTP_BAD_REQUEST;
++                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                              "Line folding encountered before first"
++                              " header line");
++                return;
++            }
++
++            if (field[1] == '\0') {
++                r->status = HTTP_BAD_REQUEST;
++                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                              "Empty folded line encountered");
++                return;
++            }
++
++            /* Leading whitespace on an obs-fold line can be
++             * similarly discarded */
++            while (field[1] == '\t' || field[1] == ' ') {
++                ++field; --len;
++            }
+ 
++            /* This line is a continuation of the preceding line(s),
++             * so append it to the line that we've set aside.
++             * Note: this uses a power-of-two allocator to avoid
++             * doing O(n) allocs and using O(n^2) space for
++             * continuations that span many many lines.
++             */
++            fold_len = last_len + len + 1; /* trailing null */
++
++            if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) {
++                r->status = HTTP_BAD_REQUEST;
++                /* report what we have accumulated so far before the
++                 * overflow (last_field) as the field with the problem
++                 */
++                apr_table_setn(r->notes, "error-notes",
++                               "Size of a request header field "
++                               "exceeds server limit.");
++                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
++                              "Request header exceeds LimitRequestFieldSize "
++                              "after folding: %.*s",
++                              field_name_len(last_field), last_field);
++                return;
++            }
++
++            if (fold_len > alloc_len) {
++                char *fold_buf;
++                alloc_len += alloc_len;
+                 if (fold_len > alloc_len) {
+-                    char *fold_buf;
+-                    alloc_len += alloc_len;
+-                    if (fold_len > alloc_len) {
+-                        alloc_len = fold_len;
+-                    }
+-                    fold_buf = (char *)apr_palloc(r->pool, alloc_len);
+-                    memcpy(fold_buf, last_field, last_len);
+-                    last_field = fold_buf;
++                    alloc_len = fold_len;
+                 }
+-                memcpy(last_field + last_len, field, len +1); /* +1 for nul */
+-                last_len += len;
+-                folded = 1;
++                fold_buf = (char *)apr_palloc(r->pool, alloc_len);
++                memcpy(fold_buf, last_field, last_len);
++                last_field = fold_buf;
+             }
+-            else /* not a continuation line */ {
++            memcpy(last_field + last_len, field, len +1); /* +1 for nul */
++            /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */
++            last_field[last_len] = ' ';
++            last_len += len;
++            /* We've appended this obs-fold line to last_len, proceed to
++             * read the next input line
++             */
++            continue;
++        }
++        else if (last_field != NULL) {
++
++            /* Process the previous last_field header line with all obs-folded
++             * segments already concatinated (this is not operating on the
++             * most recently read input line).
++             */
+ 
+-                if (r->server->limit_req_fields
++            if (r->server->limit_req_fields
+                     && (++fields_read > r->server->limit_req_fields)) {
+-                    r->status = HTTP_BAD_REQUEST;
+-                    apr_table_setn(r->notes, "error-notes",
+-                                   "The number of request header fields "
+-                                   "exceeds this server's limit.");
+-                    return;
+-                }
++              r->status = HTTP_BAD_REQUEST;
++              apr_table_setn(r->notes, "error-notes",
++                             "The number of request header fields "
++                             "exceeds this server's limit.");
++              return;
++            }
++            if (!strict)
++            {
++                /* Not Strict ('Unsafe' mode), using the legacy parser */
+ 
+-                if (!(value = strchr(last_field, ':'))) { /* Find ':' or    */
+-                    r->status = HTTP_BAD_REQUEST;      /* abort bad request */
+-                    apr_table_setn(r->notes, "error-notes",
+-                                   apr_psprintf(r->pool,
+-                                               "Request header field is "
+-                                               "missing ':' separator.<br />\n"
+-                                               "<pre>\n%.*s</pre>\n",
+-                                               (int)LOG_NAME_MAX_LEN,
+-                                               ap_escape_html(r->pool,
+-                                                              last_field)));
++                if (!(value = strchr(last_field, ':'))) { /* Find ':' or */
++                    r->status = HTTP_BAD_REQUEST;   /* abort bad request */
+                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                                   "Request header field is missing ':' "
+                                   "separator: %.*s", (int)LOG_NAME_MAX_LEN,
+                                   last_field);
+-
+                     return;
+                 }
+ 
+-                tmp_field = value - 1; /* last character of field-name */
++                /* last character of field-name */
++                tmp_field = value - (value > last_field ? 1 : 0);
+ 
+                 *value++ = '\0'; /* NUL-terminate at colon */
+ 
++                if (strpbrk(last_field, "\t\n\v\f\r ")) {
++                    r->status = HTTP_BAD_REQUEST;
++                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                                  "Request header field name presented"
++                                  " invalid whitespace");
++                    return;
++                }
++
+                 while (*value == ' ' || *value == '\t') {
+                     ++value;            /* Skip to start of value   */
+                 }
+ 
+-                /* Strip LWS after field-name: */
+-                while (tmp_field > last_field
+-                       && (*tmp_field == ' ' || *tmp_field == '\t')) {
+-                    *tmp_field-- = '\0';
++                if (strpbrk(value, "\n\v\f\r")) {
++                    r->status = HTTP_BAD_REQUEST;
++                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                                  "Request header field value presented"
++                                  " bad whitespace");
++                    return;
++                }
++
++                if (tmp_field == last_field) {
++                    r->status = HTTP_BAD_REQUEST;
++                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                                  "Request header field name was empty");
++                    return;
++                }
++
++            }
++            else /* Using strict RFC7230 parsing */
++            {
++                /* Ensure valid token chars before ':' per RFC 7230 3.2.4 */
++                value = (char *)ap_scan_http_token(last_field);
++                if ((value == last_field) || *value != ':') {
++                    r->status = HTTP_BAD_REQUEST;
++                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                                  "Request header field name is malformed: "
++                                  "%.*s", (int)LOG_NAME_MAX_LEN, last_field);
++                    return;
+                 }
+ 
+-                /* Strip LWS after field-value: */
+-                tmp_field = last_field + last_len - 1;
+-                while (tmp_field > value
+-                       && (*tmp_field == ' ' || *tmp_field == '\t')) {
+-                    *tmp_field-- = '\0';
++                *value++ = '\0'; /* NUL-terminate last_field name at ':' */
++                while (*value == ' ' || *value == '\t') {
++                    ++value;     /* Skip LWS of value */
+                 }
+ 
+-                apr_table_addn(r->headers_in, last_field, value);
++                /* Find invalid, non-HT ctrl char, or the trailing NULL */
++                tmp_field = (char *)ap_scan_http_field_content(value);
+ 
+-                /* reset the alloc_len so that we'll allocate a new
+-                 * buffer if we have to do any more folding: we can't
+-                 * use the previous buffer because its contents are
+-                 * now part of r->headers_in
++                /* Reject value for all garbage input (CTRLs excluding HT)
++                 * e.g. only VCHAR / SP / HT / obs-text are allowed per
++                 * RFC7230 3.2.6 - leave all more explicit rule enforcement
++                 * for specific header handler logic later in the cycle
+                  */
+-                alloc_len = 0;
++                if (*tmp_field != '\0') {
++                    r->status = HTTP_BAD_REQUEST;
++                    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                                  "Request header value is malformed: "
++                                  "%.*s", (int)LOG_NAME_MAX_LEN, value);
++                    return;
++                }
++            }
+ 
+-            } /* end if current line is not a continuation starting with tab */
+-        }
++            apr_table_addn(r->headers_in, last_field, value);
+ 
+-        /* Found a blank line, stop. */
++            /* This last_field header is now stored in headers_in,
++             * resume processing of the current input line.
++             */
++       }
++
++        /* Found the terminating empty end-of-headers line, stop. */
+         if (len == 0) {
+             break;
+         }
+ 
+-        /* Keep track of this line so that we can parse it on
+-         * the next loop iteration.  (In the folded case, last_field
+-         * has been updated already.)
++        /* Keep track of this new header line so that we can extend it across
++         * any obs-fold or parse it on the next loop iteration. We referenced
++         * our previously allocated buffer in r->headers_in,
++         * so allocate a fresh buffer if required.
+          */
+-        if (!folded) {
+-            last_field = field;
+-            last_len = len;
+-        }
++        alloc_len = 0;
++        last_field = field;
++        last_len = len;
+     }
+ 
+     apr_table_compress(r->headers_in, APR_OVERLAP_TABLES_MERGE);
++
++    /* enforce LimitRequestFieldSize for merged headers */
++    apr_table_do(table_do_fn_check_lengths, r, r->headers_in, NULL);
+ }
+ 
+ AP_DECLARE(void) ap_get_mime_headers(request_rec *r)
+@@ -923,26 +1233,39 @@ request_rec *ap_read_request(conn_rec *c
+ 
+     /* Get the request... */
+     if (!read_request_line(r, tmp_bb)) {
+-        if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
+-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+-                          "request failed: URI too long (longer than %d)", r->server->limit_req_line);
+-            ap_send_error_response(r, 0);
++        switch (r->status) {
++        case HTTP_REQUEST_URI_TOO_LARGE:
++        case HTTP_BAD_REQUEST:
++        case HTTP_VERSION_NOT_SUPPORTED:
++        case HTTP_NOT_IMPLEMENTED:
++            if (r->status == HTTP_REQUEST_URI_TOO_LARGE) {
++                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
++                              "request failed: client's request-line exceeds LimitRequestLine (longer than %d)",
++                              r->server->limit_req_line);
++            }
++            else if (r->method == NULL) {
++                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                              "request failed: malformed request line");
++            }
++            access_status = r->status;
++            r->status = HTTP_OK;
++            ap_die(access_status, r);
+             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+             ap_run_log_transaction(r);
++            r = NULL;
+             apr_brigade_destroy(tmp_bb);
+             return r;
+-        }
+-        else if (r->status == HTTP_REQUEST_TIME_OUT) {
++        case HTTP_REQUEST_TIME_OUT:
+             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+-            if (!r->connection->keepalives) {
++            if (!r->connection->keepalives)
+                 ap_run_log_transaction(r);
+-            }
+             apr_brigade_destroy(tmp_bb);
+             return r;
++        default:
++            apr_brigade_destroy(tmp_bb);
++            r = NULL;
++            return r;
+         }
+-
+-        apr_brigade_destroy(tmp_bb);
+-        return NULL;
+     }
+ 
+     /* We may have been in keep_alive_timeout mode, so toggle back
+@@ -959,7 +1282,7 @@ request_rec *ap_read_request(conn_rec *c
+     if (!r->assbackwards) {
+         ap_get_mime_headers_core(r, tmp_bb);
+         if (r->status != HTTP_OK) {
+-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                           "request failed: error reading the headers");
+             ap_send_error_response(r, 0);
+             ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+@@ -977,25 +1300,6 @@ request_rec *ap_read_request(conn_rec *c
+             apr_table_unset(r->headers_in, "Content-Length");
+         }
+     }
+-    else {
+-        if (r->header_only) {
+-            /*
+-             * Client asked for headers only with HTTP/0.9, which doesn't send
+-             * headers! Have to dink things just to make sure the error message
+-             * comes through...
+-             */
+-            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+-                          "client sent invalid HTTP/0.9 request: HEAD %s",
+-                          r->uri);
+-            r->header_only = 0;
+-            r->status = HTTP_BAD_REQUEST;
+-            ap_send_error_response(r, 0);
+-            ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+-            ap_run_log_transaction(r);
+-            apr_brigade_destroy(tmp_bb);
+-            return r;
+-        }
+-    }
+ 
+     apr_brigade_destroy(tmp_bb);
+ 
+@@ -1003,6 +1307,7 @@ request_rec *ap_read_request(conn_rec *c
+      * now read. may update status.
+      */
+     ap_update_vhost_from_headers(r);
++    access_status = r->status;
+ 
+     /* Toggle to the Host:-based vhost's timeout mode to fetch the
+      * request body and send the response body, if needed.
+@@ -1025,8 +1330,8 @@ request_rec *ap_read_request(conn_rec *c
+          * HTTP/1.1 mentions twice (S9, S14.23) that a request MUST contain
+          * a Host: header, and the server MUST respond with 400 if it doesn't.
+          */
+-        r->status = HTTP_BAD_REQUEST;
+-        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
++        access_status = HTTP_BAD_REQUEST;
++        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                       "client sent HTTP/1.1 request without hostname "
+                       "(see RFC2616 section 14.23): %s", r->uri);
+     }
+@@ -1041,14 +1346,8 @@ request_rec *ap_read_request(conn_rec *c
+     ap_add_input_filter_handle(ap_http_input_filter_handle,
+                                NULL, r, r->connection);
+ 
+-    if (r->status != HTTP_OK) {
+-        ap_send_error_response(r, 0);
+-        ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+-        ap_run_log_transaction(r);
+-        return r;
+-    }
+-
+-    if ((access_status = ap_run_post_read_request(r))) {
++    if (access_status != HTTP_OK
++        || (access_status = ap_run_post_read_request(r))) {
+         ap_die(access_status, r);
+         ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+         ap_run_log_transaction(r);
+@@ -1248,7 +1547,7 @@ AP_DECLARE(int) ap_get_basic_auth_pw(req
+ 
+     if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+         /* Client tried to authenticate using wrong auth scheme */
+-        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
++        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+                       "client used wrong authentication scheme: %s", r->uri);
+         ap_note_basic_auth_failure(r);
+         return HTTP_UNAUTHORIZED;
+--- a/server/vhost.c
++++ b/server/vhost.c
+@@ -687,6 +687,113 @@ AP_DECLARE(void) ap_fini_vhost_config(ap
+  * run-time vhost matching functions
+  */
+ 
++static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host)
++{
++    char *dst;
++    int double_colon = 0;
++
++    for (dst = host; *dst; dst++) {
++        if (apr_isxdigit(*dst)) {
++            if (apr_isupper(*dst)) {
++                *dst = apr_tolower(*dst);
++            }
++        }
++        else if (*dst == ':') {
++            if (*(dst + 1) == ':') {
++                if (double_colon)
++                    return APR_EINVAL;
++                double_colon = 1;
++            }
++            else if (*(dst + 1) == '.') {
++                return APR_EINVAL;
++            }
++        }
++        else if (*dst == '.') {
++            /* For IPv4-mapped IPv6 addresses like ::FFFF:129.144.52.38 */
++            if (*(dst + 1) == ':' || *(dst + 1) == '.')
++                return APR_EINVAL;
++        }
++        else {
++            return APR_EINVAL;
++        }
++    }
++    return APR_SUCCESS;
++}
++
++static apr_status_t fix_hostname_non_v6(request_rec *r, char *host)
++{
++    char *dst;
++
++    for (dst = host; *dst; dst++) {
++        if (apr_islower(*dst)) {
++            /* leave char unchanged */
++        }
++        else if (*dst == '.') {
++            if (*(dst + 1) == '.') {
++                return APR_EINVAL;
++            }
++        }
++        else if (apr_isupper(*dst)) {
++            *dst = apr_tolower(*dst);
++        }
++        else if (*dst == '/' || *dst == '\\') {
++            return APR_EINVAL;
++        }
++    }
++    /* strip trailing gubbins */
++    if (dst > host && dst[-1] == '.') {
++        dst[-1] = '\0';
++    }
++    return APR_SUCCESS;
++}
++
++/*
++ * If strict mode ever becomes the default, this should be folded into
++ * fix_hostname_non_v6()
++ */
++static apr_status_t strict_hostname_check(request_rec *r, char *host)
++{
++    char *ch;
++    int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0;
++
++    for (ch = host; *ch; ch++) {
++        if (apr_isalpha(*ch) || *ch == '-') {
++            is_dotted_decimal = 0;
++        }
++        else if (ch[0] == '.') {
++            dots++;
++            if (ch[1] == '0' && apr_isdigit(ch[2]))
++                leading_zeroes = 1;
++        }
++        else if (!apr_isdigit(*ch)) {
++           /* also takes care of multiple Host headers by denying commas */
++            goto bad;
++        }
++    }
++    if (is_dotted_decimal) {
++        if (host[0] == '.' || (host[0] == '0' && apr_isdigit(host[1])))
++            leading_zeroes = 1;
++        if (leading_zeroes || dots != 3) {
++            /* RFC 3986 7.4 */
++            goto bad;
++        }
++    }
++    else {
++        /* The top-level domain must start with a letter (RFC 1123 2.1) */
++        while (ch > host && *ch != '.')
++            ch--;
++        if (ch[0] == '.' && ch[1] != '\0' && !apr_isalpha(ch[1]))
++            goto bad;
++    }
++    return APR_SUCCESS;
++
++bad:
++    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                  "[strict] Invalid host name '%s'%s%.6s",
++                  host, *ch ? ", problem near: " : "", ch);
++    return APR_EINVAL;
++}
++
+ /* Lowercase and remove any trailing dot and/or :port from the hostname,
+  * and check that it is sane.
+  *
+@@ -700,78 +807,90 @@ AP_DECLARE(void) ap_fini_vhost_config(ap
+  * Instead we just check for filesystem metacharacters: directory
+  * separators / and \ and sequences of more than one dot.
+  */
+-static void fix_hostname(request_rec *r)
++static int fix_hostname(request_rec *r, const char *host_header,
++                        unsigned http_conformance)
+ {
++    const char *src;
+     char *host, *scope_id;
+-    char *dst;
+     apr_port_t port;
+     apr_status_t rv;
+     const char *c;
++    int is_v6literal = 0;
++    int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+ 
+-    /* According to RFC 2616, Host header field CAN be blank. */
+-    if (!*r->hostname) {
+-        return;
++    src = host_header ? host_header : r->hostname;
++
++    /* According to RFC 2616, Host header field CAN be blank */
++    if (!*src) {
++        return is_v6literal;
+     }
+ 
+     /* apr_parse_addr_port will interpret a bare integer as a port
+      * which is incorrect in this context.  So treat it separately.
+      */
+-    for (c = r->hostname; apr_isdigit(*c); ++c);
+-    if (!*c) {  /* pure integer */
+-        return;
+-    }
+-
+-    rv = apr_parse_addr_port(&host, &scope_id, &port, r->hostname, r->pool);
+-    if (rv != APR_SUCCESS || scope_id) {
+-        goto bad;
++    for (c = src; apr_isdigit(*c); ++c);
++    if (!*c) {
++        /* pure integer */
++        if (strict) {
++            /* RFC 3986 7.4 */
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                         "[strict] purely numeric host names not allowed: %s",
++                         src);
++            goto bad_nolog;
++        }
++        r->hostname = src;
++        return is_v6literal;
++    }
++
++    if (host_header) {
++        rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool);
++        if (rv != APR_SUCCESS || scope_id)
++            goto bad;
++        if (port) {
++            /* Don't throw the Host: header's port number away:
++               save it in parsed_uri -- ap_get_server_port() needs it! */
++            /* @@@ XXX there should be a better way to pass the port.
++             *         Like r->hostname, there should be a r->portno
++             */
++            r->parsed_uri.port = port;
++            r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
++        }
++        if (host_header[0] == '[')
++            is_v6literal = 1;
+     }
+-
+-    if (port) {
+-        /* Don't throw the Host: header's port number away:
+-           save it in parsed_uri -- ap_get_server_port() needs it! */
+-        /* @@@ XXX there should be a better way to pass the port.
+-         *         Like r->hostname, there should be a r->portno
++    else {
++        /*
++         * Already parsed, surrounding [ ] (if IPv6 literal) and :port have
++         * already been removed.
+          */
+-        r->parsed_uri.port = port;
+-        r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
++        host = apr_pstrdup(r->pool, r->hostname);
++        if (ap_strchr(host, ':') != NULL)
++            is_v6literal = 1;
+     }
+ 
+-    /* if the hostname is an IPv6 numeric address string, it was validated
+-     * already; otherwise, further validation is needed
+-     */
+-    if (r->hostname[0] != '[') {
+-        for (dst = host; *dst; dst++) {
+-            if (apr_islower(*dst)) {
+-                /* leave char unchanged */
+-            }
+-            else if (*dst == '.') {
+-                if (*(dst + 1) == '.') {
+-                    goto bad;
+-                }
+-            }
+-            else if (apr_isupper(*dst)) {
+-                *dst = apr_tolower(*dst);
+-            }
+-            else if (*dst == '/' || *dst == '\\') {
+-                goto bad;
+-            }
+-        }
+-        /* strip trailing gubbins */
+-        if (dst > host && dst[-1] == '.') {
+-            dst[-1] = '\0';
+-        }
++    if (is_v6literal) {
++        rv = fix_hostname_v6_literal(r, host);
++    }
++    else {
++        rv = fix_hostname_non_v6(r, host);
++        if (strict && rv == APR_SUCCESS)
++            rv = strict_hostname_check(r, host);
+     }
++    if (rv != APR_SUCCESS)
++        goto bad;
++
+     r->hostname = host;
+-    return;
++    return is_v6literal;
+ 
+ bad:
++    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                  "Client sent malformed Host header: %s",
++                  src);
++bad_nolog:
+     r->status = HTTP_BAD_REQUEST;
+-    ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
+-                  "Client sent malformed Host header");
+-    return;
++    return is_v6literal;
+ }
+ 
+-
+ /* return 1 if host matches ServerName or ServerAliases */
+ static int matches_aliases(server_rec *s, const char *host)
+ {
+@@ -968,15 +1087,78 @@ static void check_serverpath(request_rec
+     }
+ }
+ 
++static APR_INLINE const char *construct_host_header(request_rec *r,
++                                                    int is_v6literal)
++{
++    struct iovec iov[5];
++    apr_size_t nvec = 0;
++    /*
++     * We cannot use ap_get_server_name/port here, because we must
++     * ignore UseCanonicalName/Port.
++     */
++    if (is_v6literal) {
++        iov[nvec].iov_base = "[";
++        iov[nvec].iov_len = 1;
++        nvec++;
++    }
++    iov[nvec].iov_base = (void *)r->hostname;
++    iov[nvec].iov_len = strlen(r->hostname);
++    nvec++;
++    if (is_v6literal) {
++        iov[nvec].iov_base = "]";
++        iov[nvec].iov_len = 1;
++        nvec++;
++    }
++    if (r->parsed_uri.port_str) {
++        iov[nvec].iov_base = ":";
++        iov[nvec].iov_len = 1;
++        nvec++;
++        iov[nvec].iov_base = r->parsed_uri.port_str;
++        iov[nvec].iov_len = strlen(r->parsed_uri.port_str);
++        nvec++;
++    }
++    return apr_pstrcatv(r->pool, iov, nvec, NULL);
++}
+ 
+ AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r)
+ {
+-    /* must set this for HTTP/1.1 support */
+-    if (r->hostname || (r->hostname = apr_table_get(r->headers_in, "Host"))) {
+-        fix_hostname(r);
+-        if (r->status != HTTP_OK)
+-            return;
++    core_server_config *conf =
++        (core_server_config *)ap_get_module_config(r->server->module_config,
++                                                   &core_module);
++    const char *host_header = apr_table_get(r->headers_in, "Host");
++    int is_v6literal = 0;
++    int have_hostname_from_url = 0;
++
++    if (r->hostname) {
++        /*
++         * If there was a host part in the Request-URI, ignore the 'Host'
++         * header.
++         */
++        have_hostname_from_url = 1;
++        is_v6literal = fix_hostname(r, NULL, conf->http_conformance);
++    }
++    else if (host_header != NULL) {
++        is_v6literal = fix_hostname(r, host_header, conf->http_conformance);
++    }
++    if (r->status != HTTP_OK)
++        return;
++
++    if (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE) {
++        /*
++         * If we have both hostname from an absoluteURI and a Host header,
++         * we must ignore the Host header (RFC 2616 5.2).
++         * To enforce this, we reset the Host header to the value from the
++         * request line.
++         */
++        if (have_hostname_from_url && host_header != NULL) {
++            const char *repl = construct_host_header(r, is_v6literal);
++            apr_table_set(r->headers_in, "Host", repl);
++            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
++                          "Replacing host header '%s' with host '%s' given "
++                          "in the request uri", host_header, repl);
++        }
+     }
++
+     /* check if we tucked away a name_chain */
+     if (r->connection->vhost_lookup_data) {
+         if (r->hostname)
+--- a/include/http_core.h
++++ b/include/http_core.h
+@@ -618,6 +618,21 @@ typedef struct {
+ #define AP_MERGE_TRAILERS_DISABLE  2
+     int merge_trailers;
+ 
++#define AP_HTTP09_UNSET   0
++#define AP_HTTP09_ENABLE  1
++#define AP_HTTP09_DISABLE 2
++    char http09_enable;
++
++#define AP_HTTP_CONFORMANCE_UNSET     0
++#define AP_HTTP_CONFORMANCE_UNSAFE    1
++#define AP_HTTP_CONFORMANCE_STRICT    2
++    char http_conformance;
++
++#define AP_HTTP_METHODS_UNSET         0
++#define AP_HTTP_METHODS_LENIENT       1
++#define AP_HTTP_METHODS_REGISTERED    2
++    char http_methods;
++
+ } core_server_config;
+ 
+ /* for AddOutputFiltersByType in core.c */
+--- a/include/http_protocol.h
++++ b/include/http_protocol.h
+@@ -493,17 +493,22 @@ AP_DECLARE(int) ap_get_basic_auth_pw(req
+  */
+ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri);
+ 
++#define AP_GETLINE_FOLD 1 /* Whether to merge continuation lines */
++#define AP_GETLINE_CRLF 2 /*Whether line ends must be in the form CR LF */
++
+ /**
+  * Get the next line of input for the request
+  * @param s The buffer into which to read the line
+  * @param n The size of the buffer
+  * @param r The request
+- * @param fold Whether to merge continuation lines
++ * @param flags Bit flag of multiple parsing options
++ *              AP_GETLINE_FOLD Whether to merge continuation lines
++ *              AP_GETLINE_CRLF Whether line ends must be in the form CR LF
+  * @return The length of the line, if successful
+  *         n, if the line is too big to fit in the buffer
+  *         -1 for miscellaneous errors
+  */
+-AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int fold);
++AP_DECLARE(int) ap_getline(char *s, int n, request_rec *r, int flags);
+ 
+ /**
+  * Get the next line of input for the request
+@@ -521,7 +526,9 @@ AP_DECLARE(int) ap_getline(char *s, int
+  * @param n The size of the buffer
+  * @param read The length of the line.
+  * @param r The request
+- * @param fold Whether to merge continuation lines
++ * @param flags Bit flag of multiple parsing options
++ *              AP_GETLINE_FOLD Whether to merge continuation lines
++ *              AP_GETLINE_CRLF Whether line ends must be in the form CR LF
+  * @param bb Working brigade to use when reading buckets
+  * @return APR_SUCCESS, if successful
+  *         APR_ENOSPC, if the line is too big to fit in the buffer
+@@ -530,7 +537,7 @@ AP_DECLARE(int) ap_getline(char *s, int
+ #if APR_CHARSET_EBCDIC
+ AP_DECLARE(apr_status_t) ap_rgetline(char **s, apr_size_t n, 
+                                      apr_size_t *read,
+-                                     request_rec *r, int fold,
++                                     request_rec *r, int flags,
+                                      apr_bucket_brigade *bb);
+ #else /* ASCII box */
+ #define ap_rgetline(s, n, read, r, fold, bb) \
+@@ -540,7 +547,7 @@ AP_DECLARE(apr_status_t) ap_rgetline(cha
+ /** @see ap_rgetline */
+ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n, 
+                                           apr_size_t *read,
+-                                          request_rec *r, int fold,
++                                          request_rec *r, int flags,
+                                           apr_bucket_brigade *bb);
+ 
+ /**
+--- a/include/ap_mmn.h
++++ b/include/ap_mmn.h
+@@ -155,11 +155,18 @@
+ #ifndef MODULE_MAGIC_NUMBER_MAJOR
+ #define MODULE_MAGIC_NUMBER_MAJOR 20051115
+ #endif
+-#define MODULE_MAGIC_NUMBER_MINOR 30                    /* 0...n */
++#define MODULE_MAGIC_NUMBER_MINOR 31                    /* 0...n */
+ 
+ /**
+  * Determine if the server's current MODULE_MAGIC_NUMBER is at least a
+  * specified value.
++ * 20051115.42 (2.2.32) Add http09_enable, http_conformance, and
++ *                      http_methods to core_server_config
++ *                      Add ap_scan_http_field_token(),
++ *                      ap_scan_http_field_content(),
++ *                      and ap_scan_vchar_obstext()
++ *                      Replaced fold boolean with with multiple bit flags
++ *                      to ap_[r]getline()
+  * <pre>
+  * Useful for testing for features.
+  * For example, suppose you wish to use the apr_table_overlap
+--- a/include/httpd.h
++++ b/include/httpd.h
+@@ -1410,6 +1410,28 @@ AP_DECLARE(char *) ap_get_list_item(apr_
+  */
+ AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, const char *tok);
+ 
++/* Scan a string for field content chars, as defined by RFC7230 section 3.2
++ * including VCHAR/obs-text, as well as HT and SP
++ * @param ptr The string to scan
++ * @return A pointer to the first (non-HT) ASCII ctrl character.
++ * @note lws and trailing whitespace are scanned, the caller is responsible
++ * for trimming leading and trailing whitespace
++ */
++AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr);
++
++/* Scan a string for token characters, as defined by RFC7230 section 3.2.6 
++ * @param ptr The string to scan
++ * @return A pointer to the first non-token character.
++ */
++AP_DECLARE(const char *) ap_scan_http_token(const char *ptr);
++
++/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+)
++ * and return a pointer to the first SP/CTL/NUL character encountered.
++ * @param ptr The string to scan
++ * @return A pointer to the first SP/CTL character.
++ */
++AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr);
++
+ /**
+  * Retrieve a token, spacing over it and adjusting the pointer to
+  * the first non-white byte afterwards.  Note that these tokens
+--- a/modules/http/http_filters.c
++++ b/modules/http/http_filters.c
+@@ -108,14 +108,15 @@ static apr_status_t bail_out_on_error(ht
+ 
+ /**
+  * Parse a chunk line with optional extension, detect overflow.
+- * There are two error cases:
+- *  1) If the conversion would require too many bits, APR_EGENERAL is returned.
+- *  2) If the conversion used the correct number of bits, but an overflow
++ * There are several error cases:
++ *  1) If the chunk link is misformatted, APR_EINVAL is returned.
++ *  2) If the conversion would require too many bits, APR_EGENERAL is returned.
++ *  3) If the conversion used the correct number of bits, but an overflow
+  *     caused only the sign bit to flip, then APR_ENOSPC is returned.
+- * In general, any negative number can be considered an overflow error.
++ * A negative chunk length always indicates an overflow error.
+  */
+ static apr_status_t parse_chunk_size(http_ctx_t *ctx, const char *buffer,
+-                                     apr_size_t len, int linelimit)
++                                     apr_size_t len, int linelimit, int strict)
+ {
+     apr_size_t i = 0;
+ 
+@@ -128,6 +129,12 @@ static apr_status_t parse_chunk_size(htt
+         if (ctx->state == BODY_CHUNK_END
+                 || ctx->state == BODY_CHUNK_END_LF) {
+             if (c == LF) {
++                if (strict && (ctx->state != BODY_CHUNK_END_LF)) {
++                    /*
++                     * CR missing before LF.
++                     */
++                    return APR_EINVAL;
++                }
+                 ctx->state = BODY_CHUNK;
+             }
+             else if (c == CR && ctx->state == BODY_CHUNK_END) {
+@@ -135,7 +142,7 @@ static apr_status_t parse_chunk_size(htt
+             }
+             else {
+                 /*
+-                 * LF expected.
++                 * CRLF expected.
+                  */
+                 return APR_EINVAL;
+             }
+@@ -162,6 +169,12 @@ static apr_status_t parse_chunk_size(htt
+         }
+ 
+         if (c == LF) {
++            if (strict && (ctx->state != BODY_CHUNK_LF)) {
++                /*
++                 * CR missing before LF.
++                 */
++                return APR_EINVAL;
++            }
+             if (ctx->remaining) {
+                 ctx->state = BODY_CHUNK_DATA;
+             }
+@@ -183,14 +196,17 @@ static apr_status_t parse_chunk_size(htt
+         }
+         else if (ctx->state == BODY_CHUNK_EXT) {
+             /*
+-             * Control chars (but tabs) are invalid.
++             * Control chars (excluding tabs) are invalid.
++             * TODO: more precisely limit input
+              */
+             if (c != '\t' && apr_iscntrl(c)) {
+                 return APR_EINVAL;
+             }
+         }
+         else if (c == ' ' || c == '\t') {
+-            /* Be lenient up to 10 BWS (term from rfc7230 - 3.2.3).
++            /* Be lenient up to 10 implied *LWS, a legacy of RFC 2616,
++             * and noted as errata to RFC7230;
++             * https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4667
+              */
+             ctx->state = BODY_CHUNK_CR;
+             if (++ctx->chunk_bws > 10) {
+@@ -306,7 +322,10 @@ apr_status_t ap_http_filter(ap_filter_t
+                             ap_input_mode_t mode, apr_read_type_e block,
+                             apr_off_t readbytes)
+ {
+-    core_server_config *conf;
++    core_server_config *conf =
++        (core_server_config *)ap_get_module_config(f->r->server->module_config,
++                                                   &core_module);
++    int strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+     apr_bucket *e;
+     http_ctx_t *ctx = f->ctx;
+     apr_status_t rv;
+@@ -314,9 +333,6 @@ apr_status_t ap_http_filter(ap_filter_t
+     apr_bucket_brigade *bb;
+     int again;
+ 
+-    conf = (core_server_config *)
+-        ap_get_module_config(f->r->server->module_config, &core_module);
+-
+     /* just get out of the way of things we don't want. */
+     if (mode != AP_MODE_READBYTES && mode != AP_MODE_GETLINE) {
+         return ap_get_brigade(f->next, b, mode, block, readbytes);
+@@ -497,7 +513,7 @@ apr_status_t ap_http_filter(ap_filter_t
+                     if (rv == APR_SUCCESS) {
+                         parsing = 1;
+                         rv = parse_chunk_size(ctx, buffer, len,
+-                                f->r->server->limit_req_fieldsize);
++                                f->r->server->limit_req_fieldsize, strict);
+                     }
+                     if (rv != APR_SUCCESS) {
+                         ap_log_rerror(APLOG_MARK, APLOG_INFO, rv, f->r,
+@@ -639,14 +655,122 @@ apr_status_t ap_http_filter(ap_filter_t
+     return APR_SUCCESS;
+ }
+ 
++struct check_header_ctx {
++    request_rec *r;
++    int strict;
++};
++
++/* check a single header, to be used with apr_table_do() */
++static int check_header(struct check_header_ctx *ctx,
++                        const char *name, const char **val)
++{
++    const char *pos, *end;
++    char *dst = NULL;
++
++    if (name[0] == '\0') {
++        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r,
++                      "Empty response header name, aborting request");
++        return 0;
++    }
++
++    if (ctx->strict) { 
++        end = ap_scan_http_token(name);
++    }
++    else {
++        end = ap_scan_vchar_obstext(name);
++    }
++    if (*end) {
++        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r,
++                      "Response header name '%s' contains invalid "
++                      "characters, aborting request",
++                      name);
++        return 0;
++    }
++
++    for (pos = *val; *pos; pos = end) {
++        end = ap_scan_http_field_content(pos);
++        if (*end) {
++            if (end[0] != CR || end[1] != LF || (end[2] != ' ' &&
++                                                 end[2] != '\t')) {
++                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, ctx->r,
++                              "Response header '%s' value of '%s' contains "
++                              "invalid characters, aborting request",
++                              name, pos);
++                return 0;
++            }
++            if (!dst) {
++                *val = dst = apr_palloc(ctx->r->pool, strlen(*val) + 1);
++            }
++        }
++        if (dst) {
++            memcpy(dst, pos, end - pos);
++            dst += end - pos;
++            if (*end) {
++                /* skip folding and replace with a single space */
++                end += 3 + strspn(end + 3, "\t ");
++                *dst++ = ' ';
++            }
++        }
++    }
++    if (dst) {
++        *dst = '\0';
++    }
++    return 1;
++}
++
++static int check_headers_table(apr_table_t *t, struct check_header_ctx *ctx)
++{
++    const apr_array_header_t *headers = apr_table_elts(t);
++    apr_table_entry_t *header;
++    int i;
++
++    for (i = 0; i < headers->nelts; ++i) {
++        header = &((apr_table_entry_t *)headers->elts)[i];
++        if (!header->key) {
++            continue;
++        }
++        if (!check_header(ctx, header->key, (const char **)&header->val)) {
++            return 0;
++        }
++    }
++    return 1;
++}
++
++/**
++ * Check headers for HTTP conformance
++ * @return 1 if ok, 0 if bad
++ */
++static APR_INLINE int check_headers(request_rec *r)
++{
++    struct check_header_ctx ctx;
++    core_server_config *conf =
++        (core_server_config *)ap_get_module_config(r->server->module_config,
++                                                   &core_module);
++
++    ctx.r = r;
++    ctx.strict = (conf->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
++    return check_headers_table(r->headers_out, &ctx) &&
++           check_headers_table(r->err_headers_out, &ctx);
++}
++
++static int check_headers_recursion(request_rec *r)
++{
++    void *check = NULL;
++    apr_pool_userdata_get(&check, "check_headers_recursion", r->pool);
++    if (check) {
++        return 1;
++    }
++    apr_pool_userdata_setn("true", "check_headers_recursion", NULL, r->pool);
++    return 0;
++}
++
+ typedef struct header_struct {
+     apr_pool_t *pool;
+     apr_bucket_brigade *bb;
+ } header_struct;
+ 
+ /* Send a single HTTP header field to the client.  Note that this function
+- * is used in calls to table_do(), so their interfaces are co-dependent.
+- * In other words, don't change this one without checking table_do in alloc.c.
++ * is used in calls to apr_table_do(), so don't change its interface.
+  * It returns true unless there was a write error of some kind.
+  */
+ static int form_header_field(header_struct *h,
+@@ -1118,6 +1242,7 @@ AP_DECLARE_NONSTD(int) ap_send_http_trac
+ 
+ typedef struct header_filter_ctx {
+     int headers_sent;
++    int headers_error;
+ } header_filter_ctx;
+ 
+ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
+@@ -1133,19 +1258,23 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
+     header_filter_ctx *ctx = f->ctx;
+     const char *ctype;
+     ap_bucket_error *eb = NULL;
++    apr_bucket *eos = NULL;
+ 
+     AP_DEBUG_ASSERT(!r->main);
+ 
+-    if (r->header_only) {
+-        if (!ctx) {
+-            ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx));
+-        }
+-        else if (ctx->headers_sent) {
++    if (!ctx) {
++        ctx = f->ctx = apr_pcalloc(r->pool, sizeof(header_filter_ctx));
++    }
++    if (ctx->headers_sent) {
++        /* Eat body if response must not have one. */
++        if (r->header_only || r->status == HTTP_NO_CONTENT) {
+             apr_brigade_cleanup(b);
+-            return OK;
++            return APR_SUCCESS;
+         }
+     }
+-
++    else if (!ctx->headers_error && !check_headers(r)) {
++        ctx->headers_error = 1;
++    }
+     for (e = APR_BRIGADE_FIRST(b);
+          e != APR_BRIGADE_SENTINEL(b);
+          e = APR_BUCKET_NEXT(e))
+@@ -1162,10 +1291,44 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
+             ap_remove_output_filter(f);
+             return ap_pass_brigade(f->next, b);
+         }
++        if (ctx->headers_error && APR_BUCKET_IS_EOS(e)) {
++            eos = e;
++        }
+     }
+-    if (eb) {
+-        int status;
++    if (ctx->headers_error) {
++        if (!eos) {
++            /* Eat body until EOS */
++            apr_brigade_cleanup(b);
++            return APR_SUCCESS;
++        }
+ 
++        /* We may come back here from ap_die() below,
++         * so clear anything from this response.
++         */
++        ctx->headers_error = 0;
++        apr_table_clear(r->headers_out);
++        apr_table_clear(r->err_headers_out);
++
++        /* Don't recall ap_die() if we come back here (from its own internal
++         * redirect or error response), otherwise we can end up in infinite
++         * recursion; better fall through with 500, minimal headers and an
++         * empty body (EOS only).
++         */
++        if (!check_headers_recursion(r)) {
++            apr_brigade_cleanup(b);
++            ap_die(HTTP_INTERNAL_SERVER_ERROR, r);
++            return AP_FILTER_ERROR;
++        }
++        APR_BUCKET_REMOVE(eos);
++        apr_brigade_cleanup(b);
++        APR_BRIGADE_INSERT_TAIL(b, eos);
++        r->status = HTTP_INTERNAL_SERVER_ERROR;
++        r->content_type = r->content_encoding = NULL;
++        r->content_languages = NULL;
++        ap_set_content_length(r, 0);
++    }
++    else if (eb) {
++        int status;
+         status = eb->status;
+         apr_brigade_cleanup(b);
+         ap_die(status, r);
+@@ -1222,6 +1385,10 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
+         apr_table_unset(r->headers_out, "Content-Length");
+     }
+ 
++    if (r->status == HTTP_NO_CONTENT) {
++        apr_table_unset(r->headers_out, "Content-Length");
++    }
++
+     ctype = ap_make_content_type(r, r->content_type);
+     if (strcasecmp(ctype, NO_CONTENT_TYPE)) {
+         apr_table_setn(r->headers_out, "Content-Type", ctype);
+@@ -1310,11 +1477,11 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_
+     terminate_header(b2);
+ 
+     ap_pass_brigade(f->next, b2);
++    ctx->headers_sent = 1;
+ 
+-    if (r->header_only) {
++    if (r->header_only || r->status == HTTP_NO_CONTENT) {
+         apr_brigade_cleanup(b);
+-        ctx->headers_sent = 1;
+-        return OK;
++        return APR_SUCCESS;
+     }
+ 
+     r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
diff -Nru apache2-2.2.22/debian/patches/series apache2-2.2.22/debian/patches/series
--- apache2-2.2.22/debian/patches/series	2016-07-20 01:04:30.000000000 -0400
+++ apache2-2.2.22/debian/patches/series	2017-01-17 10:53:33.000000000 -0500
@@ -52,3 +52,4 @@
 CVE-2015-3183.patch
 SSL_CTX_use_certificate_clear_errors.diff
 CVE-2016-5387.patch
+CVE-2016-8743.patch

Reply to: