summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlenn Strauss <gstrauss@gluelogic.com>2020-08-27 02:50:13 -0400
committerGlenn Strauss <gstrauss@gluelogic.com>2020-10-11 11:43:06 -0400
commit8fc8ab891a570fe48fcb51d21be0299bc25d9c34 (patch)
tree83dc8035c9ac43a78d996a5bbb7e238d030a582d
parentada09a23b029731e24df65a39b11c4d3f90f59a0 (diff)
downloadlighttpd-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.c177
-rw-r--r--src/request.c439
-rw-r--r--src/request.h17
-rw-r--r--src/t/test_request.c5
4 files changed, 351 insertions, 287 deletions
diff --git a/src/h2.c b/src/h2.c
index 4f60023c..46c6bf31 100644
--- a/src/h2.c
+++ b/src/h2.c
@@ -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();