Commit b7bc7f56 authored by Dridi Boukelmoune's avatar Dridi Boukelmoune

http2_hpack: Reorganize header addition for clarity

Instead of passing both a decoder and individual decoder fields, the
signature for h2h_addhdr() changed to only take the decoder. The order
of parameters is destination first, then the source following the
calling conventon of functions like memcpy().

Internally the function is reorganized with a bunch of txt variables to
keep track of the header being added, its name and value. In addition to
clarity, this also helps improve safety and correctness.

For example the :authority pseudo-header name is erased in place to turn
it into a regular host header, but having a dedicated txt for the header
name allows its preservation.
parent 7c684d90
...@@ -39,6 +39,18 @@ ...@@ -39,6 +39,18 @@
#include "http2/cache_http2.h" #include "http2/cache_http2.h"
#include "vct.h" #include "vct.h"
static void
h2h_assert_ready(struct h2h_decode *d)
{
CHECK_OBJ_NOTNULL(d, H2H_DECODE_MAGIC);
AN(d->out);
assert(d->namelen >= 2); /* 2 chars from the ": " that we added */
assert(d->namelen <= d->out_u);
assert(d->out[d->namelen - 2] == ':');
assert(d->out[d->namelen - 1] == ' ');
}
// rfc9113,l,2493,2528 // rfc9113,l,2493,2528
static h2_error static h2_error
h2h_checkhdr(const struct http *hp, const char *b, size_t namelen, size_t len) h2h_checkhdr(const struct http *hp, const char *b, size_t namelen, size_t len)
...@@ -127,132 +139,130 @@ h2h_checkhdr(const struct http *hp, const char *b, size_t namelen, size_t len) ...@@ -127,132 +139,130 @@ h2h_checkhdr(const struct http *hp, const char *b, size_t namelen, size_t len)
} }
static h2_error static h2_error
h2h_addhdr(struct h2h_decode *d, struct http *hp, char *b, size_t namelen, h2h_addhdr(struct http *hp, struct h2h_decode *d)
size_t len)
{ {
/* XXX: This might belong in cache/cache_http.c */ /* XXX: This might belong in cache/cache_http.c */
const char *b0; txt hdr, nm, val;
int disallow_empty; int disallow_empty;
const char *p;
unsigned n; unsigned n;
char *p;
unsigned u;
CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC); CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
AN(b); h2h_assert_ready(d);
assert(namelen >= 2); /* 2 chars from the ': ' that we added */
assert(namelen <= len); /* Assume hdr is by default a regular header from what we decoded. */
hdr.b = d->out;
hdr.e = hdr.b + d->out_u;
n = hp->nhd;
/* nm and val are separated by ": " */
nm.b = hdr.b;
nm.e = nm.b + d->namelen - 2;
val.b = nm.e + 2;
val.e = hdr.e;
disallow_empty = 0; disallow_empty = 0;
if (len > UINT_MAX) { /* XXX: cache_param max header size */ if (Tlen(hdr) > UINT_MAX) { /* XXX: cache_param max header size */
VSLb(hp->vsl, SLT_BogoHeader, "Header too large: %.20s", b); VSLb(hp->vsl, SLT_BogoHeader, "Header too large: %.20s", hdr.b);
return (H2SE_ENHANCE_YOUR_CALM); return (H2SE_ENHANCE_YOUR_CALM);
} }
b0 = b; if (*nm.b == ':') {
if (b[0] == ':') {
/* Match H/2 pseudo headers */ /* Match H/2 pseudo headers */
/* XXX: Should probably have some include tbl for /* XXX: Should probably have some include tbl for
pseudo-headers */ pseudo-headers */
if (!strncmp(b, ":method: ", namelen)) { if (!Tstrcmp(nm, ":method")) {
b += namelen; hdr.b = val.b;
len -= namelen;
n = HTTP_HDR_METHOD; n = HTTP_HDR_METHOD;
disallow_empty = 1; disallow_empty = 1;
/* First field cannot contain SP or CTL */ /* Check HTTP token */
for (p = b, u = 0; u < len; p++, u++) { for (p = hdr.b; p < hdr.e; p++) {
if (vct_issp(*p) || vct_isctl(*p)) if (!vct_istchar(*p))
return (H2SE_PROTOCOL_ERROR); return (H2SE_PROTOCOL_ERROR);
} }
} else if (!strncmp(b, ":path: ", namelen)) { } else if (!Tstrcmp(nm, ":path")) {
b += namelen; hdr.b = val.b;
len -= namelen;
n = HTTP_HDR_URL; n = HTTP_HDR_URL;
disallow_empty = 1; disallow_empty = 1;
// rfc9113,l,2693,2705 // rfc9113,l,2693,2705
if (len > 0 && *b != '/' && if (Tlen(val) > 0 && *val.b != '/' &&
strncmp(b, "*", len) != 0) { Tstrcmp(val, "*")) {
VSLb(hp->vsl, SLT_BogoHeader, VSLb(hp->vsl, SLT_BogoHeader,
"Illegal :path pseudo-header %.*s", "Illegal :path pseudo-header %.*s",
(int)len, b); (int)Tlen(val), val.b);
return (H2SE_PROTOCOL_ERROR); return (H2SE_PROTOCOL_ERROR);
} }
/* Second field cannot contain LWS or CTL */ /* Path cannot contain LWS or CTL */
for (p = b, u = 0; u < len; p++, u++) { for (p = hdr.b; p < hdr.e; p++) {
if (vct_islws(*p) || vct_isctl(*p)) if (vct_islws(*p) || vct_isctl(*p))
return (H2SE_PROTOCOL_ERROR); return (H2SE_PROTOCOL_ERROR);
} }
} else if (!strncmp(b, ":scheme: ", namelen)) { } else if (!Tstrcmp(nm, ":scheme")) {
/* XXX: What to do about this one? (typically /* XXX: What to do about this one? (typically
"http" or "https"). For now set it as a normal "http" or "https"). For now set it as a normal
header, stripping the first ':'. */ header, stripping the first ':'. */
if (d->has_scheme) { if (d->has_scheme) {
VSLb(hp->vsl, SLT_BogoHeader, VSLb(hp->vsl, SLT_BogoHeader,
"Duplicate pseudo-header %.*s%.*s", "Duplicate pseudo-header :scheme: %.*s",
(int)namelen, b0, vmin_t(int, Tlen(val), 20), val.b);
(int)(len > 20 ? 20 : len), b);
return (H2SE_PROTOCOL_ERROR); return (H2SE_PROTOCOL_ERROR);
} }
b++; hdr.b++;
len-=1;
n = hp->nhd;
d->has_scheme = 1; d->has_scheme = 1;
disallow_empty = 1;
for (p = b + namelen, u = 0; u < len-namelen; /* Check HTTP token */
p++, u++) { for (p = val.b; p < val.e; p++) {
if (vct_issp(*p) || vct_isctl(*p)) if (!vct_istchar(*p))
return (H2SE_PROTOCOL_ERROR); return (H2SE_PROTOCOL_ERROR);
} }
} else if (!Tstrcmp(nm, ":authority")) {
if (!u) /* NB: we inject "host" in place of "rity" for
return (H2SE_PROTOCOL_ERROR); * the ":authority" pseudo-header.
} else if (!strncmp(b, ":authority: ", namelen)) { */
b+=6; memcpy(d->out + 6, "host", 4);
len-=6; hdr.b += 6;
memcpy(b, "host", 4); nm = Tstr(":authority"); /* preserve original */
n = hp->nhd;
} else { } else {
/* Unknown pseudo-header */ /* Unknown pseudo-header */
VSLb(hp->vsl, SLT_BogoHeader, VSLb(hp->vsl, SLT_BogoHeader,
"Unknown pseudo-header: %.*s", "Unknown pseudo-header: %.*s",
(int)(len > 20 ? 20 : len), b); vmin_t(int, Tlen(hdr), 20), hdr.b);
return (H2SE_PROTOCOL_ERROR); // rfc7540,l,2990,2992 return (H2SE_PROTOCOL_ERROR); // rfc7540,l,2990,2992
} }
} else }
n = hp->nhd;
if (disallow_empty && Tlen(val) == 0) {
VSLb(hp->vsl, SLT_BogoHeader,
"Empty pseudo-header %.*s",
(int)Tlen(nm), nm.b);
return (H2SE_PROTOCOL_ERROR);
}
if (n < HTTP_HDR_FIRST) { if (n < HTTP_HDR_FIRST) {
/* Check for duplicate pseudo-header */
if (hp->hd[n].b != NULL) { if (hp->hd[n].b != NULL) {
VSLb(hp->vsl, SLT_BogoHeader, VSLb(hp->vsl, SLT_BogoHeader,
"Duplicate pseudo-header %.*s%.*s", "Duplicate pseudo-header %.*s",
(int)namelen, b0, (int)(len > 20 ? 20 : len), b); (int)Tlen(nm), nm.b);
return (H2SE_PROTOCOL_ERROR); // rfc7540,l,3158,3162 return (H2SE_PROTOCOL_ERROR); // rfc7540,l,3158,3162
} }
} else { } else {
/* Check for space in struct http */ /* Check for space in struct http */
if (n >= hp->shd) { if (n >= hp->shd) {
VSLb(hp->vsl, SLT_LostHeader, "Too many headers: %.*s", VSLb(hp->vsl, SLT_LostHeader,
(int)(len > 20 ? 20 : len), b); "Too many headers: %.*s",
vmin_t(int, Tlen(hdr), 20), hdr.b);
return (H2SE_ENHANCE_YOUR_CALM); return (H2SE_ENHANCE_YOUR_CALM);
} }
hp->nhd++; hp->nhd++;
} }
hp->hd[n].b = b; hp->hd[n] = hdr;
hp->hd[n].e = b + len;
if (disallow_empty && !Tlen(hp->hd[n])) {
VSLb(hp->vsl, SLT_BogoHeader,
"Empty pseudo-header %.*s",
(int)namelen, b0);
return (H2SE_PROTOCOL_ERROR);
}
return (0); return (0);
} }
...@@ -393,8 +403,7 @@ h2h_decode_bytes(struct h2_sess *h2, const uint8_t *in, size_t in_l) ...@@ -393,8 +403,7 @@ h2h_decode_bytes(struct h2_sess *h2, const uint8_t *in, size_t in_l)
d->out_u); d->out_u);
if (d->error) if (d->error)
break; break;
d->error = h2h_addhdr(d, hp, d->out, d->error = h2h_addhdr(hp, d);
d->namelen, d->out_u);
if (d->error) if (d->error)
break; break;
d->out[d->out_u++] = '\0'; /* Zero guard */ d->out[d->out_u++] = '\0'; /* Zero guard */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment