[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:
parent
ada09a23b0
commit
8fc8ab891a
177
src/h2.c
177
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)
|
||||
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) {
|
||||
|
|
439
src/request.c
439
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");
|
||||
__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 */
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue