]> granicus.if.org Git - apache/blobdiff - server/protocol.c
Improve comments for reviewers to understand this logic
[apache] / server / protocol.c
index 7745c1bdd7638553cf00648f07dac218f172be4a..9bc0083100c95d26218a74d49418a45d40e67c71 100644 (file)
@@ -67,6 +67,9 @@ APR_HOOK_STRUCT(
     APR_HOOK_LINK(http_scheme)
     APR_HOOK_LINK(default_port)
     APR_HOOK_LINK(note_auth_failure)
+    APR_HOOK_LINK(protocol_propose)
+    APR_HOOK_LINK(protocol_switch)
+    APR_HOOK_LINK(protocol_get)
 )
 
 AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL;
@@ -185,9 +188,6 @@ AP_DECLARE(apr_time_t) ap_rationalize_mtime(request_rec *r, apr_time_t mtime)
     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.
@@ -239,7 +239,9 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n,
             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;
         }
@@ -290,9 +292,6 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n,
                 /* 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) {
@@ -401,7 +400,8 @@ AP_DECLARE(apr_status_t) ap_rgetline_core(char **s, apr_size_t n,
                      */
                     if (do_alloc) {
                         tmp = NULL;
-                    } else {
+                    }
+                    else {
                         /* We're null terminated. */
                         tmp = last_char;
                     }
@@ -527,7 +527,7 @@ AP_CORE_DECLARE(void) ap_parse_uri(request_rec *r, const char *uri)
     if (status == APR_SUCCESS) {
         /* if it has a scheme we may need to do absoluteURI vhost stuff */
         if (r->parsed_uri.scheme
-            && !strcasecmp(r->parsed_uri.scheme, ap_http_scheme(r))) {
+            && !ap_cstr_casecmp(r->parsed_uri.scheme, ap_http_scheme(r))) {
             r->hostname = r->parsed_uri.hostname;
         }
         else if (r->method_number == M_CONNECT) {
@@ -565,19 +565,14 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
     const char *uri;
     const char *pro;
 
-    int major = 1, minor = 0;   /* Assume HTTP/1.0 if non-"HTTP" protocol */
+    unsigned int major = 1, minor = 0;   /* Assume HTTP/1.0 if non-"HTTP" protocol */
     char http[5];
     apr_size_t len;
-    int num_blank_lines = 0;
-    int max_blank_lines = r->server->limit_req_fields;
+    int num_blank_lines = DEFAULT_LIMIT_BLANK_LINES;
     core_server_config *conf = ap_get_core_module_config(r->server->module_config);
     int strict = conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT;
     int enforce_strict = !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY);
 
-    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.
      *
@@ -611,9 +606,7 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
              * 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->proto_num = HTTP_VERSION(1,0);
-                r->protocol  = apr_pstrdup(r->pool, "HTTP/1.0");
+                r->status = HTTP_REQUEST_URI_TOO_LARGE;
             }
             else if (APR_STATUS_IS_TIMEUP(rv)) {
                 r->status = HTTP_REQUEST_TIME_OUT;
@@ -621,9 +614,11 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
             else if (APR_STATUS_IS_EINVAL(rv)) {
                 r->status = HTTP_BAD_REQUEST;
             }
+            r->proto_num = HTTP_VERSION(1,0);
+            r->protocol  = "HTTP/1.0";
             return 0;
         }
-    } while ((len <= 0) && (++num_blank_lines < max_blank_lines));
+    } while ((len <= 0) && (--num_blank_lines >= 0));
 
     if (APLOGrtrace5(r)) {
         ap_log_rerror(APLOG_MARK, APLOG_TRACE5, 0, r,
@@ -637,6 +632,13 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
 
     uri = ap_getword_white(r->pool, &ll);
 
+    if (!*r->method || !*uri) {
+        r->status    = HTTP_BAD_REQUEST;
+        r->proto_num = HTTP_VERSION(1,0);
+        r->protocol  = "HTTP/1.0";
+        return 0;
+    }
+
     /* Provide quick information about the request method as soon as known */
 
     r->method_number = ap_method_number_of(r->method);
@@ -645,12 +647,18 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
     }
 
     ap_parse_uri(r, uri);
+    if (r->status != HTTP_OK) {
+        r->proto_num = HTTP_VERSION(1,0);
+        r->protocol  = "HTTP/1.0";
+        return 0;
+    }
 
     if (ll[0]) {
         r->assbackwards = 0;
         pro = ll;
         len = strlen(ll);
-    } else {
+    }
+    else {
         r->assbackwards = 1;
         pro = "HTTP/0.9";
         len = 8;
@@ -680,12 +688,15 @@ static int read_request_line(request_rec *r, apr_bucket_brigade *bb)
             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02418)
                           "Invalid protocol '%s'", r->protocol);
             if (enforce_strict) {
+                r->proto_num = HTTP_VERSION(1,0);
+                r->protocol  = "HTTP/1.0";
+                r->connection->keepalive = AP_CONN_CLOSE;
                 r->status = HTTP_BAD_REQUEST;
                 return 0;
             }
         }
         if (3 == sscanf(r->protocol, "%4s/%u.%u", http, &major, &minor)
-            && (strcasecmp("http", http) == 0)
+            && (ap_cstr_casecmp("http", http) == 0)
             && (minor < HTTP_VERSION(1, 0)) ) { /* don't allow HTTP/0.1000 */
             r->proto_num = HTTP_VERSION(major, minor);
         }
