Skip to content

Commit ea10570

Browse files
icingbagder
authored andcommitted
h2/h3: handle methods with spaces
The parsing of the HTTP/1.1 formatted request into the h2/h3 header structures should detect CURLOPT_CUSTOMREQUEST methods and forward them correctly. Add test_01_20 to verify Fixes curl#19543 Reported-by: Omdahake on github Closes curl#19563
1 parent 2459dc7 commit ea10570

8 files changed

Lines changed: 80 additions & 21 deletions

File tree

lib/http1.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ static ssize_t next_line(struct h1_req_parser *parser,
134134
}
135135

136136
static CURLcode start_req(struct h1_req_parser *parser,
137-
const char *scheme_default, int options)
137+
const char *scheme_default,
138+
const char *custom_method,
139+
int options)
138140
{
139141
const char *p, *m, *target, *hv, *scheme, *authority, *path;
140142
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,
144146

145147
DEBUGASSERT(!parser->req);
146148
/* line must match: "METHOD TARGET HTTP_VERSION" */
147-
p = memchr(parser->line, ' ', parser->line_len);
148-
if(!p || p == parser->line)
149-
goto out;
149+
if(custom_method && custom_method[0] &&
150+
!strncmp(custom_method, parser->line, strlen(custom_method))) {
151+
p = parser->line + strlen(custom_method);
152+
}
153+
else {
154+
p = memchr(parser->line, ' ', parser->line_len);
155+
if(!p || p == parser->line)
156+
goto out;
157+
}
150158

151159
m = parser->line;
152160
m_len = p - parser->line;
@@ -258,8 +266,9 @@ static CURLcode start_req(struct h1_req_parser *parser,
258266

259267
ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
260268
const char *buf, size_t buflen,
261-
const char *scheme_default, int options,
262-
CURLcode *err)
269+
const char *scheme_default,
270+
const char *custom_method,
271+
int options, CURLcode *err)
263272
{
264273
ssize_t nread = 0, n;
265274

@@ -285,7 +294,7 @@ ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
285294
goto out;
286295
}
287296
else if(!parser->req) {
288-
*err = start_req(parser, scheme_default, options);
297+
*err = start_req(parser, scheme_default, custom_method, options);
289298
if(*err) {
290299
nread = -1;
291300
goto out;

lib/http1.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,9 @@ void Curl_h1_req_parse_free(struct h1_req_parser *parser);
5050

5151
ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
5252
const char *buf, size_t buflen,
53-
const char *scheme_default, int options,
54-
CURLcode *err);
53+
const char *scheme_default,
54+
const char *custom_method,
55+
int options, CURLcode *err);
5556

5657
CURLcode Curl_h1_req_dprint(const struct httpreq *req,
5758
struct dynbuf *dbuf);

lib/http2.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2248,7 +2248,10 @@ static CURLcode h2_submit(struct h2_stream_ctx **pstream,
22482248
if(result)
22492249
goto out;
22502250

2251-
rc = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, &result);
2251+
rc = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL,
2252+
!data->state.http_ignorecustom ?
2253+
data->set.str[STRING_CUSTOMREQUEST] : NULL,
2254+
0, &result);
22522255
if(!curlx_sztouz(rc, &nwritten))
22532256
goto out;
22542257
*pnwritten = nwritten;

lib/vquic/curl_ngtcp2.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1531,7 +1531,10 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf,
15311531
goto out;
15321532
}
15331533

1534-
nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, &result);
1534+
nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL,
1535+
!data->state.http_ignorecustom ?
1536+
data->set.str[STRING_CUSTOMREQUEST] : NULL,
1537+
0, &result);
15351538
if(nwritten < 0)
15361539
goto out;
15371540
*pnwritten = (size_t)nwritten;

lib/vquic/curl_osslq.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1900,7 +1900,10 @@ static ssize_t h3_stream_open(struct Curl_cfilter *cf,
19001900
goto out;
19011901
}
19021902

