]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
h2/h3: handle methods with spaces
authorStefan Eissing <stefan@eissing.org>
Mon, 17 Nov 2025 08:56:48 +0000 (09:56 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 17 Nov 2025 14:43:28 +0000 (15:43 +0100)
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 #19543
Reported-by: Omdahake on github
Closes #19563

lib/http1.c
lib/http1.h
lib/http2.c
lib/vquic/curl_ngtcp2.c
lib/vquic/curl_osslq.c
lib/vquic/curl_quiche.c
tests/http/test_01_basic.py
tests/unit/unit2603.c

index 0403e95ba20b1ae3eca39d002bb6f5ae21d37548..c487597e3492036f7badf1cd75bf8bf34b911103 100644 (file)
@@ -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;
index b38b32f591150221c94c9a8400ad1a94cd45459d..94b5a44e3143a185960cc7e6913d1058d08b9d50 100644 (file)
@@ -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);
index 2e1e5bd07e560b303b682ed2ef1b471f82037867..1565e0b9ead36661a58cfff4ce30dac25fb9678c 100644 (file)
@@ -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;
index 475060ebdd345de9e73fb2f5734c53491279ec70..ce5786ca83e8841d91bc6ffd6f611511cbee5a0a 100644 (file)
@@ -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;
index 75dc5cc6947316509bfcf5bdbc748c4e32d01639..f328c08e35b5f5ea18896f9bd6a37bb36dc9ccac 100644 (file)
@@ -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) {
index ff3e76b06342462921689c8db2651895dfb0045f..02b679ab8431375ceb1a40df1f0b246353a72b73 100644 (file)
@@ -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) {
index c734318890f316d4528ee429f63def0370c03b95..aa94238c3f01f4066b7d343462a6d86dd040ed5e 100644 (file)
@@ -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)
index 5915555f8b04c991de8044cc3a623d67865f73dc..a8ed09f1c2b5e1009d61fc18f7d9a33f8dd79f00 100644 (file)
@@ -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