@@ -779,7 +790,6 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb
      */
     while(1) {
         apr_status_t rv;
-        int folded = 0;
 
         field = NULL;
         rv = ap_rgetline(&field, r->server->limit_req_fieldsize + 2,
@@ -799,7 +809,7 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb
              */
             if (rv == APR_ENOSPC) {
                 const char *field_escaped;
-                if (field) {
+                if (field && len) {
                     /* ensure ap_escape_html will terminate correctly */
                     field[len - 1] = '\0';
                     field_escaped = ap_escape_html(r->pool, field);
@@ -824,154 +834,195 @@ AP_DECLARE(void) ap_get_mime_headers_core(request_rec *r, apr_bucket_brigade *bb
             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 */
+        /* Process an obs-fold immediately by appending it to last_field */
+        if ((*field == '\t') || *field == ' ') {
 
-                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, APLOGNO(00562)
-                                  "Request header exceeds LimitRequestFieldSize "
-                                  "after folding: %.*s",
-                                  field_name_len(last_field), last_field);
-                    return;
-                }
+            if (last_field == NULL) {
+                r->status = HTTP_BAD_REQUEST;
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(03442)
+                              "Line folding encountered before first"
+                              " header line");
+                return;
+            }
+
+            /* 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 */
+
+            if (fold_len >= (apr_size_t)(r->server->limit_req_fieldsize)) {
+                const char *field_escaped;
 
+                r->status = HTTP_BAD_REQUEST;
+                /* report what we have accumulated so far before the
+                 * overflow (last_field) as the field with the problem
+                 */
+                field_escaped = ap_escape_html(r->pool, last_field);
+                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(field_escaped), 
+                                 field_escaped));
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00562)
+                              "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 */ {
-
-                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.");
-                    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563)
-                                  "Number of request headers exceeds "
-                                  "LimitRequestFields");
-                    return;
-                }
+            memcpy(last_field + last_len, field, len +1); /* +1 for nul */
+            /* Replace obs-fold w/ SP per RFC 7230 3.2.4 */
+            if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) {
+                last_field[last_len] = ' ';
+            }
+            last_len += len;
 
-                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)));
-                    ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564)
-                                  "Request header field is missing ':' "
-                                  "separator: %.*s", (int)LOG_NAME_MAX_LEN,
-                                  last_field);
-                    return;
-                }
+            /* The obs-fold continuation line is merged to the last_field
+             * so continue to the next input line
+             */
+            continue;
+        }
 
-                tmp_field = value - 1; /* last character of field-name */
+        /* Not a continuation line, so process the last_field fully composed */
 
-                *value++ = '\0'; /* NUL-terminate at colon */
+        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.");
+            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00563)
+                          "Number of request headers exceeds "
+                          "LimitRequestFields");
+            return;
+        }
 
-                while (*value == ' ' || *value == '\t') {
-                    ++value;            /* Skip to start of value   */
-                }
+        if (!(conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT))
+        {
+            /* Not Strict, using the legacy parser */
 
-                /* Strip LWS after field-name: */
-                while (tmp_field > last_field
-                       && (*tmp_field == ' ' || *tmp_field == '\t')) {
-                    *tmp_field-- = '\0';
-                }
+            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)));
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00564)
+                              "Request header field is missing ':' "
+                              "separator: %.*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';
-                }
+            tmp_field = value - 1; /* last character of field-name */
 
