[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
This commit is contained in:
Glenn Strauss 2020-08-27 02:50:13 -04:00
parent ada09a23b0
commit 8fc8ab891a
4 changed files with 351 additions and 287 deletions

177
src/h2.c
View File

@ -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)
h2_parse_headers_frame (request_st * const restrict r, const unsigned char *psrc, const uint32_t plen, const int trailers)
{
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)
{
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) {

View File

@ -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");
__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 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;
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 */
}

View File

@ -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);

View File

@ -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();