diff options
author | Glenn Strauss <gstrauss@gluelogic.com> | 2020-08-27 02:50:13 -0400 |
---|---|---|
committer | Glenn Strauss <gstrauss@gluelogic.com> | 2020-10-11 11:43:06 -0400 |
commit | 8fc8ab891a570fe48fcb51d21be0299bc25d9c34 (patch) | |
tree | 83dc8035c9ac43a78d996a5bbb7e238d030a582d | |
parent | ada09a23b029731e24df65a39b11c4d3f90f59a0 (diff) | |
download | lighttpd-git-8fc8ab891a570fe48fcb51d21be0299bc25d9c34.tar.gz |
[core] http_request_parse_header() specialized
http_request_parse_header() specialized for HTTP/2 request headers
to be parsed as each field-name and value is HPACK-decoded; send headers
directly from HPACK decoder, rather than double-buffering in chunkqueue
http_request_headers_process_h2() for post-processing
-rw-r--r-- | src/h2.c | 177 | ||||
-rw-r--r-- | src/request.c | 439 | ||||
-rw-r--r-- | src/request.h | 17 | ||||
-rw-r--r-- | src/t/test_request.c | 5 |
4 files changed, 351 insertions, 287 deletions
@@ -885,75 +885,22 @@ h2_recv_trailers_r (connection * const con, h2con * const h2c, const uint32_t id } -/* prototype if HPACK-decoded HEADERS reconstituted - * into HTTP/1.1 request format in r->read_queue */ - -/* Note: similar to connection_handle_read_state(), except operates on single - * buf since HTTP/2 headers delivered in a single buffer and are complete or err - */ -__attribute_noinline__ -static void -h2_parse_request_headers (request_st * const r, char * const hdrs, const uint32_t header_len) -{ - unsigned short hoff[8192]; /* max num header lines + 3; 16k on stack */ - hoff[0] = 1; /* number of lines */ - hoff[1] = 0; /* base offset for all lines */ - /*hoff[2] = ...;*/ /* offset from base for 2nd line */ - r->rqst_header_len = http_header_parse_hoff(hdrs, header_len, hoff); - if (0 == r->rqst_header_len || hoff[0] >= sizeof(hoff)/sizeof(hoff[0])-1) { - /* error if headers incomplete or too many header fields */ - r->http_status = 431; /* Request Header Fields Too Large */ - log_error(r->conf.errh, __FILE__, __LINE__, - "oversized request-header -> sending Status 431"); - return; - } - #if 0 /*(handled in h2_parse_headers_frame())*/ - if (r->conf.log_request_header) - log_error(r->conf.errh, __FILE__, __LINE__, - "fd: %d request-len: %d\n%.*s", r->con->fd, - (int)r->rqst_header_len, (int)r->rqst_header_len, hdrs); - #endif - http_request_headers_process(r, hdrs, hoff, r->con->proto_default_port); -} - - static void -h2_parse_request (request_st * const r) +h2_parse_headers_frame (request_st * const restrict r, const unsigned char *psrc, const uint32_t plen, const int trailers) { - chunk * const c = r->read_queue->first; - r->rqst_header_len = buffer_string_length(c->mem) - (uint32_t)c->offset; - h2_parse_request_headers(r, c->mem->ptr + c->offset, r->rqst_header_len); - chunkqueue_mark_written(r->read_queue, r->rqst_header_len); - - if (0 != r->http_status) { - if (431 == r->http_status) /*(e.g. too many header lines)*/ - log_error(r->conf.errh, __FILE__, __LINE__, - "oversized request-header -> sending Status 431"); - } - - /* ignore Upgrade if using HTTP/2 */ - if (r->rqst_htags & HTTP_HEADER_UPGRADE) { - http_header_request_unset(r, HTTP_HEADER_UPGRADE, - CONST_STR_LEN("upgrade")); - buffer * const connhdr = - http_header_request_get(r, HTTP_HEADER_CONNECTION, - CONST_STR_LEN("connection")); - if (connhdr) - http_header_remove_token(connhdr, CONST_STR_LEN("upgrade")); - } - /* XXX: should filter out other hop-by-hop connection headers, too */ -} - - -static int -h2_parse_headers_frame (connection * const con, const unsigned char *psrc, const uint32_t plen, request_st * const restrict r, const int trailers) -{ - h2con * const h2c = con->h2; + h2con * const h2c = r->con->h2; struct lshpack_dec * const restrict decoder = &h2c->decoder; const unsigned char * const endp = psrc + plen; - uint32_t hlen = 0; - const uint32_t max_request_field_size = r->conf.max_request_field_size; + http_header_parse_ctx hpctx; + hpctx.hlen = 0; + hpctx.pseudo = 1; + hpctx.scheme = 0; + hpctx.trailers = trailers; + hpctx.max_request_field_size = r->conf.max_request_field_size; + hpctx.http_parseopts = r->conf.http_parseopts; const int log_request_header = r->conf.log_request_header; + int rc = LSHPACK_OK; + /*buffer_clear(&r->target);*//*(initial state)*/ /*(h2_init_con() resized h2r->tmp_buf to 64k; shared with r->tmp_buf)*/ buffer * const tb = r->tmp_buf; @@ -974,56 +921,27 @@ h2_parse_headers_frame (connection * const con, const unsigned char *psrc, const memset(&lsx, 0, sizeof(lsxpack_header_t)); lsx.buf = tb->ptr; lsx.val_len = tbsz - 1; - int rc = lshpack_dec_decode(decoder, &psrc, endp, &lsx); + rc = lshpack_dec_decode(decoder, &psrc, endp, &lsx); + if (0 == lsx.name_len) + rc = LSHPACK_ERR_BAD_DATA; if (rc == LSHPACK_OK) { - uint32_t len = - lsx.name_len + lsx.val_len + lshpack_dec_extra_bytes(decoder); - if ((hlen += len) > max_request_field_size) { - log_error(r->conf.errh, __FILE__, __LINE__, "%s", - "oversized request-header -> sending Status 431"); - r->http_status = 431; /* Request Header Fields Too Large */ - r->rqst_header_len += hlen; - r->read_queue->bytes_in += (off_t)hlen; - return 1; - } /* request parsing code expects value to be '\0'-terminated for * libc string functions (e.g parsing Content-Length w/ strtoll()) * so subtract 1 from initial lsx.val_len and '\0'-term here */ - lsx.buf[len] = '\0'; + lsx.buf[lsx.val_offset+lsx.val_len] = '\0'; + hpctx.k = lsx.buf+lsx.name_offset; + hpctx.v = lsx.buf+lsx.val_offset; + hpctx.klen = lsx.name_len; + hpctx.vlen = lsx.val_len; if (log_request_header) log_error(r->conf.errh, __FILE__, __LINE__, "fd:%d id:%u rqst: %.*s: %.*s", r->con->fd, r->h2id, - (int)lsx.name_len, lsx.buf+lsx.name_offset, - (int)lsx.val_len, lsx.buf+lsx.val_offset); + (int)hpctx.klen, hpctx.k, (int)hpctx.vlen, hpctx.v); - if (!trailers) { - chunkqueue_append_mem(r->read_queue, - lsx.buf+lsx.name_offset, len); - } - else { /*(trailers)*/ - /* ignore trailers (after required HPACK decoding) if streaming - * request body to backend since headers have already been sent - * to backend via Common Gateway Interface (CGI) (CGI, FastCGI, - * SCGI, etc) or HTTP/1.1 (proxy) (mod_proxy does not currently - * support using HTTP/2 to connect to backends) */ - if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST) - continue; - /* Note: do not unconditionally merge into headers since if - * headers had already been sent to backend, then mod_accesslog - * logging of request headers might be inaccurate. - * Many simple backends do not support HTTP/1.1 requests sending - * Transfer-Encoding: chunked, and even those that do might not - * handle trailers. Some backends do not even support HTTP/1.1. - * For all these reasons, ignore trailers if streaming request - * body to backend. Revisit in future if adding support for - * connecting to backends using HTTP/2 (with explicit config - * option to force connecting to backends using HTTP/2) */ - - /* XXX: TODO: request trailers not handled if streaming reqbody - * XXX: must ensure that trailers are not disallowed field-names - */ - } + r->http_status = http_request_parse_header(r, &hpctx); + if (0 != r->http_status) + break; } #if 0 /*(see catch-all below)*/ /* Send GOAWAY (further below) (decoder state not maintained on error) @@ -1047,39 +965,35 @@ h2_parse_headers_frame (connection * const con, const unsigned char *psrc, const * (slightly more specific, but not by much) before GOAWAY*/ /* LSHPACK_ERR_MORE_BUF is treated as an attack, send GOAWAY * (h2r->tmp_buf was resized to 64k in h2_init_con()) */ - request_h2error_t err = ( rc == LSHPACK_ERR_BAD_DATA - || rc == LSHPACK_ERR_TOO_LARGE - || rc == LSHPACK_ERR_MORE_BUF) - ? H2_E_COMPRESSION_ERROR - : H2_E_PROTOCOL_ERROR; - h2_send_rst_stream(r, con, err); + request_h2error_t err = H2_E_COMPRESSION_ERROR; + if (rc != LSHPACK_ERR_BAD_DATA) { + /* LSHPACK_ERR_TOO_LARGE, LSHPACK_ERR_MORE_BUF */ + err = H2_E_PROTOCOL_ERROR; + h2_send_rst_stream(r, r->con, err); + } if (!h2c->sent_goaway && !trailers) h2c->h2_cid = r->h2id; - h2_send_goaway_e(con, err); - return 0; + h2_send_goaway_e(r->con, err); + h2_retire_stream(r, r->con); + break; } } - #if 1 - /* terminate reconstituted HTTP/1.1 request - * (along with HTTP/2 pseudo-headers) */ - chunkqueue_append_mem(r->read_queue, CONST_STR_LEN("\r\n")); - if (r->read_queue->first->next) { - hlen += 2; - h2_frame_cq_compact(r->read_queue, hlen); - } - h2_parse_request(r); - #else - /* future: adjust counts if bypassing HTTP/1.x compatibility - * (avoiding reconsitituting HTTP/1.1 request in r->read_queue) */ - r->rqst_header_len += hlen; + hpctx.hlen += 2; + r->rqst_header_len = hpctx.hlen; /*(accounting for mod_accesslog and mod_rrdtool)*/ chunkqueue * const rq = r->read_queue; - rq->bytes_in += (off_t)hlen; - rq->bytes_out += (off_t)hlen; - #endif + rq->bytes_in += (off_t)hpctx.hlen; + rq->bytes_out += (off_t)hpctx.hlen; - return 1; + if (0 == r->http_status && LSHPACK_OK == rc) { + if (hpctx.pseudo) + r->http_status = + http_request_validate_pseudohdrs(r, hpctx.scheme, + hpctx.http_parseopts); + if (0 == r->http_status) + http_request_headers_process_h2(r, r->con->proto_default_port); + } } @@ -1185,8 +1099,7 @@ h2_recv_headers (connection * const con, uint8_t * const s, uint32_t flen) alen -= 5; } - if (!h2_parse_headers_frame(con, psrc, alen, r, trailers)) - return 0; + h2_parse_headers_frame(r, psrc, alen, trailers); #if 0 /*(handled in h2_parse_frames() as a connection error)*/ if (s[3] == H2_FTYPE_PUSH_PROMISE) { diff --git a/src/request.c b/src/request.c index f2080696..5a276644 100644 --- a/src/request.c +++ b/src/request.c @@ -559,162 +559,257 @@ static const char * http_request_parse_reqline_uri(request_st * const restrict r } } -static int http_request_parse_pseudohdrs(request_st * const restrict r, const char * const restrict ptr, const unsigned short * const restrict hoff, const unsigned int http_parseopts) { - /* HTTP/2 request pseudo-header fields */ - const unsigned int http_header_strict = (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT); - int scheme = 0; - uint32_t ulen = 0, alen = 0; - const char *uri = NULL; - for (int i = 1; i < hoff[0]; ++i) { - const char *k = ptr + ((i > 1) ? hoff[i] : 0); - /* one past last line hoff[hoff[0]] is to final "\r\n" */ - const char *end = ptr + hoff[i+1]; - - if (*k != ':') - break; - ++k; - const char *colon = memchr(k, ':', end - k); - if (NULL == colon) - return http_request_header_line_invalid(r, 400, "invalid header missing ':' -> 400"); - - const int klen = (int)(colon - k); - if (0 == klen) - return http_request_header_line_invalid(r, 400, "invalid header key -> 400"); - - const char *v = colon + 1; - /* remove leading whitespace from value */ - while (*v == ' ' || *v == '\t') ++v; - #ifdef __COVERITY__ - /*(ptr has at least ::\r\n by now, so end[-2] valid)*/ - force_assert(end >= k + 1); - #endif - /* remove trailing whitespace from value (+ remove '\r\n') */ - if (end[-2] == '\r') - --end; - else if (http_header_strict) - return http_request_header_line_invalid(r, 400, "missing CR before LF in header -> 400"); - --end; - while (v > end && (end[-1] == ' ' || end[-1] == '\t')) --end; +__attribute_cold__ +__attribute_noinline__ +static int http_request_parse_header_other(request_st * const restrict r, const char * const restrict k, const int klen, const unsigned int http_header_strict); - const int vlen = (int)(end - v); - if (vlen <= 0) - return http_request_header_line_invalid(r, 400, "invalid pseudo-header -> 400"); - - switch (klen) { - case 4: - if (0 == memcmp(k, "path", 4)) { - if (NULL != uri) - return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400"); - uri = v; - ulen = (uint32_t)vlen; - continue; - } - break; - case 6: - if (0 == memcmp(k, "method", 6)) { - if (HTTP_METHOD_UNSET != r->http_method) - return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400"); - r->http_method = get_http_method_key(v, vlen); - if (HTTP_METHOD_UNSET >= r->http_method) - return http_request_header_line_invalid(r, 501, "unknown http-method -> 501"); - continue; - } - else if (0 == memcmp(k, "scheme", 6)) { - if (scheme) - return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400"); - switch (vlen) { /*(validated, but then value is ignored)*/ - case 5: /* "https" */ - if (v[4]!='s') break; - __attribute_fallthrough__ - case 4: /* "http" */ - if (v[0]=='h' && v[1]=='t' && v[2]=='t' && v[3]=='p') { - scheme = 1; - continue; - } - break; - default: - break; - } - return http_request_header_line_invalid(r, 400, "unknown pseudo-header scheme -> 400"); - } - break; - case 9: - if (0 == memcmp(k, "authority", 9)) { - if (r->http_host) - return http_request_header_line_invalid(r, 400, "repeated pseudo-header -> 400"); - if (vlen >= 1024) /*(expecting < 256)*/ - return http_request_header_line_invalid(r, 400, "invalid pseudo-header authority too long -> 400"); - alen = (uint32_t)vlen; - /* insert as host header */ - http_header_request_set(r, HTTP_HEADER_HOST, CONST_STR_LEN("host"), v, vlen); - r->http_host = http_header_request_get(r, HTTP_HEADER_HOST, CONST_STR_LEN("Host")); - continue; - } - break; - default: - break; - } - return http_request_header_line_invalid(r, 400, "invalid pseudo-header -> 400"); - } +int +http_request_validate_pseudohdrs (request_st * const restrict r, const int scheme, const unsigned int http_parseopts) +{ /* :method is required to indicate method * CONNECT method must have :method and :authority * All other methods must have at least :method :scheme :path */ if (HTTP_METHOD_UNSET == r->http_method) - return http_request_header_line_invalid(r, 400, "missing pseudo-header method -> 400"); + return http_request_header_line_invalid(r, 400, + "missing pseudo-header method -> 400"); if (HTTP_METHOD_CONNECT != r->http_method) { if (!scheme) - return http_request_header_line_invalid(r, 400, "missing pseudo-header scheme -> 400"); + return http_request_header_line_invalid(r, 400, + "missing pseudo-header scheme -> 400"); - if (NULL == uri) - return http_request_header_line_invalid(r, 400, "missing pseudo-header path -> 400"); + if (buffer_string_is_empty(&r->target)) + return http_request_header_line_invalid(r, 400, + "missing pseudo-header path -> 400"); + const char * const uri = r->target.ptr; if (*uri != '/') { /* (common case: (*uri == '/')) */ - if (*uri != '*' || ulen != 1 || HTTP_METHOD_OPTIONS != r->http_method) - return http_request_header_line_invalid(r, 400, "invalid pseudo-header path -> 400"); + if (uri[0] != '*' || uri[1] != '\0' + || HTTP_METHOD_OPTIONS != r->http_method) + return http_request_header_line_invalid(r, 400, + "invalid pseudo-header path -> 400"); } } else { /* HTTP_METHOD_CONNECT */ if (NULL == r->http_host) - return http_request_header_line_invalid(r, 400, "missing pseudo-header authority -> 400"); - if (NULL != uri || scheme) - return http_request_header_line_invalid(r, 400, "invalid pseudo-header with CONNECT -> 400"); + return http_request_header_line_invalid(r, 400, + "missing pseudo-header authority -> 400"); + if (!buffer_string_is_empty(&r->target) || scheme) + return http_request_header_line_invalid(r, 400, + "invalid pseudo-header with CONNECT -> 400"); /*(reuse uri and ulen to assign to r->target)*/ - uri = r->http_host->ptr; - ulen = alen; + buffer_copy_buffer(&r->target, r->http_host); } + buffer_copy_buffer(&r->target_orig, &r->target); /* r->http_host, if set, is checked with http_request_host_policy() * in http_request_parse() */ - /* copied from end of http_request_parse_reqline() */ + /* copied and modified from end of http_request_parse_reqline() */ /* check uri for invalid characters */ + const unsigned int http_header_strict = + (http_parseopts & HTTP_PARSEOPT_HEADER_STRICT); + if (http_header_strict + && (http_parseopts & HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT)) + return 0; /* URI will be checked in http_request_parse_target() */ + + const uint32_t ulen = buffer_string_length(&r->target); + const uint8_t * const uri = (uint8_t *)r->target.ptr; if (http_header_strict) { - if ((http_parseopts & HTTP_PARSEOPT_URL_NORMALIZE_CTRLS_REJECT)) { - /* URI will be checked in http_request_parse_target() */ - } - else { - for (uint32_t i = 0; i < ulen; ++i) { - if (!request_uri_is_valid_char(uri[i])) - return http_request_header_char_invalid(r, uri[i], "invalid character in URI -> 400"); - } + for (uint32_t i = 0; i < ulen; ++i) { + if (!request_uri_is_valid_char(uri[i])) + return http_request_header_char_invalid(r, uri[i], + "invalid character in URI -> 400"); } } else { - /* check entire set of request headers for '\0' */ - if (NULL != memchr(ptr, '\0', hoff[hoff[0]])) - return http_request_header_char_invalid(r, '\0', "invalid character in header -> 400"); + if (NULL != memchr(uri, '\0', ulen)) + return http_request_header_char_invalid(r, '\0', + "invalid character in header -> 400"); } - buffer_copy_string_len(&r->target, uri, ulen); - buffer_copy_string_len(&r->target_orig, uri, ulen); return 0; } + +int +http_request_parse_header (request_st * const restrict r, http_header_parse_ctx * const restrict hpctx) +{ + const char * const restrict k = hpctx->k; + const char * const restrict v = hpctx->v; + const uint32_t klen = hpctx->klen; + const uint32_t vlen = hpctx->vlen; + + if (0 == klen) + return http_request_header_line_invalid(r, 400, + "invalid header key -> 400"); + if (0 == vlen) + return http_request_header_line_invalid(r, 400, + "invalid header value -> 400"); + + if ((hpctx->hlen += klen + vlen + 4) > hpctx->max_request_field_size) { + /*(configurable with server.max-request-field-size; default 8k)*/ + #if 1 /* emit to error log for people sending large headers */ + log_error(r->conf.errh, __FILE__, __LINE__, + "oversized request header -> 431"); + return 431; /* Request Header Fields Too Large */ + #else + /* 431 Request Header Fields Too Large */ + return http_request_header_line_invalid(r, 431, + "oversized request header -> 431"); + #endif + } + + if (2 == klen && k[0] == 't' && k[1] == 'e' + && !buffer_eq_icase_ss(v, vlen, CONST_STR_LEN("trailers"))) + return http_request_header_line_invalid(r, 400, + "invalid TE header value with HTTP/2 -> 400"); + + if (!hpctx->trailers) { + if (*k == ':') { + /* HTTP/2 request pseudo-header fields */ + if (!hpctx->pseudo) /*(pseudo header after non-pseudo header)*/ + return http_request_header_line_invalid(r, 400, + "invalid pseudo-header -> 400"); + switch (klen-1) { + case 4: + if (0 == memcmp(k+1, "path", 4)) { + if (!buffer_string_is_empty(&r->target)) + return http_request_header_line_invalid(r, 400, + "repeated pseudo-header -> 400"); + buffer_copy_string_len(&r->target, v, vlen); + return 0; + } + break; + case 6: + if (0 == memcmp(k+1, "method", 6)) { + if (HTTP_METHOD_UNSET != r->http_method) + return http_request_header_line_invalid(r, 400, + "repeated pseudo-header -> 400"); + r->http_method = get_http_method_key(v, vlen); + if (HTTP_METHOD_UNSET >= r->http_method) + return http_request_header_line_invalid(r, 501, + "unknown http-method -> 501"); + return 0; + } + else if (0 == memcmp(k+1, "scheme", 6)) { + if (hpctx->scheme) + return http_request_header_line_invalid(r, 400, + "repeated pseudo-header -> 400"); + switch (vlen) {/*(validated, but then ignored)*/ + case 5: /* "https" */ + if (v[4]!='s') break; + __attribute_fallthrough__ + case 4: /* "http" */ + if (v[0]=='h' && v[1]=='t' && v[2]=='t' && v[3]=='p') { + hpctx->scheme = 1; + return 0; + } + break; + default: + break; + } + return http_request_header_line_invalid(r, 400, + "unknown pseudo-header scheme -> 400"); + } + break; + case 9: + if (0 == memcmp(k+1, "authority", 9)) { + if (r->http_host) + return http_request_header_line_invalid(r, 400, + "repeated pseudo-header -> 400"); + if (vlen >= 1024) /*(expecting < 256)*/ + return http_request_header_line_invalid(r, 400, + "invalid pseudo-header authority too long -> 400"); + /* insert as host header */ + http_header_request_set(r, HTTP_HEADER_HOST, + CONST_STR_LEN("host"), v, vlen); + r->http_host = + http_header_request_get(r, HTTP_HEADER_HOST, + CONST_STR_LEN("Host")); + return 0; + } + break; + default: + break; + } + return http_request_header_line_invalid(r, 400, + "invalid pseudo-header -> 400"); + } + else { /*(non-pseudo headers)*/ + if (hpctx->pseudo) { /*(transition to non-pseudo headers)*/ + hpctx->pseudo = 0; + int status = + http_request_validate_pseudohdrs(r, hpctx->scheme, + hpctx->http_parseopts); + if (0 != status) return status; + } + + const unsigned int http_header_strict = + (hpctx->http_parseopts & HTTP_PARSEOPT_HEADER_STRICT); + + for (uint32_t j = 0; j < klen; ++j) { + if ((k[j] >= 'a' && k[j] <= 'z') || k[j] == '-') + continue; /*(common cases)*/ + if (k[j] >= 'A' && k[j] <= 'Z') + return 400; + if (0 != http_request_parse_header_other(r, k+j, klen-j, + http_header_strict)) + return 400; + break; + } + + if (http_header_strict) { + for (uint32_t j = 0; j < vlen; ++j) { + if ((((uint8_t *)v)[j] < 32 && v[j] != '\t') || v[j]==127) + return http_request_header_char_invalid(r, v[j], + "invalid character in header -> 400"); + } + } + else { + if (NULL != memchr(v, '\0', vlen)) + return http_request_header_char_invalid(r, '\0', + "invalid character in header -> 400"); + } + + const enum http_header_e id = http_header_hkey_get(k, klen); + return http_request_parse_single_header(r, id, k, klen, v, vlen); + } + } + else { /*(trailers)*/ + /* ignore trailers (after required HPACK decoding) if streaming + * request body to backend since headers have already been sent + * to backend via Common Gateway Interface (CGI) (CGI, FastCGI, + * SCGI, etc) or HTTP/1.1 (proxy) (mod_proxy does not currently + * support using HTTP/2 to connect to backends) */ + #if 0 /* (if needed, save flag in hpctx instead of fdevent.h dependency)*/ + if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST) + return 0; + #endif + /* Note: do not unconditionally merge into headers since if + * headers had already been sent to backend, then mod_accesslog + * logging of request headers might be inaccurate. + * Many simple backends do not support HTTP/1.1 requests sending + * Transfer-Encoding: chunked, and even those that do might not + * handle trailers. Some backends do not even support HTTP/1.1. + * For all these reasons, ignore trailers if streaming request + * body to backend. Revisit in future if adding support for + * connecting to backends using HTTP/2 (with explicit config + * option to force connecting to backends using HTTP/2) */ + + /* XXX: TODO: request trailers not handled if streaming reqbody + * XXX: must ensure that trailers are not disallowed field-names + */ + + return 0; + } +} + + static int http_request_parse_reqline(request_st * const restrict r, const char * const restrict ptr, const unsigned short * const restrict hoff, const unsigned int http_parseopts) { size_t len = hoff[2]; @@ -982,15 +1077,7 @@ static int http_request_parse_headers(request_st * const restrict r, char * cons } #endif - int i = 2; - if (r->http_version >= HTTP_VERSION_2) { - /* CONNECT must have :method and :authority - * All other methods must have at least :method :scheme :path */ - i += (r->http_method != HTTP_METHOD_CONNECT) ? 2 : 1; - while (ptr[hoff[i]] == ':') ++i; - } - - for (; i < hoff[0]; ++i) { + for (int i = 2; i < hoff[0]; ++i) { const char *k = ptr + hoff[i]; /* one past last line hoff[hoff[0]] is to final "\r\n" */ char *end = ptr + hoff[i+1]; @@ -1090,26 +1177,9 @@ static int http_request_parse_headers(request_st * const restrict r, char * cons static int -http_request_parse (request_st * const restrict r, char * const restrict hdrs, const unsigned short * const restrict hoff, const int scheme_port) +http_request_parse (request_st * const restrict r, const int scheme_port) { - /* - * Request: "^(GET|POST|HEAD|...) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$" - * Header : "^([-a-zA-Z]+): (.+)$" - * End : "^$" - */ - - int status; - const unsigned int http_parseopts = r->conf.http_parseopts; - - status = (r->http_version >= HTTP_VERSION_2) - ? http_request_parse_pseudohdrs(r, hdrs, hoff, http_parseopts) - : http_request_parse_reqline(r, hdrs, hoff, http_parseopts); - if (0 != status) return status; - - status = http_request_parse_target(r, scheme_port); - if (0 != status) return status; - - status = http_request_parse_headers(r, hdrs, hoff, http_parseopts); + int status = http_request_parse_target(r, scheme_port); if (0 != status) return status; /*(r->http_host might not be set until after parsing request headers)*/ @@ -1117,6 +1187,7 @@ http_request_parse (request_st * const restrict r, char * const restrict hdrs, c buffer_to_lower(&r->uri.authority); /* post-processing */ + const unsigned int http_parseopts = r->conf.http_parseopts; /* check hostname field if it is set */ if (r->http_host) { @@ -1172,11 +1243,31 @@ http_request_parse (request_st * const restrict r, char * const restrict hdrs, c } -void -http_request_headers_process (request_st * const restrict r, char * const restrict hdrs, const unsigned short * const restrict hoff, const int scheme_port) +static int +http_request_parse_hoff (request_st * const restrict r, char * const restrict hdrs, const unsigned short * const restrict hoff, const int scheme_port) { - r->http_status = http_request_parse(r, hdrs, hoff, scheme_port); + /* + * Request: "^(GET|POST|HEAD|...) ([^ ]+(\\?[^ ]+|)) (HTTP/1\\.[01])$" + * Header : "^([-a-zA-Z]+): (.+)$" + * End : "^$" + */ + + int status; + const unsigned int http_parseopts = r->conf.http_parseopts; + + status = http_request_parse_reqline(r, hdrs, hoff, http_parseopts); + if (0 != status) return status; + + status = http_request_parse_headers(r, hdrs, hoff, http_parseopts); + if (0 != status) return status; + return http_request_parse(r, scheme_port); +} + + +static void +http_request_headers_fin (request_st * const restrict r) +{ if (0 == r->http_status) { #if 0 r->conditional_is_valid = (1 << COMP_SERVER_SOCKET) @@ -1196,12 +1287,54 @@ http_request_headers_process (request_st * const restrict r, char * const restri else { r->keep_alive = 0; r->reqbody_length = 0; + } +} + +void +http_request_headers_process (request_st * const restrict r, char * const restrict hdrs, const unsigned short * const restrict hoff, const int scheme_port) +{ + r->http_status = http_request_parse_hoff(r, hdrs, hoff, scheme_port); + + http_request_headers_fin(r); + + if (0 != r->http_status) { if (r->conf.log_request_header_on_error) { - /*(http_request_parse() modifies hdrs only to + /*(http_request_parse_headers() modifies hdrs only to * undo line-wrapping in-place using spaces)*/ log_error(r->conf.errh, __FILE__, __LINE__, "request-header:\n%.*s", (int)r->rqst_header_len, hdrs); } } } + + +void +http_request_headers_process_h2 (request_st * const restrict r, const int scheme_port) +{ + if (0 == r->http_status) + r->http_status = http_request_parse(r, scheme_port); + + if (0 == r->http_status) { + if (r->rqst_htags & HTTP_HEADER_CONNECTION) + r->http_status = http_request_header_line_invalid(r, 400, + "invalid Connection header with HTTP/2 -> 400"); + } + + http_request_headers_fin(r); + + #if 0 /* not supported; headers not collected into a single buf for HTTP/2 */ + if (0 != r->http_status) { + if (r->conf.log_request_header_on_error) { + log_error(r->conf.errh, __FILE__, __LINE__, + "request-header:\n%.*s", (int)r->rqst_header_len, hdrs); + } + } + #endif + + /* ignore Upgrade if using HTTP/2 */ + if (r->rqst_htags & HTTP_HEADER_UPGRADE) + http_header_request_unset(r, HTTP_HEADER_UPGRADE, + CONST_STR_LEN("upgrade")); + /* XXX: should filter out other hop-by-hop connection headers, too */ +} diff --git a/src/request.h b/src/request.h index 407202b2..fba62963 100644 --- a/src/request.h +++ b/src/request.h @@ -193,6 +193,23 @@ struct request_st { }; +typedef struct http_header_parse_ctx { + char *k; + char *v; + uint32_t klen; + uint32_t vlen; + uint32_t hlen; + int pseudo; + int scheme; + int trailers; + uint32_t max_request_field_size; + unsigned int http_parseopts; +} http_header_parse_ctx; + + +int http_request_validate_pseudohdrs (request_st * restrict r, int scheme, unsigned int http_parseopts); +int http_request_parse_header (request_st * restrict r, http_header_parse_ctx * restrict hpctx); +void http_request_headers_process_h2 (request_st * restrict r, int scheme_port); void http_request_headers_process (request_st * restrict r, char * restrict hdrs, const unsigned short * restrict hoff, int scheme_port); int http_request_parse_target(request_st *r, int scheme_port); int http_request_host_normalize(buffer *b, int scheme_port); diff --git a/src/t/test_request.c b/src/t/test_request.c index 6801a068..887614c1 100644 --- a/src/t/test_request.c +++ b/src/t/test_request.c @@ -35,11 +35,12 @@ static void run_http_request_parse(request_st * const r, int line, int status, c } --hloffsets[0]; /*(ignore final blank line "\r\n" ending headers)*/ const int proto_default_port = 80; - int http_status = http_request_parse(r,hdrs,hloffsets,proto_default_port); + int http_status = + http_request_parse_hoff(r, hdrs, hloffsets, proto_default_port); if (http_status != status) { fprintf(stderr, "%s.%d: %s() failed: expected '%d', got '%d' for test %s\n", - __FILE__, line, "http_request_parse", status, http_status, + __FILE__, line, "http_request_parse_hoff", status, http_status, desc); fflush(stderr); abort(); |