From: Maxime Henrion Date: Sat, 23 May 2026 01:18:47 +0000 (-0400) Subject: MINOR: http: add two header parsing functions X-Git-Url: http://git.kaiwu.me/http/static/gitweb.js?a=commitdiff_plain;h=ef73a1f64a2ffb3907897d1db297884dbf96ef27;p=haproxy.git MINOR: http: add two header parsing functions 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. --- diff --git a/include/haproxy/http-t.h b/include/haproxy/http-t.h index 5703f38b1..94bc12670 100644 --- a/include/haproxy/http-t.h +++ b/include/haproxy/http-t.h @@ -58,6 +58,10 @@ #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, diff --git a/include/haproxy/http.h b/include/haproxy/http.h index 8ac549d72..aebc524ea 100644 --- a/include/haproxy/http.h +++ b/include/haproxy/http.h @@ -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, diff --git a/src/http.c b/src/http.c index b58185a26..421fe9b23 100644 --- a/src/http.c +++ b/src/http.c @@ -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. must point to the unconsumed portion of the list. 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 + * . + * + * 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 ; callers requiring a + * non-empty value must check it themselves. + * + * On success, and are set to the parsed parameter, and + * 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. + * must point to the unconsumed portion of the list. + * + * On success, is set to the parsed value (trimmed of surrounding + * whitespace), and 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 and . 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