MEDIUM: h2: always parse and deduplicate the content-length header
The header used to be parsed only in HTX but not in legacy. And even in
HTX mode, the value was dropped. Let's always parse it and report the
parsed value back so that we'll be able to store it in the streams.
diff --git a/include/common/h2.h b/include/common/h2.h
index 1ef57b5..71be8ce 100644
--- a/include/common/h2.h
+++ b/include/common/h2.h
@@ -181,11 +181,11 @@
/* various protocol processing functions */
-int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf);
+int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len);
int h2_make_h1_trailers(struct http_hdr *list, char *out, int osize);
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);
+int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len);
+int h2_make_htx_response(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len);
int h2_make_htx_trailers(struct http_hdr *list, struct htx *htx);
/*
diff --git a/src/h2.c b/src/h2.c
index 72b2dc6..1a103e4 100644
--- a/src/h2.c
+++ b/src/h2.c
@@ -130,7 +130,7 @@
* The Cookie header will be reassembled at the end, and for this, the <list>
* will be used to create a linked list, so its contents may be destroyed.
*/
-int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf)
+int h2_make_h1_request(struct http_hdr *list, char *out, int osize, unsigned int *msgf, unsigned long long *body_len)
{
struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
char *out_end = out + osize;
@@ -191,9 +191,14 @@
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;
+
+ if (ret == 0)
+ continue; // skip this duplicate
+ }
/* these ones are forbidden in requests (RFC7540#8.1.2.2) */
if (isteq(list[idx].n, ist("connection")) ||
@@ -556,7 +561,7 @@
* The Cookie header will be reassembled at the end, and for this, the <list>
* will be used to create a linked list, so its contents may be destroyed.
*/
-int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf)
+int h2_make_htx_request(struct http_hdr *list, struct htx *htx, unsigned int *msgf, unsigned long long *body_len)
{
struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
uint32_t fields; /* bit mask of H2_PHDR_FND_* */
@@ -567,7 +572,6 @@
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;
@@ -620,7 +624,7 @@
fields |= H2_PHDR_FND_HOST;
if (isteq(list[idx].n, ist("content-length"))) {
- ret = h2_parse_cont_len_header(msgf, &list[idx].v, &body_len);
+ ret = h2_parse_cont_len_header(msgf, &list[idx].v, body_len);
if (ret < 0)
goto fail;
@@ -784,7 +788,7 @@
* - in all cases except the end of list, v.name and v.len must designate a
* valid value.
*/
-int h2_make_htx_response(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, unsigned long long *body_len)
{
struct ist phdr_val[H2_PHDR_NUM_ENTRIES];
uint32_t fields; /* bit mask of H2_PHDR_FND_* */
@@ -794,7 +798,6 @@
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++) {
@@ -843,7 +846,7 @@
}
if (isteq(list[idx].n, ist("content-length"))) {
- ret = h2_parse_cont_len_header(msgf, &list[idx].v, &body_len);
+ ret = h2_parse_cont_len_header(msgf, &list[idx].v, body_len);
if (ret < 0)
goto fail;
diff --git a/src/mux_h2.c b/src/mux_h2.c
index 5754f79..c0b6230 100644
--- a/src/mux_h2.c
+++ b/src/mux_h2.c
@@ -270,7 +270,7 @@
static int h2_process(struct h2c *h2c);
static struct task *h2_io_cb(struct task *t, void *ctx, unsigned short state);
static inline struct h2s *h2c_st_by_id(struct h2c *h2c, int id);
-static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags);
+static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len);
static int h2_frt_transfer_data(struct h2s *h2s);
static struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned short state);
static struct h2s *h2c_bck_stream_new(struct h2c *h2c, struct conn_stream *cs, struct session *sess);
@@ -1893,6 +1893,7 @@
static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
{
struct buffer rxbuf = BUF_NULL;
+ unsigned long long body_len = 0;
uint32_t flags = 0;
int error;
@@ -1913,7 +1914,7 @@
if (h2s->st != H2_SS_IDLE) {
/* The stream exists/existed, this must be a trailers frame */
if (h2s->st != H2_SS_CLOSED) {
- if (h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags) <= 0)
+ if (h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &body_len) <= 0)
goto out;
goto done;
}
@@ -1930,7 +1931,7 @@
else if (h2c->flags & H2_CF_DEM_TOOMANY)
goto out; // IDLE but too many cs still present
- error = h2c_decode_headers(h2c, &rxbuf, &flags);
+ error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
/* unrecoverable error ? */
if (h2c->st0 >= H2_CS_ERROR)
@@ -2001,6 +2002,7 @@
*/
static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s)
{
+ unsigned long long body_len = 0;
int error;
if (!h2c->dfl) {
@@ -2016,7 +2018,7 @@
if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf))
return NULL; // incomplete frame
- error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags);
+ error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &body_len);
/* unrecoverable error ? */
if (h2c->st0 >= H2_CS_ERROR)
@@ -3271,7 +3273,7 @@
* decoding, in order to detect if we're dealing with a headers or a trailers
* block (the trailers block appears after H2_SF_HEADERS_RCVD was seen).
*/
-static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags)
+static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len)
{
const uint8_t *hdrs = (uint8_t *)b_head(&h2c->dbuf);
struct buffer *tmp = get_trash_chunk();
@@ -3431,12 +3433,12 @@
if (htx) {
/* HTX mode */
if (h2c->flags & H2_CF_IS_BACK)
- outlen = h2_make_htx_response(list, htx, &msgf);
+ outlen = h2_make_htx_response(list, htx, &msgf, body_len);
else
- outlen = h2_make_htx_request(list, htx, &msgf);
+ outlen = h2_make_htx_request(list, htx, &msgf, body_len);
} else {
/* HTTP/1 mode */
- outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf);
+ outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body_len);
if (outlen > 0)
b_add(rxbuf, outlen);
}