1903-
nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
1903+
nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL,
1904+
!data->state.http_ignorecustom ?
1905+
data->set.str[STRING_CUSTOMREQUEST] : NULL,
1906+
0, err);
19041907
if(nwritten < 0)
19051908
goto out;
19061909
if(!stream->h1.done) {

lib/vquic/curl_quiche.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -991,7 +991,10 @@ static CURLcode h3_open_stream(struct Curl_cfilter *cf,
991991
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
992992

993993
DEBUGASSERT(stream);
994-
nwritten = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL, 0, &result);
994+
nwritten = Curl_h1_req_parse_read(&stream->h1, buf, blen, NULL,
995+
!data->state.http_ignorecustom ?
996+
data->set.str[STRING_CUSTOMREQUEST] : NULL,
997+
0, &result);
995998
if(nwritten < 0)
996999
goto out;
9971000
if(!stream->h1.done) {

tests/http/test_01_basic.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
###########################################################################
2626
#
2727
import logging
28+
import re
2829
import pytest
2930

3031
from testenv import Env
@@ -293,3 +294,24 @@ def test_01_19_plain_reuse(self, env: Env, httpd):
293294
r = curl.http_download(urls=[url1, url2], alpn_proto=proto, with_stats=True)
294295
assert len(r.stats) == 2
295296
assert r.total_connects == 2, f'{r.dump_logs()}'
297+
298+
# use a custom method containing a space
299+
# check that h2/h3 did send that in the :method pseudo header. #19543
300+
@pytest.mark.skipif(condition=not Env.curl_is_verbose(), reason="needs verbosecurl")
301+
@pytest.mark.parametrize("proto", Env.http_protos())
302+
def test_01_20_method_space(self, env: Env, proto, httpd):
303+
curl = CurlClient(env=env)
304+
method = 'IN SANE'
305+
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
306+
r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
307+
extra_args=['-X', method])
308+
assert len(r.stats) == 1
309+
if proto == 'h2' or proto == 'h3':
310+
r.check_response(http_status=0)
311+
re_m = re.compile(r'.*\[:method: ([^\]]+)\].*')
312+
lines = [line for line in r.trace_lines if re_m.match(line)]
313+
assert len(lines) == 1, f'{r.dump_logs()}'
314+
m = re_m.match(lines[0])
315+
assert m.group(1) == method, f'{r.dump_logs()}'
316+
else:
317+
r.check_response(http_status=400)

tests/unit/unit2603.c

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ static void check_eq(const char *s, const char *exp_s, const char *name)
5151
struct tcase {
5252
const char **input;
5353
const char *default_scheme;
54+
const char *custom_method;
5455
const char *method;
5556
const char *scheme;
5657
const char *authority;
@@ -74,7 +75,7 @@ static void parse_success(const struct tcase *t)
7475
buflen = strlen(buf);
7576
in_len += buflen;
7677
nread = Curl_h1_req_parse_read(&p, buf, buflen, t->default_scheme,
77-
0, &err);
78+
t->custom_method, 0, &err);
7879
if(nread < 0) {
7980
curl_mfprintf(stderr, "got err %d parsing: '%s'\n", err, buf);
8081
fail("error consuming");
@@ -122,10 +123,10 @@ static CURLcode test_unit2603(const char *arg)
122123
NULL,
123124
};
124125
static const struct tcase TEST1a = {
125-
T1_INPUT, NULL, "GET", NULL, NULL, "/path", 1, 0
126+
T1_INPUT, NULL, NULL, "GET", NULL, NULL, "/path", 1, 0
126127
};
127128
static const struct tcase TEST1b = {
128-
T1_INPUT, "https", "GET", "https", NULL, "/path", 1, 0
129+
T1_INPUT, "https", NULL, "GET", "https", NULL, "/path", 1, 0
129130
};
130131

131132
static const char *T2_INPUT[] = {
@@ -136,7 +137,7 @@ static CURLcode test_unit2603(const char *arg)
136137
NULL,
137138
};
138139
static const struct tcase TEST2 = {
139-
T2_INPUT, NULL, "GET", NULL, NULL, "/path", 1, 8
140+
T2_INPUT, NULL, NULL, "GET", NULL, NULL, "/path", 1, 8
140141
};
141142

142143
static const char *T3_INPUT[] = {
@@ -145,7 +146,7 @@ static CURLcode test_unit2603(const char *arg)
145146
NULL,
146147
};
147148
static const struct tcase TEST3a = {
148-
T3_INPUT, NULL, "GET", "ftp", "ftp.curl.se", "/xxx?a=2", 2, 0
149+
T3_INPUT, NULL, NULL, "GET", "ftp", "ftp.curl.se", "/xxx?a=2", 2, 0
149150
};
150151

151152
static const char *T4_INPUT[] = {
@@ -155,7 +156,7 @@ static CURLcode test_unit2603(const char *arg)
155156
NULL,
156157
};
157158
static const struct tcase TEST4a = {
158-
T4_INPUT, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2
159+
T4_INPUT, NULL, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2
159160
};
160161

161162
static const char *T5_INPUT[] = {
@@ -165,15 +166,27 @@ static CURLcode test_unit2603(const char *arg)
165166
NULL,
166167
};
167168
static const struct tcase TEST5a = {
168-
T5_INPUT, NULL, "OPTIONS", NULL, NULL, "*", 2, 3
169+
T5_INPUT, NULL, NULL, "OPTIONS", NULL, NULL, "*", 2, 3
169170
};
170171

171172
static const char *T6_INPUT[] = {
172173
"PUT /path HTTP/1.1\nHost: test.curl.se\n\n123",
173174
NULL,
174175
};
175176
static const struct tcase TEST6a = {
176-
T6_INPUT, NULL, "PUT", NULL, NULL, "/path", 1, 3
177+
T6_INPUT, NULL, NULL, "PUT", NULL, NULL, "/path", 1, 3
178+
};
179+
180+
/* test a custom method with space, #19543 */
181+
static const char *T7_INPUT[] = {
182+
"IN SANE /path HTTP/1.1\r\nContent-Length: 0\r\n\r\n",
183+
NULL,
184+
};
185+
static const struct tcase TEST7a = {
186+
T7_INPUT, NULL, NULL, "IN", NULL, NULL, "SANE /path", 1, 0
187+
};
188+
static const struct tcase TEST7b = {
189+
T7_INPUT, NULL, "IN SANE", "IN SANE", NULL, NULL, "/path", 1, 0
177190
};
178191

179192
parse_success(&TEST1a);
@@ -183,6 +196,8 @@ static CURLcode test_unit2603(const char *arg)
183196
parse_success(&TEST4a);
184197
parse_success(&TEST5a);
185198
parse_success(&TEST6a);
199+
parse_success(&TEST7a);
200+
parse_success(&TEST7b);
186201
#endif
187202

188203
UNITTEST_END_SIMPLE

0 commit comments

Comments
 (0)