httpd-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From wr...@apache.org
Subject svn commit: r1775787 [2/2] - in /httpd/httpd/branches/2.2.x: ./ docs/manual/mod/ include/ modules/http/ server/
Date Fri, 23 Dec 2016 05:19:21 GMT
Modified: httpd/httpd/branches/2.2.x/server/protocol.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/server/protocol.c?rev=1775787&r1=1775786&r2=1775787&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/server/protocol.c (original)
+++ httpd/httpd/branches/2.2.x/server/protocol.c Fri Dec 23 05:19:21 2016
@@ -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;
                     }
@@ -466,7 +475,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;
@@ -474,7 +483,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. */
@@ -554,16 +563,31 @@ 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;
+}
 
-    unsigned 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;
     int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES;
+    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 past empty lines until we get a real request line,
      * a read error, the connection closes (EOF), or we timeout.
@@ -588,7 +612,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();
@@ -598,7 +622,7 @@ static int read_request_line(request_rec
              * happen if it exceeds the configured limit for a request-line.
              */
             if (APR_STATUS_IS_ENOSPC(rv)) {
-                r->status    = HTTP_REQUEST_URI_TOO_LARGE;
+                r->status = HTTP_REQUEST_URI_TOO_LARGE;
             }
             else if (APR_STATUS_IS_TIMEUP(rv)) {
                 r->status = HTTP_REQUEST_TIME_OUT;
@@ -613,68 +637,282 @@ static int read_request_line(request_rec
     } while ((len <= 0) && (--num_blank_lines >= 0));
 
     r->request_time = apr_time_now();
-    ll = r->the_request;
-    r->method = ap_getword_white(r->pool, &ll);
 
-    uri = ap_getword_white(r->pool, &ll);
+    r->method = r->the_request;
 
-    if (!*r->method || !*uri) {
-        r->status    = HTTP_BAD_REQUEST;
-        r->proto_num = HTTP_VERSION(1,0);
-        r->protocol  = apr_pstrdup(r->pool, "HTTP/1.0");
-        return 0;
+    /* 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)
+            ; 
     }
 
-    /* Provide quick information about the request method as soon as known */
+    /* 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; 
+    }
+
+    /* Advance uri pointer over leading whitespace, NUL terminate the method
+     * If non-SP whitespace is encountered, mark as specific error
+     */
+    for (uri = ll; apr_isspace(*uri); ++uri) 
+        if (*uri != ' ' && deferred_error == rrl_none)
+            deferred_error = rrl_badwhitespace; 
+    *ll = '\0';
+
+    if (!*uri && deferred_error == rrl_none)
+        deferred_error = rrl_missinguri;
 
+    /* Scan the URI up to the next whitespace, ensure it contains no raw
+     * control characters, otherwise mark in error
+     */
+    ll = (char*) ap_scan_vchar_obstext(uri);
+    if (ll == uri || (*ll && !apr_isspace(*ll))) {
+        deferred_error = rrl_baduri;
+        ll = strpbrk(ll, "\t\n\v\f\r ");
+    }
+
+    /* Verify URI terminated with a single SP, or mark as specific error */
+    if (!ll) {
+        r->protocol = "";
+        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') {
+    if (r->method_number == M_GET && r->method[0] == 'H')
         r->header_only = 1;
-    }
 
     ap_parse_uri(r, uri);
-    if (r->status != HTTP_OK) {
-        r->proto_num = HTTP_VERSION(1,0);
-        r->protocol  = apr_pstrdup(r->pool, "HTTP/1.0");
+
+    /* With the request understood, we can consider HTTP/0.9 specific errors */
+    if (r->proto_num == HTTP_VERSION(0, 9) && deferred_error == rrl_none) {
+        if (conf->http09_enable == AP_HTTP09_DISABLE)
+            deferred_error = rrl_reject09;
+        else if (strict && (r->method_number != M_GET || r->header_only))
+            deferred_error = rrl_badmethod09;
+    }
+
+    /* Now that the method, uri and protocol are all processed,
+     * we can safely resume any deferred error reporting
+     */
+    if (deferred_error != rrl_none) {
+        if (deferred_error == rrl_badmethod)
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "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;
+    }
+
+    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 (ll[0]) {
-        r->assbackwards = 0;
-        pro = ll;
-        len = strlen(ll);
-    } else {
-        r->assbackwards = 1;
-        pro = "HTTP/0.9";
-        len = 8;
+    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;
     }
-    r->protocol = apr_pstrmemdup(r->pool, pro, len);
 
-    /* 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 (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)
@@ -687,6 +925,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,
@@ -694,11 +936,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)) {
@@ -714,148 +955,228 @@ 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) {
-                /* ensure 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;
-            }
-            else /* not a continuation line */ {
+                fold_buf = (char *)apr_palloc(r->pool, alloc_len);
+                memcpy(fold_buf, last_field, last_len);
+                last_field = fold_buf;
+            }
+            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.");
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+                              "Number of request headers exceeds "
+                              "LimitRequestFields");
+                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   */
+                     ++value;            /* Skip to start of value   */
+                }
+
+                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;
                 }
 
-                /* Strip LWS after field-name: */
-                while (tmp_field > last_field
-                       && (*tmp_field == ' ' || *tmp_field == '\t')) {
-                    *tmp_field-- = '\0';
+                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);
+
+            /* This last_field header is now stored in headers_in,
+             * resume processing of the current input line.
+             */
         }
 
-        /* Found a blank line, stop. */
+        /* 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;
     }
 
     /* Combine multiple message-header fields with the same
      * field-name, following RFC 2616, 4.2.
      */
     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,33 +1244,38 @@ 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
-            || r->status == HTTP_BAD_REQUEST) {
-            if (r->status == HTTP_BAD_REQUEST) {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                              "request failed: invalid characters in URI");
-            }
-            else {
-                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
-                              "request failed: URI too long (longer than %d)", r->server->limit_req_line);
+        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);
             }
-            ap_send_error_response(r, 0);
+            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);
             return r;
         }
-
-        apr_brigade_destroy(tmp_bb);
-        return NULL;
     }
 
     /* We may have been in keep_alive_timeout mode, so toggle back
@@ -968,7 +1294,7 @@ request_rec *ap_read_request(conn_rec *c
 
         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);
@@ -987,7 +1313,7 @@ request_rec *ap_read_request(conn_rec *c
              */
             if (!(strcasecmp(tenc, "chunked") == 0 /* fast path */
                     || ap_find_last_token(r->pool, tenc, "chunked"))) {
-                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
                               "client sent unknown Transfer-Encoding "
                               "(%s): %s", tenc, r->uri);
                 r->status = HTTP_BAD_REQUEST;
@@ -1008,25 +1334,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);
 
@@ -1034,6 +1341,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.
@@ -1056,8 +1364,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);
     }
@@ -1072,14 +1380,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);
@@ -1279,7 +1581,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;

Modified: httpd/httpd/branches/2.2.x/server/util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/server/util.c?rev=1775787&r1=1775786&r2=1775787&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/server/util.c (original)
+++ httpd/httpd/branches/2.2.x/server/util.c Fri Dec 23 05:19:21 2016
@@ -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.
@@ -1425,6 +1425,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

Modified: httpd/httpd/branches/2.2.x/server/vhost.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.2.x/server/vhost.c?rev=1775787&r1=1775786&r2=1775787&view=diff
==============================================================================
--- httpd/httpd/branches/2.2.x/server/vhost.c (original)
+++ httpd/httpd/branches/2.2.x/server/vhost.c Fri Dec 23 05:19:21 2016
@@ -687,6 +687,116 @@ 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_isascii(*ch)) {
+            goto bad;
+        }
+        else 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 +810,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 +1090,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)



Mime
View raw message