-                if (conf->http_conformance & AP_HTTP_CONFORMANCE_STRICT) {
-                    int err = 0;
+            *value++ = '\0'; /* NUL-terminate at colon */
 
-                    if (*last_field == '\0') {
-                        err = HTTP_BAD_REQUEST;
-                        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02425)
-                                      "Empty request header field name not allowed");
-                    }
-                    else if (ap_has_cntrl(last_field)) {
-                        err = HTTP_BAD_REQUEST;
-                        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02426)
-                                      "[HTTP strict] Request header field name contains "
-                                      "control character: %.*s",
-                                      (int)LOG_NAME_MAX_LEN, last_field);
-                    }
-                    else if (ap_has_cntrl(value)) {
-                        err = HTTP_BAD_REQUEST;
-                        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02427)
-                                      "Request header field '%.*s' contains"
-                                      "control character", (int)LOG_NAME_MAX_LEN,
-                                      last_field);
-                    }
-                    if (err && !(conf->http_conformance & AP_HTTP_CONFORMANCE_LOGONLY)) {
-                        r->status = err;
-                        return;
-                    }
-                }
-                apr_table_addn(r->headers_in, last_field, value);
+            while (*value == ' ' || *value == '\t') {
+                ++value;            /* Skip to start of 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
-                 */
-                alloc_len = 0;
+            /* Strip LWS after field-name: */
+            while (tmp_field > last_field
+                   && (*tmp_field == ' ' || *tmp_field == '\t')) {
+                *tmp_field-- = '\0';
+            }
+
+            /* Strip LWS after field-value: */
+            tmp_field = last_field + last_len - 1;
+            while (tmp_field > value
+                   && (*tmp_field == ' ' || *tmp_field == '\t')) {
+                *tmp_field-- = '\0';
+            }
+        }
+        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;
+                apr_table_setn(r->notes, "error-notes",
+                    apr_psprintf(r->pool,
+                                 "Request header field name"
+                                 " is malformed.<br />\n"
+                                 "<pre>\n%.*s</pre>\n", (int)LOG_NAME_MAX_LEN,
+                                 ap_escape_html(r->pool, last_field)));
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02426)
+                              "Request header field name is malformed: "
+                              "%.*s", (int)LOG_NAME_MAX_LEN, last_field);
+                return;
+            }
+
+            *value++ = '\0'; /* NUL-terminate last_field name at ':' */
+
+            while (*value == ' ' || *value == '\t') {
+                ++value;     /* Skip LWS of value */
+            }
+
+            /* Find invalid, non-HT ctrl char, or the trailing NULL */
+            tmp_field = (char *)ap_scan_http_field_content(value);
+                
+            /* Strip LWS after field-value, if string not empty */
+            if (*value && (*tmp_field == '\0')) {
+                tmp_field--;
+                while (*tmp_field == ' ' || *tmp_field == '\t') {
+                    *tmp_field-- = '\0';
+                }
+                ++tmp_field;
+            }
 
-            } /* end if current line is not a continuation starting with tab */
+            /* 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
+             */
+            if (*tmp_field != '\0') {
+                r->status = HTTP_BAD_REQUEST;
+                apr_table_setn(r->notes, "error-notes",
+                    apr_psprintf(r->pool,
+                                 "Request header value is malformed.<br />\n"
+                                 "<pre>\n%.*s</pre>\n", (int)LOG_NAME_MAX_LEN,
+                                 ap_escape_html(r->pool, value)));
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02427)
+                              "Request header value is malformed: "
+                              "%.*s", (int)LOG_NAME_MAX_LEN, value);
+                return;
+            }
         }
 
-        /* Found a blank line, stop. */
+        apr_table_addn(r->headers_in, last_field, value);
+
+        /* After recording the last_field header line, we end our read loop
+         * here upon encountering the trailing line terminating headers
+         */
         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 (not an obs-fold). We will merge
+         * subsequent obs_fold lines on the next loop iterations. The zero
+         * alloc_len signals we have not allocated an obs-folding buffer yet
          */
-        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
@@ -991,16 +1042,10 @@ AP_DECLARE(void) ap_get_mime_headers(request_rec *r)
     apr_brigade_destroy(tmp_bb);
 }
 
