mirror of
https://git.haproxy.org/git/haproxy.git/
synced 2025-09-21 13:51:26 +02:00
MEDIUM: h2: properly check and deduplicate the content-length header in HTX
When producing an HTX message, we can't rely on the next-level H1 parser to check and deduplicate the content-length header, so we have to do it while parsing a message. The algorithm is the exact same as used for H1 messages.
This commit is contained in:
parent
78f5ff86da
commit
beefaee4f5
@ -155,6 +155,7 @@ enum h2_err {
|
||||
/* various protocol processing functions */
|
||||
|
||||
int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf);
|
||||
int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len);
|
||||
int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf);
|
||||
int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf);
|
||||
|
||||
|
94
src/h2.c
94
src/h2.c
@ -320,6 +320,78 @@ int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Parse the Content-Length header field of an HTTP/2 request. The function
|
||||
* checks all possible occurrences of a comma-delimited value, and verifies
|
||||
* if any of them doesn't match a previous value. It returns <0 if a value
|
||||
* differs, 0 if the whole header can be dropped (i.e. already known), or >0
|
||||
* if the value can be indexed (first one). In the last case, the value might
|
||||
* be adjusted and the caller must only add the updated value.
|
||||
*/
|
||||
int h2_parse_cont_len_header(unsigned int *msgf, struct ist *value, unsigned long long *body_len)
|
||||
{
|
||||
char *e, *n;
|
||||
unsigned long long cl;
|
||||
int not_first = !!(*msgf & H2_MSGF_BODY_CL);
|
||||
struct ist word;
|
||||
|
||||
word.ptr = value->ptr - 1; // -1 for next loop's pre-increment
|
||||
e = value->ptr + value->len;
|
||||
|
||||
while (++word.ptr < e) {
|
||||
/* skip leading delimitor and blanks */
|
||||
if (unlikely(HTTP_IS_LWS(*word.ptr)))
|
||||
continue;
|
||||
|
||||
/* digits only now */
|
||||
for (cl = 0, n = word.ptr; n < e; n++) {
|
||||
unsigned int c = *n - '0';
|
||||
if (unlikely(c > 9)) {
|
||||
/* non-digit */
|
||||
if (unlikely(n == word.ptr)) // spaces only
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
if (unlikely(cl > ULLONG_MAX / 10ULL))
|
||||
goto fail; /* multiply overflow */
|
||||
cl = cl * 10ULL;
|
||||
if (unlikely(cl + c < cl))
|
||||
goto fail; /* addition overflow */
|
||||
cl = cl + c;
|
||||
}
|
||||
|
||||
/* keep a copy of the exact cleaned value */
|
||||
word.len = n - word.ptr;
|
||||
|
||||
/* skip trailing LWS till next comma or EOL */
|
||||
for (; n < e; n++) {
|
||||
if (!HTTP_IS_LWS(*n)) {
|
||||
if (unlikely(*n != ','))
|
||||
goto fail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* if duplicate, must be equal */
|
||||
if (*msgf & H2_MSGF_BODY_CL && cl != *body_len)
|
||||
goto fail;
|
||||
|
||||
/* OK, store this result as the one to be indexed */
|
||||
*msgf |= H2_MSGF_BODY_CL;
|
||||
*body_len = cl;
|
||||
*value = word;
|
||||
word.ptr = n;
|
||||
}
|
||||
/* here we've reached the end with a single value or a series of
|
||||
* identical values, all matching previous series if any. The last
|
||||
* parsed value was sent back into <value>. We just have to decide
|
||||
* if this occurrence has to be indexed (it's the first one) or
|
||||
* silently skipped (it's not the first one)
|
||||
*/
|
||||
return !not_first;
|
||||
fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Prepare the request line into <htx> from pseudo headers stored in <phdr[]>.
|
||||
* <fields> indicates what was found so far. This should be called once at the
|
||||
* detection of the first general header field or at the end of the request if
|
||||
@ -424,6 +496,7 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
|
||||
int i;
|
||||
struct htx_sl *sl = NULL;
|
||||
unsigned int sl_flags = 0;
|
||||
unsigned long long body_len;
|
||||
|
||||
lck = ck = -1; // no cookie for now
|
||||
fields = 0;
|
||||
@ -475,10 +548,14 @@ int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *ms
|
||||
if (isteq(list[idx].n, ist("host")))
|
||||
fields |= H2_PHDR_FND_HOST;
|
||||
|
||||
if ((*msgf & (H2_MSGF_BODY|H2_MSGF_BODY_TUNNEL|H2_MSGF_BODY_CL)) == H2_MSGF_BODY &&
|
||||
isteq(list[idx].n, ist("content-length"))) {
|
||||
*msgf |= H2_MSGF_BODY_CL;
|
||||
if (isteq(list[idx].n, ist("content-length"))) {
|
||||
ret = h2_parse_cont_len_header(msgf, &list[idx].v, &body_len);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
sl_flags |= HTX_SL_F_CLEN;
|
||||
if (ret == 0)
|
||||
continue; // skip this duplicate
|
||||
}
|
||||
|
||||
/* these ones are forbidden in requests (RFC7540#8.1.2.2) */
|
||||
@ -646,6 +723,7 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m
|
||||
int i;
|
||||
struct htx_sl *sl = NULL;
|
||||
unsigned int sl_flags = 0;
|
||||
unsigned long long body_len;
|
||||
|
||||
fields = 0;
|
||||
for (idx = 0; list[idx].n.len != 0; idx++) {
|
||||
@ -693,10 +771,14 @@ int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *m
|
||||
fields |= H2_PHDR_FND_NONE;
|
||||
}
|
||||
|
||||
if ((*msgf & (H2_MSGF_BODY|H2_MSGF_BODY_TUNNEL|H2_MSGF_BODY_CL)) == H2_MSGF_BODY &&
|
||||
isteq(list[idx].n, ist("content-length"))) {
|
||||
*msgf |= H2_MSGF_BODY_CL;
|
||||
if (isteq(list[idx].n, ist("content-length"))) {
|
||||
ret = h2_parse_cont_len_header(msgf, &list[idx].v, &body_len);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
sl_flags |= HTX_SL_F_CLEN;
|
||||
if (ret == 0)
|
||||
continue; // skip this duplicate
|
||||
}
|
||||
|
||||
/* these ones are forbidden in responses (RFC7540#8.1.2.2) */
|
||||
|
Loading…
x
Reference in New Issue
Block a user