h2/h3: handle methods with spaces
No binary (build failed)
—
(log yok)
| @@ -134,7 +134,9 @@ static ssize_t next_line(struct h1_req_parser *parser, | |
| } | |
| static CURLcode start_req(struct h1_req_parser *parser, | |
| - | const char *scheme_default, int options) |
| + | const char *scheme_default, |
| + | const char *custom_method, |
| + | int options) |
| { | |
| const char *p, *m, *target, *hv, *scheme, *authority, *path; | |
| size_t m_len, target_len, hv_len, scheme_len, authority_len, path_len; | |
| @@ -144,9 +146,15 @@ static CURLcode start_req(struct h1_req_parser *parser, | |
| DEBUGASSERT(!parser->req); | |
| /* line must match: "METHOD TARGET HTTP_VERSION" */ | |
| - | p = memchr(parser->line, ' ', parser->line_len); |
| - | if(!p || p == parser->line) |
| - | goto out; |
| + | if(custom_method && custom_method[0] && |
| + | !strncmp(custom_method, parser->line, strlen(custom_method))) { |
| + | p = parser->line + strlen(custom_method); |
| + | } |
| + | else { |
| + | p = memchr(parser->line, ' ', parser->line_len); |
| + | if(!p || p == parser->line) |
| + | goto out; |
| + | } |
| m = parser->line; | |
| m_len = p - parser->line; | |
| @@ -258,8 +266,9 @@ out: | |
| ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, | |
| const char *buf, size_t buflen, | |
| - | const char *scheme_default, int options, |
| - | CURLcode *err) |
| + | const char *scheme_default, |
| + | const char *custom_method, |
| + | int options, CURLcode *err) |
| { | |
| ssize_t nread = 0, n; | |
| @@ -285,7 +294,7 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, | |
| goto out; | |
| } | |
| else if(!parser->req) { | |
| - | *err = start_req(parser, scheme_default, options); |
| + | *err = start_req(parser, scheme_default, custom_method, options); |
| if(*err) { | |
| nread = -1; | |
| goto out; | |
| @@ -50,8 +50,9 @@ void Curl_h1_req_parse_free(struct h1_req_parser *parser); | |
| ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser, | |
| const char *buf, size_t buflen, | |
| - | const char *scheme_default, int options, |
| - | CURLcode *err); |
| + | const char *scheme_default, |
| + | const char *custom_method, |
| + | int options, CURLcode *err); |
| CURLcode Curl_h1_req_dprint(const struct httpreq *req, | |
| struct dynbuf *dbuf); | |
| @@ -2248,7 +2248,10 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream, | |
| if(result) | |
| goto out; | |
| - | rc = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, &result); |
| + | rc = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, |
| + | !data->state.http_ignorecustom ? |
| + | data->set.str[STRING_CUSTOMREQUEST] : NULL, |
| + | 0, &result); |
| if(!curlx_sztouz(rc, &nwritten)) | |
| goto out; | |
| *pnwritten = nwritten; | |
| @@ -1531,7 +1531,10 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, | |
| goto out; | |
| } | |
| - | nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, &result); |
| + | nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, |
| + | !data->state.http_ignorecustom ? |
| + | data->set.str[STRING_CUSTOMREQUEST] : NULL, |
| + | 0, &result); |
| if(nwritten < 0) | |
| goto out; | |
| *pnwritten = (size_t)nwritten; | |
| @@ -1900,7 +1900,10 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf, | |
| goto out; | |
| } | |
| - | nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); |
| + | nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, |
| + | !data->state.http_ignorecustom ? |
| + | data->set.str[STRING_CUSTOMREQUEST] : NULL, |
| + | 0, err); |
| if(nwritten < 0) | |
| goto out; | |
| if(!stream->h1.done) { | |
| @@ -991,7 +991,10 @@ static CURLcode h3_open_stream(struct Curl_cfilter *cf, | |
| Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); | |
| DEBUGASSERT(stream); | |
| - | nwritten = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL, 0, &result); |
| + | nwritten = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL, |
| + | !data->state.http_ignorecustom ? |
| + | data->set.str[STRING_CUSTOMREQUEST] : NULL, |
| + | 0, &result); |
| if(nwritten < 0) | |
| goto out; | |
| if(!stream->h1.done) { | |
| @@ -25,6 +25,7 @@ | |
| ########################################################################### | |
| # | |
| import logging | |
| + | import re |
| import pytest | |
| from testenv import Env | |
| @@ -293,3 +294,24 @@ class TestBasic: | |
| r = curl.http_download(urls=[url1, url2], alpn_proto=proto, with_stats=True) | |
| assert len(r.stats) == 2 | |
| assert r.total_connects == 2, f'{r.dump_logs()}' | |
| + | |
| + | # use a custom method containing a space |
| + | # check that h2/h3 did send that in the :method pseudo header. #19543 |
| + | @pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs verbosecurl") |
| + | @pytest.mark.parametrize("proto", Env.http_protos()) |
| + | def test_01_20_method_space(self, env: Env, proto, httpd): |
| + | curl = CurlClient(env=env) |
| + | method = 'IN SANE' |
| + | url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo' |
| + | r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True, |
| + | extra_args=['-X', method]) |
| + | assert len(r.stats) == 1 |
| + | if proto == 'h2' or proto == 'h3': |
| + | r.check_response(http_status=0) |
| + | re_m = re.compile(r'.*\[:method: ([^\]]+)\].*') |
| + | lines = [line for line in r.trace_lines if re_m.match(line)] |
| + | assert len(lines) == 1, f'{r.dump_logs()}' |
| + | m = re_m.match(lines[0]) |
| + | assert m.group(1) == method, f'{r.dump_logs()}' |
| + | else: |
| + | r.check_response(http_status=400) |
| @@ -51,6 +51,7 @@ static void check_eq(const char *s, const char *exp_s, const char *name) | |
| struct tcase { | |
| const char **input; | |
| const char *default_scheme; | |
| + | const char *custom_method; |
| const char *method; | |
| const char *scheme; | |
| const char *authority; | |
| @@ -74,7 +75,7 @@ static void parse_success(const struct tcase *t) | |
| buflen = strlen(buf); | |
| in_len += buflen; | |
| nread = Curl_h1_req_parse_read(&p, buf, buflen, t->default_scheme, | |
| - | 0, &err); |
| + | t->custom_method, 0, &err); |
| if(nread < 0) { | |
| curl_mfprintf(stderr, "got err %d parsing: '%s'\n", err, buf); | |
| fail("error consuming"); | |
| @@ -122,10 +123,10 @@ static CURLcode test_unit2603(const char *arg) | |
| NULL, | |
| }; | |
| static const struct tcase TEST1a = { | |
| - | T1_INPUT, NULL, "GET", NULL, NULL, "/path", 1, 0 |
| + | T1_INPUT, NULL, NULL, "GET", NULL, NULL, "/path", 1, 0 |
| }; | |
| static const struct tcase TEST1b = { | |
| - | T1_INPUT, "https", "GET", "https", NULL, "/path", 1, 0 |
| + | T1_INPUT, "https", NULL, "GET", "https", NULL, "/path", 1, 0 |
| }; | |
| static const char *T2_INPUT[] = { | |
| @@ -136,7 +137,7 @@ static CURLcode test_unit2603(const char *arg) | |
| NULL, | |
| }; | |
| static const struct tcase TEST2 = { | |
| - | T2_INPUT, NULL, "GET", NULL, NULL, "/path", 1, 8 |
| + | T2_INPUT, NULL, NULL, "GET", NULL, NULL, "/path", 1, 8 |
| }; | |
| static const char *T3_INPUT[] = { | |
| @@ -145,7 +146,7 @@ static CURLcode test_unit2603(const char *arg) | |
| NULL, | |
| }; | |
| static const struct tcase TEST3a = { | |
| - | T3_INPUT, NULL, "GET", "ftp", "ftp.curl.se", "/xxx?a=2", 2, 0 |
| + | T3_INPUT, NULL, NULL, "GET", "ftp", "ftp.curl.se", "/xxx?a=2", 2, 0 |
| }; | |
| static const char *T4_INPUT[] = { | |
| @@ -155,7 +156,7 @@ static CURLcode test_unit2603(const char *arg) | |
| NULL, | |
| }; | |
| static const struct tcase TEST4a = { | |
| - | T4_INPUT, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2 |
| + | T4_INPUT, NULL, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2 |
| }; | |
| static const char *T5_INPUT[] = { | |
| @@ -165,7 +166,7 @@ static CURLcode test_unit2603(const char *arg) | |
| NULL, | |
| }; | |
| static const struct tcase TEST5a = { | |
| - | T5_INPUT, NULL, "OPTIONS", NULL, NULL, "*", 2, 3 |
| + | T5_INPUT, NULL, NULL, "OPTIONS", NULL, NULL, "*", 2, 3 |
| }; | |
| static const char *T6_INPUT[] = { | |
| @@ -173,7 +174,19 @@ static CURLcode test_unit2603(const char *arg) | |
| NULL, | |
| }; | |
| static const struct tcase TEST6a = { | |
| - | T6_INPUT, NULL, "PUT", NULL, NULL, "/path", 1, 3 |
| + | T6_INPUT, NULL, NULL, "PUT", NULL, NULL, "/path", 1, 3 |
| + | }; |
| + | |
| + | /* test a custom method with space, #19543 */ |
| + | static const char *T7_INPUT[] = { |
| + | "IN SANE /path HTTP/1.1\r\nContent-Length: 0\r\n\r\n", |
| + | NULL, |
| + | }; |
| + | static const struct tcase TEST7a = { |
| + | T7_INPUT, NULL, NULL, "IN", NULL, NULL, "SANE /path", 1, 0 |
| + | }; |
| + | static const struct tcase TEST7b = { |
| + | T7_INPUT, NULL, "IN SANE", "IN SANE", NULL, NULL, "/path", 1, 0 |
| }; | |
| parse_success(&TEST1a); | |
| @@ -183,6 +196,8 @@ static CURLcode test_unit2603(const char *arg) | |
| parse_success(&TEST4a); | |
| parse_success(&TEST5a); | |
| parse_success(&TEST6a); | |
| + | parse_success(&TEST7a); |
| + | parse_success(&TEST7b); |
| #endif | |
| UNITTEST_END_SIMPLE | |