-request_rec *ap_read_request(conn_rec *conn)
+AP_DECLARE(request_rec *) ap_create_request(conn_rec *conn)
 {
     request_rec *r;
     apr_pool_t *p;
-    const char *expect;
-    int access_status;
-    apr_bucket_brigade *tmp_bb;
-    apr_socket_t *csd;
-    apr_interval_time_t cur_timeout;
-
 
     apr_pool_create(&p, conn->pool);
     apr_pool_tag(p, "request");
@@ -1016,9 +1061,11 @@ request_rec *ap_read_request(conn_rec *conn)
     r->allowed_methods = ap_make_method_list(p, 2);
 
     r->headers_in      = apr_table_make(r->pool, 25);
+    r->trailers_in     = apr_table_make(r->pool, 5);
     r->subprocess_env  = apr_table_make(r->pool, 25);
     r->headers_out     = apr_table_make(r->pool, 12);
     r->err_headers_out = apr_table_make(r->pool, 5);
+    r->trailers_out    = apr_table_make(r->pool, 5);
     r->notes           = apr_table_make(r->pool, 5);
 
     r->request_config  = ap_create_request_config(r->pool);
@@ -1037,6 +1084,7 @@ request_rec *ap_read_request(conn_rec *conn)
     r->read_body       = REQUEST_NO_BODY;
 
     r->status          = HTTP_OK;  /* Until further notice */
+    r->header_only     = 0;
     r->the_request     = NULL;
 
     /* Begin by presuming any module can make its own path_info assumptions,
@@ -1047,6 +1095,20 @@ request_rec *ap_read_request(conn_rec *conn)
     r->useragent_addr = conn->client_addr;
     r->useragent_ip = conn->client_ip;
 
+    return r;
+}
+
+request_rec *ap_read_request(conn_rec *conn)
+{
+    const char *expect;
+    int access_status;
+    apr_bucket_brigade *tmp_bb;
+    apr_socket_t *csd;
+    apr_interval_time_t cur_timeout;
+
+
+    request_rec *r = ap_create_request(conn);
+
     tmp_bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
 
     ap_run_pre_read_request(r, conn);
@@ -1065,15 +1127,18 @@ request_rec *ap_read_request(conn_rec *conn)
             }
             else if (r->method == NULL) {
                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00566)
-                              "request failed: invalid characters in URI");
+                              "request failed: malformed request line");
             }
-            ap_send_error_response(r, 0);
+            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);
             goto traceout;
         case HTTP_REQUEST_TIME_OUT:
-            ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+            ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, NULL);
             if (!r->connection->keepalives)
                 ap_run_log_transaction(r);
             apr_brigade_destroy(tmp_bb);
@@ -1097,6 +1162,8 @@ request_rec *ap_read_request(conn_rec *conn)
     }
 
     if (!r->assbackwards) {
+        const char *tenc;
+
         ap_get_mime_headers_core(r, tmp_bb);
         if (r->status != HTTP_OK) {
             ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00567)
@@ -1108,12 +1175,34 @@ request_rec *ap_read_request(conn_rec *conn)
             goto traceout;
         }
 
-        if (apr_table_get(r->headers_in, "Transfer-Encoding")
-            && apr_table_get(r->headers_in, "Content-Length")) {
-            /* 2616 section 4.4, point 3: "if both Transfer-Encoding
-             * and Content-Length are received, the latter MUST be
-             * ignored"; so unset it here to prevent any confusion
-             * later. */
+        tenc = apr_table_get(r->headers_in, "Transfer-Encoding");
+        if (tenc) {
+            /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
+             * Section 3.3.3.3: "If a Transfer-Encoding header field is
+             * present in a request and the chunked transfer coding is not
+             * the final encoding ...; the server MUST respond with the 400
+             * (Bad Request) status code and then close the connection".
+             */
+            if (!(ap_cstr_casecmp(tenc, "chunked") == 0 /* fast path */
+                    || ap_find_last_token(r->pool, tenc, "chunked"))) {
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02539)
+                              "client sent unknown Transfer-Encoding "
+                              "(%s): %s", tenc, r->uri);
+                r->status = HTTP_BAD_REQUEST;
+                conn->keepalive = AP_CONN_CLOSE;
+                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);
+                goto traceout;
+            }
+
+            /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-23
+             * Section 3.3.3.3: "If a message is received with both a
+             * Transfer-Encoding and a Content-Length header field, the
+             * Transfer-Encoding overrides the Content-Length. ... A sender
+             * MUST remove the received Content-Length field".
+             */
             apr_table_unset(r->headers_in, "Content-Length");
         }
     }
@@ -1199,18 +1288,27 @@ request_rec *ap_read_request(conn_rec *conn)
          * unfortunately, to signal a poor man's mandatory extension that
          * the server must understand or return 417 Expectation Failed.
          */
-        if (strcasecmp(expect, "100-continue") == 0) {
+        if (ap_cstr_casecmp(expect, "100-continue") == 0) {
             r->expecting_100 = 1;
         }
         else {
-            r->status = HTTP_EXPECTATION_FAILED;
-            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570)
-                          "client sent an unrecognized expectation value of "
-                          "Expect: %s", expect);
-            ap_send_error_response(r, 0);
-            ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
-            ap_run_log_transaction(r);
-            goto traceout;
+            core_server_config *conf;
+
+            conf = ap_get_core_module_config(r->server->module_config);
+            if (conf->http_expect_strict != AP_HTTP_EXPECT_STRICT_DISABLE) {
+                r->status = HTTP_EXPECTATION_FAILED;
+                ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00570)
+                              "client sent an unrecognized expectation value "
+                              "of Expect: %s", expect);
+                ap_send_error_response(r, 0);
+                ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+                ap_run_log_transaction(r);
+                goto traceout;
+            } else {
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02595)
+                              "client sent an unrecognized expectation value "
+                              "of Expect (not fatal): %s", expect);
+            }
         }
     }
 
@@ -1262,6 +1360,7 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew,
     rnew->status          = HTTP_OK;
 
     rnew->headers_in      = apr_table_copy(rnew->pool, r->headers_in);
+    rnew->trailers_in     = apr_table_copy(rnew->pool, r->trailers_in);
 
     /* did the original request have a body?  (e.g. POST w/SSI tags)
      * if so, make sure the subrequest doesn't inherit body headers
@@ -1273,6 +1372,7 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew,
     rnew->subprocess_env  = apr_table_copy(rnew->pool, r->subprocess_env);
     rnew->headers_out     = apr_table_make(rnew->pool, 5);
     rnew->err_headers_out = apr_table_make(rnew->pool, 5);
+    rnew->trailers_out    = apr_table_make(rnew->pool, 5);
     rnew->notes           = apr_table_make(rnew->pool, 5);
 
     rnew->expecting_100   = r->expecting_100;
@@ -1282,6 +1382,21 @@ AP_DECLARE(void) ap_set_sub_req_protocol(request_rec *rnew,
     rnew->main = (request_rec *) r;
 }
 
+static void error_output_stream(request_rec *r, int status)
+{
+    conn_rec *c = r->connection;
+    apr_bucket_brigade *bb;
+    apr_bucket *b;
+
+    bb = apr_brigade_create(r->pool, c->bucket_alloc);
+    b = ap_bucket_error_create(status, NULL, r->pool,
+            r->connection->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
+    b = apr_bucket_eos_create(c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(bb, b);
+    ap_pass_brigade(r->output_filters, bb);
+}
+
 static void end_output_stream(request_rec *r)
 {
     conn_rec *c = r->connection;
@@ -1309,9 +1424,12 @@ AP_DECLARE(void) ap_finalize_sub_req_protocol(request_rec *sub)
  */
 AP_DECLARE(void) ap_finalize_request_protocol(request_rec *r)
 {
-    (void) ap_discard_request_body(r);
+    int status = ap_discard_request_body(r);
 
     /* tell the filter chain there is no more content coming */
+    if (status) {
+        error_output_stream(r, status);
+    }
     if (!r->eos_sent) {
         end_output_stream(r);
     }
@@ -1327,8 +1445,8 @@ AP_DECLARE(void) ap_note_auth_failure(request_rec *r)
         ap_run_note_auth_failure(r, type);
     }
     else {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR,
-                      0, r, APLOGNO(00571) "need AuthType to note auth failure: %s", r->uri);
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00571)
+                      "need AuthType to note auth failure: %s", r->uri);
     }
 }
 
@@ -1344,27 +1462,27 @@ AP_DECLARE(void) ap_note_digest_auth_failure(request_rec *r)
 
 AP_DECLARE(int) ap_get_basic_auth_pw(request_rec *r, const char **pw)
 {
-    const char *auth_line = apr_table_get(r->headers_in,
-                                          (PROXYREQ_PROXY == r->proxyreq)
-                                              ? "Proxy-Authorization"
-                                              : "Authorization");
-    const char *t;
+    const char *t, *auth_line;
 
-    if (!(t = ap_auth_type(r)) || strcasecmp(t, "Basic"))
+    if (!(t = ap_auth_type(r)) || ap_cstr_casecmp(t, "Basic"))
         return DECLINED;
 
     if (!ap_auth_name(r)) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR,
-                      0, r, APLOGNO(00572) "need AuthName: %s", r->uri);
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00572) 
+                      "need AuthName: %s", r->uri);
         return HTTP_INTERNAL_SERVER_ERROR;
     }
 
