MINOR: h1: Change T-E header parsing to fail if chunked encoding is found twice
According to the RFC7230, "chunked" encoding must not be applied more than
once to a message body. To handle this case, h1_parse_xfer_enc_header() is
now responsible to fail when a parsing error is found. It also fails if the
"chunked" encoding is not the last one for a request.
To help the parsing, two H1 parser flags have been added: H1_MF_TE_CHUNKED
and H1_MF_TE_OTHER. These flags are set, respectively, when "chunked"
encoding and any other encoding are found. H1_MF_CHNK flag is used when
"chunked" encoding is the last one.
diff --git a/include/haproxy/h1.h b/include/haproxy/h1.h
index 858db7d..7ebd9c8 100644
--- a/include/haproxy/h1.h
+++ b/include/haproxy/h1.h
@@ -81,7 +81,7 @@
/* HTTP/1 message flags (32 bit), for use in h1m->flags only */
#define H1_MF_NONE 0x00000000
#define H1_MF_CLEN 0x00000001 // content-length present
-#define H1_MF_CHNK 0x00000002 // chunk present, exclusive with c-l
+#define H1_MF_CHNK 0x00000002 // chunk present (as last encoding), exclusive with c-l
#define H1_MF_RESP 0x00000004 // this message is the response message
#define H1_MF_TOLOWER 0x00000008 // turn the header names to lower case
#define H1_MF_VER_11 0x00000010 // message indicates version 1.1 or above
@@ -96,6 +96,8 @@
#define H1_MF_METH_CONNECT 0x00002000 // Set for a response to a CONNECT request
#define H1_MF_METH_HEAD 0x00004000 // Set for a response to a HEAD request
#define H1_MF_UPG_WEBSOCKET 0x00008000 // Set for a Websocket upgrade handshake
+#define H1_MF_TE_CHUNKED 0x00010000 // T-E "chunked"
+#define H1_MF_TE_OTHER 0x00020000 // T-E other than supported ones found (only "chunked" is supported for now)
/* Note: for a connection to be persistent, we need this for the request :
* - one of CLEN or CHNK
@@ -146,7 +148,7 @@
int h1_measure_trailers(const struct buffer *buf, unsigned int ofs, unsigned int max);
int h1_parse_cont_len_header(struct h1m *h1m, struct ist *value);
-void h1_parse_xfer_enc_header(struct h1m *h1m, struct ist value);
+int h1_parse_xfer_enc_header(struct h1m *h1m, struct ist value);
void h1_parse_connection_header(struct h1m *h1m, struct ist *value);
void h1_parse_upgrade_header(struct h1m *h1m, struct ist value);
diff --git a/src/h1.c b/src/h1.c
index e7334fa..e0ba8d7 100644
--- a/src/h1.c
+++ b/src/h1.c
@@ -93,19 +93,21 @@
}
/* Parse the Transfer-Encoding: header field of an HTTP/1 request, looking for
- * "chunked" being the last value, and setting H1_MF_CHNK in h1m->flags only in
- * this case. Any other token found or any empty header field found will reset
- * this flag, so that it accurately represents the token's presence at the last
- * position. The H1_MF_XFER_ENC flag is always set. Note that transfer codings
- * are case-insensitive (cf RFC7230#4).
+ * "chunked" encoding to perform some checks (it must be the last encoding for
+ * the request and must not be performed twice for any message). The
+ * H1_MF_TE_CHUNKED is set if a valid "chunked" encoding is found. The
+ * H1_MF_TE_OTHER flag is set if any other encoding is found. The H1_MF_XFER_ENC
+ * flag is always set. The H1_MF_CHNK is set when "chunked" encoding is the last
+ * one. Note that transfer codings are case-insensitive (cf RFC7230#4). This
+ * function returns <0 if a error is found, 0 if the whole header can be dropped
+ * (not used yet), or >0 if the value can be indexed.
*/
-void h1_parse_xfer_enc_header(struct h1m *h1m, struct ist value)
+int h1_parse_xfer_enc_header(struct h1m *h1m, struct ist value)
{
char *e, *n;
struct ist word;
h1m->flags |= H1_MF_XFER_ENC;
- h1m->flags &= ~H1_MF_CHNK;
word.ptr = value.ptr - 1; // -1 for next loop's pre-increment
e = value.ptr + value.len;
@@ -123,11 +125,36 @@
word.len--;
h1m->flags &= ~H1_MF_CHNK;
- if (isteqi(word, ist("chunked")))
- h1m->flags |= H1_MF_CHNK;
+ if (isteqi(word, ist("chunked"))) {
+ if (h1m->flags & H1_MF_TE_CHUNKED) {
+ /* cf RFC7230#3.3.1 : A sender MUST NOT apply
+ * chunked more than once to a message body
+ * (i.e., chunking an already chunked message is
+ * not allowed)
+ */
+ goto fail;
+ }
+ h1m->flags |= (H1_MF_TE_CHUNKED|H1_MF_CHNK);
+ }
+ else {
+ if ((h1m->flags & (H1_MF_RESP|H1_MF_TE_CHUNKED)) == H1_MF_TE_CHUNKED) {
+ /* cf RFC7230#3.3.1 : If any transfer coding
+ * other than chunked is applied to a request
+ * payload body, the sender MUST apply chunked
+ * as the final transfer coding to ensure that
+ * the message is properly framed.
+ */
+ goto fail;
+ }
+ h1m->flags |= H1_MF_TE_OTHER;
+ }
word.ptr = n;
}
+
+ return 1;
+ fail:
+ return -1;
}
/* Parse the Connection: header of an HTTP/1 request, looking for "close",
@@ -843,7 +870,16 @@
}
if (isteqi(n, ist("transfer-encoding"))) {
- h1_parse_xfer_enc_header(h1m, v);
+ ret = h1_parse_xfer_enc_header(h1m, v);
+ if (ret < 0) {
+ state = H1_MSG_HDR_L2_LWS;
+ ptr = v.ptr; /* Set ptr on the error */
+ goto http_msg_invalid;
+ }
+ else if (ret == 0) {
+ /* skip it */
+ break;
+ }
}
else if (isteqi(n, ist("content-length"))) {
ret = h1_parse_cont_len_header(h1m, &v);
diff --git a/src/hlua.c b/src/hlua.c
index e997964..4ccd8c5 100644
--- a/src/hlua.c
+++ b/src/hlua.c
@@ -5355,8 +5355,19 @@
value = lua_tolstring(L, -1, &vlen);
/* Simple Protocol checks. */
- if (isteqi(ist2(name, nlen), ist("transfer-encoding")))
- h1_parse_xfer_enc_header(&h1m, ist2(value, vlen));
+ if (isteqi(ist2(name, nlen), ist("transfer-encoding"))) {
+ int ret;
+
+ ret = h1_parse_xfer_enc_header(&h1m, ist2(value, vlen));
+ if (ret < 0) {
+ hlua_pusherror(L, "Lua applet http '%s': Invalid '%s' header.\n",
+ luactx->appctx->rule->arg.hlua_rule->fcn->name,
+ name);
+ WILL_LJMP(lua_error(L));
+ }
+ else if (ret == 0)
+ goto next; /* Skip it */
+ }
else if (isteqi(ist2(name, nlen), ist("content-length"))) {
struct ist v = ist2(value, vlen);
int ret;