]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: h2: properly check and deduplicate the content-length header in HTX
authorWilly Tarreau <w@1wt.eu>
Wed, 19 Dec 2018 12:08:08 +0000 (13:08 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 19 Dec 2018 12:08:08 +0000 (13:08 +0100)
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.

include/common/h2.h
src/h2.c

index e0684270ca4ee686bc305c2a5a02e2606fce21a6..acc80c017d4c2c6cf5f326bfeb123ccd1b5423c4 100644 (file)
@@ -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);
 
index 883075262bdd3803a45b96db232843b051e017b0..7c6cbeeac05eb96c1a016a406033abfbdad9b2e1 100644 (file)
--- a/src/h2.c
+++ b/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) */