+    auth_line = apr_table_get(r->headers_in,
+                              (PROXYREQ_PROXY == r->proxyreq)
+                                  ? "Proxy-Authorization" : "Authorization");
+
     if (!auth_line) {
         ap_note_auth_failure(r);
         return HTTP_UNAUTHORIZED;
     }
 
-    if (strcasecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
+    if (ap_cstr_casecmp(ap_getword(r->pool, &auth_line, ' '), "Basic")) {
         /* Client tried to authenticate using wrong auth scheme */
         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00573)
                       "client used wrong authentication scheme: %s", r->uri);
@@ -1478,10 +1596,35 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter(
      * We can only set a C-L in the response header if we haven't already
      * sent any buckets on to the next output filter for this request.
      */
-    if (ctx->data_sent == 0 && eos &&
+    if (ctx->data_sent == 0 && eos) {
+        core_server_config *conf =
+            ap_get_core_module_config(r->server->module_config);
+
+        /* This is a hack, but I can't find anyway around it.  The idea is that
+         * we don't want to send out 0 Content-Lengths if it is a HEAD request.
+         * [Unless the corresponding body (for a GET) would really be empty!]
+         * This happens when modules try to outsmart the server, and return
+         * if they see a HEAD request.  Apache 1.3 handlers were supposed to
+         * just return in that situation, and the core handled the HEAD.  From
+         * 2.0, if a handler returns, then the core sends an EOS bucket down
+         * the filter stack, and this content-length filter computes a length
+         * of zero and we would end up sending a zero C-L to the client.
+         * We can't just remove the this C-L filter, because well behaved 2.0+
+         * handlers will send their data down the stack, and we will compute
+         * a real C-L for the head request. RBB
+         *
+         * Allow modification of this behavior through the
+         * HttpContentLengthHeadZero directive.
+         *
+         * The default (unset) behavior is to squelch the C-L in this case.
+         */
+
         /* don't whack the C-L if it has already been set for a HEAD
          * by something like proxy.  the brigade only has an EOS bucket
-         * in this case, making r->bytes_sent zero.
+         * in this case, making r->bytes_sent zero, and either there is
+         * an existing C-L we want to preserve, or r->sent_bodyct is not
+         * zero (the empty body is being sent) thus we don't want to add
+         * a C-L of zero (the backend did not provide it, neither do we).
          *
          * if r->bytes_sent > 0 we have a (temporary) body whose length may
          * have been changed by a filter.  the C-L header might not have been
@@ -1489,9 +1632,13 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_content_length_filter(
          * such filters update or remove the C-L header, and just use it
          * if present.
          */
-        !(r->header_only && r->bytes_sent == 0 &&
-            apr_table_get(r->headers_out, "Content-Length"))) {
-        ap_set_content_length(r, r->bytes_sent);
+        if (!(r->header_only
+              && !r->bytes_sent
+              && (r->sent_bodyct
+                  || conf->http_cl_head_zero != AP_HTTP_CL_HEAD_ZERO_ENABLE
+                  || apr_table_get(r->headers_out, "Content-Length")))) {
+            ap_set_content_length(r, r->bytes_sent);
+        }
     }
 
     ctx->data_sent = 1;
@@ -1658,7 +1805,8 @@ struct ap_vrprintf_data {
     char *buff;
 };
 
-static apr_status_t r_flush(apr_vformatter_buff_t *buff)
+/* Flush callback for apr_vformatter; returns -1 on error. */
+static int r_flush(apr_vformatter_buff_t *buff)
 {
     /* callback function passed to ap_vformatter to be called when
      * vformatter needs to write into buff and buff.curpos > buff.endpos */
@@ -1679,7 +1827,7 @@ static apr_status_t r_flush(apr_vformatter_buff_t *buff)
     vd->vbuff.curpos = vd->buff;
     vd->vbuff.endpos = vd->buff + AP_IOBUFSIZE;
 
-    return APR_SUCCESS;
+    return 0;
 }
 
 AP_DECLARE(int) ap_vrprintf(request_rec *r, const char *fmt, va_list va)
@@ -1800,19 +1948,21 @@ typedef struct hdr_ptr {
     ap_filter_t *f;
     apr_bucket_brigade *bb;
 } hdr_ptr;
+
 static int send_header(void *data, const char *key, const char *val)
 {
     ap_fputstrs(((hdr_ptr*)data)->f, ((hdr_ptr*)data)->bb,
                 key, ": ", val, CRLF, NULL);
     return 1;
 }
+
 AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
 {
     hdr_ptr x;
     char *status_line = NULL;
     request_rec *rr;
 
-    if (r->proto_num < 1001) {
+    if (r->proto_num < HTTP_VERSION(1,1)) {
         /* don't send interim response to HTTP/1.0 Client */
         return;
     }
@@ -1838,7 +1988,7 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
         rr->expecting_100 = 0;
     }
 
-    status_line = apr_pstrcat(r->pool, AP_SERVER_PROTOCOL, " ", r->status_line, CRLF, NULL);
+    status_line = apr_pstrcat(r->pool, AP_SERVER_PROTOCOL " ", r->status_line, CRLF, NULL);
     ap_xlate_proto_to_ascii(status_line, strlen(status_line));
 
     x.f = r->connection->output_filters;
@@ -1854,6 +2004,213 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
     apr_brigade_destroy(x.bb);
 }
 
+/*
+ * Compare two protocol identifier. Result is similar to strcmp():
+ * 0 gives same precedence, >0 means proto1 is preferred.
+ */
+static int protocol_cmp(const apr_array_header_t *preferences,
+                        const char *proto1,
+                        const char *proto2)
+{
+    if (preferences && preferences->nelts > 0) {
+        int index1 = ap_array_str_index(preferences, proto1, 0);
+        int index2 = ap_array_str_index(preferences, proto2, 0);
+        if (index2 > index1) {
+            return (index1 >= 0) ? 1 : -1;
+        }
+        else if (index1 > index2) {
+            return (index2 >= 0) ? -1 : 1;
+        }
+    }
+    /* both have the same index (mabye -1 or no pref configured) and we compare
+     * the names so that spdy3 gets precedence over spdy2. That makes
+     * the outcome at least deterministic. */
+    return strcmp(proto1, proto2);
+}
+
+AP_DECLARE(const char *) ap_get_protocol(conn_rec *c)
+{
+    const char *protocol = ap_run_protocol_get(c);
+    return protocol? protocol : AP_PROTOCOL_HTTP1;
+}
+
+AP_DECLARE(apr_status_t) ap_get_protocol_upgrades(conn_rec *c, request_rec *r, 
+                                                  server_rec *s, int report_all, 
+                                                  const apr_array_header_t **pupgrades)
+{
+    apr_pool_t *pool = r? r->pool : c->pool;
+    core_server_config *conf;
+    const char *existing;
+    apr_array_header_t *upgrades = NULL;
+
+    if (!s) {
+        s = (r? r->server : c->base_server);
+    }
+    conf = ap_get_core_module_config(s->module_config);
+    
+    if (conf->protocols->nelts > 0) {
+        existing = ap_get_protocol(c);
+        if (conf->protocols->nelts > 1 
+            || !ap_array_str_contains(conf->protocols, existing)) {
+            int i;
+            
+            /* possibly more than one choice or one, but not the
+             * existing. (TODO: maybe 426 and Upgrade then?) */
+            upgrades = apr_array_make(pool, conf->protocols->nelts + 1, 
+                                      sizeof(char *));
+            for (i = 0; i < conf->protocols->nelts; i++) {
+                const char *p = APR_ARRAY_IDX(conf->protocols, i, char *);
+                if (strcmp(existing, p)) {
+                    /* not the one we have and possible, add in this order */
+                    APR_ARRAY_PUSH(upgrades, const char*) = p;
+                }
+                else if (!report_all) {
+                    break;
+                }
+            }
+        }
+    }
+    
+    *pupgrades = upgrades;
+    return APR_SUCCESS;
+}
+
+AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r, 
+                                            server_rec *s,
+                                            const apr_array_header_t *choices)
+{
+    apr_pool_t *pool = r? r->pool : c->pool;
+    core_server_config *conf;
+    const char *protocol = NULL, *existing;
+    apr_array_header_t *proposals;
+
+    if (!s) {
+        s = (r? r->server : c->base_server);
+    }
+    conf = ap_get_core_module_config(s->module_config);
+    
+    if (APLOGcdebug(c)) {
+        const char *p = apr_array_pstrcat(pool, conf->protocols, ',');
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03155) 
+                      "select protocol from %s, choices=%s for server %s", 
+                      p, apr_array_pstrcat(pool, choices, ','),
+                      s->server_hostname);
+    }
+
+    if (conf->protocols->nelts <= 0) {
+        /* nothing configured, by default, we only allow http/1.1 here.
+         * For now...
+         */
+        if (ap_array_str_contains(choices, AP_PROTOCOL_HTTP1)) {
+            return AP_PROTOCOL_HTTP1;
+        }
+        else {
+            return NULL;
+        }
+    }
+
+    proposals = apr_array_make(pool, choices->nelts + 1, sizeof(char *));
+    ap_run_protocol_propose(c, r, s, choices, proposals);
+
+    /* If the existing protocol has not been proposed, but is a choice,
+     * add it to the proposals implicitly.
+     */
+    existing = ap_get_protocol(c);
+    if (!ap_array_str_contains(proposals, existing)
+        && ap_array_str_contains(choices, existing)) {
+        APR_ARRAY_PUSH(proposals, const char*) = existing;
+    }
+
+    if (proposals->nelts > 0) {
+        int i;
+        const apr_array_header_t *prefs = NULL;
+
+        /* Default for protocols_honor_order is 'on' or != 0 */
+        if (conf->protocols_honor_order == 0 && choices->nelts > 0) {
+            prefs = choices;
+        }
+        else {
+            prefs = conf->protocols;
+        }
+
+        /* Select the most preferred protocol */
+        if (APLOGcdebug(c)) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03156) 
+                          "select protocol, proposals=%s preferences=%s configured=%s", 
+                          apr_array_pstrcat(pool, proposals, ','),
+                          apr_array_pstrcat(pool, prefs, ','),
+                          apr_array_pstrcat(pool, conf->protocols, ','));
+        }
+        for (i = 0; i < proposals->nelts; ++i) {
+            const char *p = APR_ARRAY_IDX(proposals, i, const char *);
+            if (!ap_array_str_contains(conf->protocols, p)) {
+                /* not a configured protocol here */
+                continue;
+            }
+            else if (!protocol 
+                     || (protocol_cmp(prefs, protocol, p) < 0)) {
+                /* none selected yet or this one has preference */
+                protocol = p;
+            }
+        }
+    }
+    if (APLOGcdebug(c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03157)
+                      "selected protocol=%s", 
+                      protocol? protocol : "(none)");
+    }
+
+    return protocol;
+}
+
+AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r, 
+                                            server_rec *s,
+                                            const char *protocol)
+{
+    const char *current = ap_get_protocol(c);
+    int rc;
+    
+    if (!strcmp(current, protocol)) {
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02906)
+                      "already at it, protocol_switch to %s", 
+                      protocol);
+        return APR_SUCCESS;
+    }
+    
+    rc = ap_run_protocol_switch(c, r, s, protocol);
+    switch (rc) {
+        case DECLINED:
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02907)
+                          "no implementation for protocol_switch to %s", 
+                          protocol);
+            return APR_ENOTIMPL;
+        case OK:
+        case DONE:
+            return APR_SUCCESS;
+        default:
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02905)
+                          "unexpected return code %d from protocol_switch to %s"
+                          , rc, protocol);
+            return APR_EOF;
+    }    
+}
+
+AP_DECLARE(int) ap_is_allowed_protocol(conn_rec *c, request_rec *r,
+                                       server_rec *s, const char *protocol)
+{
+    core_server_config *conf;
+
+    if (!s) {
+        s = (r? r->server : c->base_server);
+    }
+    conf = ap_get_core_module_config(s->module_config);
+    
+    if (conf->protocols->nelts > 0) {
+        return ap_array_str_contains(conf->protocols, protocol);
+    }
+    return !strcmp(AP_PROTOCOL_HTTP1, protocol);
+}
+
 
 AP_IMPLEMENT_HOOK_VOID(pre_read_request,
                        (request_rec *r, conn_rec *c),
@@ -1869,3 +2226,14 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port,
 AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure,
                             (request_rec *r, const char *auth_type),
                             (r, auth_type), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_ALL(int,protocol_propose,
+                          (conn_rec *c, request_rec *r, server_rec *s,
+                           const apr_array_header_t *offers,
+                           apr_array_header_t *proposals), 
+                          (c, r, s, offers, proposals), OK, DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,protocol_switch,
+                            (conn_rec *c, request_rec *r, server_rec *s,
+                             const char *protocol), 
+                            (c, r, s, protocol), DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,protocol_get,
+                            (const conn_rec *c), (c), NULL)