]> git.kaiwu.me - haproxy.git/commitdiff
MINOR: http: add two header parsing functions
authorMaxime Henrion <mhenrion@haproxy.com>
Sat, 23 May 2026 01:18:47 +0000 (21:18 -0400)
committerWilly Tarreau <w@1wt.eu>
Fri, 10 Jul 2026 14:42:35 +0000 (16:42 +0200)
Add http_next_hdr_value() to iterate over the comma-separated values
that some headers contain. Add http_get_hdr_param() to iterate over the
';'-separated parameters that may follow a value.

Currently unused but will be used for parsing Link headers in the cache
to support 103 Early Hints, and could also clean up Cache-Control
parsing among others.

include/haproxy/http-t.h
include/haproxy/http.h
src/http.c

index 5703f38b1c1b1bd62796e70e44879d6fd63098af..94bc12670ea76967c4283d34fd16c7c3d1946cb7 100644 (file)
 #define HTTP_IS_VER_TOKEN(x) (http_char_classes[(uint8_t)(x)] & HTTP_FLG_VER)
 #define HTTP_IS_DIGIT(x)     (http_char_classes[(uint8_t)(x)] & HTTP_FLG_DIG)
 
+/* Flags for http_get_hdr_param() */
+#define HTTP_PARAM_BADWS  0x00000001  /* tolerate whitespace around '=' (e.g. "Link") */
+#define HTTP_PARAM_NOVAL  0x00000002  /* accept valueless parameters (no '=', e.g. "crossorigin") */
+
 /* Known HTTP methods */
 enum http_meth_t {
        HTTP_METH_OPTIONS,
index 8ac549d72c1e8b25823b0f49172bef251c7edcf7..aebc524eab23730b7aa74770ecea28add82975dd 100644 (file)
@@ -52,6 +52,8 @@ int http_parse_cont_len_header(struct ist *value, unsigned long long *body_len,
 int http_header_match2(const char *hdr, const char *end,
                        const char *name, int len);
 char *http_find_hdr_value_end(char *s, const char *e);
+int http_get_hdr_param(struct ist *params, struct ist *name, struct ist *value, int flags);
+int http_next_hdr_value(struct ist *iter, struct ist *value);
 char *http_find_cookie_value_end(char *s, const char *e);
 char *http_extract_cookie_value(char *hdr, const char *hdr_end,
                                 char *cookie_name, size_t cookie_name_l,
index b58185a26a4360ddd2f5b3aba2882d28c5e8be48..421fe9b23ca6efc9da04ee2dbe1cdedefe4acaf0 100644 (file)
@@ -912,6 +912,133 @@ char *http_find_hdr_value_end(char *s, const char *e)
        return s;
 }
 
+/* Extract the next parameter from a ';'-separated parameter list in a header
+ * value. <params> must point to the unconsumed portion of the list. <flags> is
+ * a combination of HTTP_PARAM_* values relaxing the otherwise strict parser:
+ *
+ *   - HTTP_PARAM_BADWS : tolerate "bad whitespace" around '=', as historically
+ *                        tolerated in the "Link" header ;
+ *   - HTTP_PARAM_NOVAL : accept a valueless parameter, i.e. one with no '='
+ *                        (e.g. "crossorigin"), which then returns an empty
+ *                        <value>.
+ *
+ * Without these flags the stricter generic HTTP grammar applies: no whitespace
+ * is allowed around '=', and a parameter must carry a '='. An empty value after
+ * '=' (e.g. "key=") is still returned as an empty <value>; callers requiring a
+ * non-empty value must check it themselves.
+ *
+ * On success, <name> and <value> are set to the parsed parameter, and <params>
+ * is advanced so the next call returns the following one.
+ *
+ * Whitespace at the boundary between parameters is absorbed at either the
+ * end of one call (trailing-of-value) or the start of the next (leading-of-key).
+ * The two are equivalent and either is sufficient on its own.
+ *
+ * Returns 1 on success, 0 when no more parameters remain and -1 when we
+ * encounter malformed input.
+ */
+int http_get_hdr_param(struct ist *params, struct ist *name, struct ist *value, int flags)
+{
+       const char *p = istptr(*params), *end = istend(*params);
+       const char *kbeg, *kend, *vbeg, *vend;
+
+       while (p < end && (HTTP_IS_LWS(*p) || *p == ';'))
+               p++;
+       if (p >= end)
+               return 0;
+
+       kbeg = p;
+       while (p < end && HTTP_IS_TOKEN(*p))
+               p++;
+       kend = p;
+
+       if (kbeg == kend)
+               return -1;
+
+       if (flags & HTTP_PARAM_BADWS) {
+               while (p < end && HTTP_IS_LWS(*p))
+                       p++;
+       }
+
+       if (p < end && *p == '=') {
+               p++;
+               if (flags & HTTP_PARAM_BADWS) {
+                       while (p < end && HTTP_IS_LWS(*p))
+                               p++;
+               }
+               if (p < end && *p == '"') {
+                       vbeg = ++p;
+                       while (p < end && *p != '"') {
+                               /* Skip quoted-pairs (RFC 9110#5.6.4) so that an
+                                * escaped '"' does not close the string. The
+                                * value is returned in its escaped form.
+                                */
+                               if (*p == '\\' && p + 1 < end)
+                                       p++;
+                               p++;
+                       }
+                       if (p == end)
+                               return -1;
+                       vend = p;
+                       p++;
+               } else {
+                       vbeg = p;
+                       while (p < end && HTTP_IS_TOKEN(*p))
+                               p++;
+                       vend = p;
+               }
+       } else {
+               if (!(flags & HTTP_PARAM_NOVAL))
+                       return -1;
+               vbeg = vend = p;
+       }
+
+       /* After a value, only optional whitespace and then a delimiter is
+        * acceptable. We completely reject the value otherwise.
+        */
+       while (p < end && HTTP_IS_LWS(*p))
+               p++;
+
+       if (p < end && *p != ';')
+               return -1;
+
+       *name = ist2(kbeg, kend - kbeg);
+       *value = ist2(vbeg, vend - vbeg);
+       *params = ist2(p, end - p);
+       return 1;
+}
+
+/* Extract the next value from a ','-separated value list in a header value.
+ * <iter> must point to the unconsumed portion of the list.
+ *
+ * On success, <value> is set to the parsed value (trimmed of surrounding
+ * whitespace), and <iter> is advanced so the next call returns the following
+ * one. Empty list elements are skipped, and an empty list yields no value at
+ * all, as mandated for recipients by RFC 9110 section 5.6.1.
+ *
+ * Returns 1 on success, 0 when no more values remain.
+ */
+int http_next_hdr_value(struct ist *iter, struct ist *value)
+{
+       const char *p = istptr(*iter), *end = istend(*iter);
+       char *next;
+       struct ist v;
+
+       while (p < end && (HTTP_IS_LWS(*p) || *p == ','))
+               p++;
+       if (p == end)
+               return 0;
+
+       next = http_find_hdr_value_end((char *)p, (char *)end);
+       v = ist2(p, next - p);
+       while (v.len && HTTP_IS_LWS(v.ptr[v.len - 1]))
+               v.len--;
+
+       *iter = ist2(next, end - next);
+       *value = v;
+       return 1;
+}
+
 /* Find the end of a cookie value contained between <s> and <e>. It works the
  * same way as with headers above except that the semi-colon also ends a token.
  * See RFC2965 for more information. Note that it requires a valid header to