/* keep them sorted by length! */
static struct name_const H2_NON_FIELD[] = {
- { STRCONST("TE") },
{ STRCONST("Host") },
{ STRCONST("Upgrade") },
{ STRCONST("Connection") },
{ STRCONST("Transfer-Encoding") },
};
-static bool h2_non_field(const char *name, size_t namelen)
+static bool h2_permissible_field(struct dynhds_entry *e)
{
size_t i;
for(i = 0; i < CURL_ARRAYSIZE(H2_NON_FIELD); ++i) {
- if(namelen < H2_NON_FIELD[i].namelen)
+ if(e->namelen < H2_NON_FIELD[i].namelen)
+ return TRUE;
+ if(e->namelen == H2_NON_FIELD[i].namelen &&
+ strcasecompare(H2_NON_FIELD[i].name, e->name))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static bool http_TE_has_token(const char *fvalue, const char *token)
+{
+ while(*fvalue) {
+ struct Curl_str name;
+
+ /* skip to first token */
+ while(ISBLANK(*fvalue) || *fvalue == ',')
+ fvalue++;
+ if(Curl_str_cspn(&fvalue, &name, " \t\r;,"))
return FALSE;
- if(namelen == H2_NON_FIELD[i].namelen &&
- strcasecompare(H2_NON_FIELD[i].name, name))
+ if(Curl_str_casecompare(&name, token))
return TRUE;
+
+ /* skip any remainder after token, e.g. parameters with quoted strings */
+ while(*fvalue && *fvalue != ',') {
+ if(*fvalue == '"') {
+ struct Curl_str qw;
+ /* if we do not cleanly find a quoted word here, the header value
+ * does not follow HTTP syntax and we reject */
+ if(Curl_str_quotedword(&fvalue, &qw, CURL_MAX_HTTP_HEADER))
+ return FALSE;
+ }
+ else
+ fvalue++;
+ }
}
return FALSE;
}
}
for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) {
e = Curl_dynhds_getn(&req->headers, i);
- if(!h2_non_field(e->name, e->namelen)) {
+ /* "TE" is special in that it is only permissible when it
+ * has only value "trailers". RFC 9113 ch. 8.2.2 */
+ if(e->namelen == 2 && strcasecompare("TE", e->name)) {
+ if(http_TE_has_token(e->value, "trailers"))
+ result = Curl_dynhds_add(h2_headers, e->name, e->namelen,
+ "trailers", sizeof("trailers") - 1);
+ }
+ else if(h2_permissible_field(e)) {
result = Curl_dynhds_add(h2_headers, e->name, e->namelen,
e->value, e->valuelen);
}
r.check_exit_code(0)
else:
r.check_exit_code(43)
+
+ # http: special handling of TE request header
+ @pytest.mark.parametrize("te_in, te_out", [
+ ['trailers', 'trailers'],
+ ['chunked', None],
+ ['gzip, trailers', 'trailers'],
+ ['gzip ;q=0.2;x="y,x", trailers', 'trailers'],
+ ['gzip ;x="trailers", chunks', None],
+ ])
+ def test_01_17_TE(self, env: Env, httpd, te_in, te_out):
+ proto = 'h2'
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
+ r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
+ with_headers=True,
+ extra_args=['-H', f'TE: {te_in}'])
+ r.check_response(200)
+ if te_out is not None:
+ assert r.responses[0]['header']['request-te'] == te_out, f'{r.responses[0]}'
+ else:
+ assert 'request-te' not in r.responses[0]['header'], f'{r.responses[0]}'
ct = apr_table_get(r->headers_in, "content-type");
ap_set_content_type(r, ct ? ct : "application/octet-stream");
+ if(apr_table_get(r->headers_in, "TE"))
+ apr_table_setn(r->headers_out, "Request-TE",
+ apr_table_get(r->headers_in, "TE"));
+
bb = apr_brigade_create(r->pool, c->bucket_alloc);
/* copy any request body into the response */
rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK);