]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
websocket: fix curl_ws_recv()
authorStefan Eissing <stefan@eissing.org>
Thu, 15 Feb 2024 15:39:40 +0000 (16:39 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 20 Feb 2024 12:57:58 +0000 (13:57 +0100)
- when data arrived in several chunks, the collection into
  the passed buffer always started at offset 0, overwriting
  the data already there.

adding test_20_07 to verify fix

- debug environment var CURL_WS_CHUNK_SIZE can be used to
  influence the buffer chunk size used for en-/decoding.

Closes #12945

docs/libcurl/libcurl-env-dbg.md
lib/ws.c
tests/http/test_20_websockets.py
tests/http/testenv/client.py

index 21b763bb4fcaa849dc76e278ef3f2d9e41fac1cc..21ed3bf25ae7c766c92f5d1b6e79bf540cea7b99 100644 (file)
@@ -116,3 +116,8 @@ Debug-version of the *ntlm-wb* executable.
 
 OpenLDAP tracing is enabled if this variable exists and its value is 1 or
 greater. There is a number of debug levels, refer to *openldap.c* comments.
+
+## CURL_WS_CHUNK_SIZE
+
+Used to influence the buffer chunk size used for WebSocket encoding and
+decoding.
index d9765182d956d3c5ccb8c9d7e016484bc992fce7..f4675cec474c64ec708ff2dd9058630a9a30d88c 100644 (file)
--- a/lib/ws.c
+++ b/lib/ws.c
@@ -754,13 +754,26 @@ CURLcode Curl_ws_accept(struct Curl_easy *data,
   DEBUGASSERT(data->conn);
   ws = data->conn->proto.ws;
   if(!ws) {
+    size_t chunk_size = WS_CHUNK_SIZE;
     ws = calloc(1, sizeof(*ws));
     if(!ws)
       return CURLE_OUT_OF_MEMORY;
     data->conn->proto.ws = ws;
-    Curl_bufq_init2(&ws->recvbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT,
+#ifdef DEBUGBUILD
+    {
+      char *p = getenv("CURL_WS_CHUNK_SIZE");
+      if(p) {
+        long l = strtol(p, NULL, 10);
+        if(l > 0 && l <= (1*1024*1024)) {
+          chunk_size = (size_t)l;
+        }
+      }
+    }
+#endif
+    DEBUGF(infof(data, "WS, using chunk size %zu", chunk_size));
+    Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT,
                     BUFQ_OPT_SOFT_LIMIT);
-    Curl_bufq_init2(&ws->sendbuf, WS_CHUNK_SIZE, WS_CHUNK_COUNT,
+    Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT,
                     BUFQ_OPT_SOFT_LIMIT);
     ws_dec_init(&ws->dec);
     ws_enc_init(&ws->enc);
@@ -834,7 +847,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data,
 
 struct ws_collect {
   struct Curl_easy *data;
-  void *buffer;
+  unsigned char *buffer;
   size_t buflen;
   size_t bufidx;
   int frame_age;
@@ -886,7 +899,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
       return -1;
     }
     *err = CURLE_OK;
-    memcpy(ctx->buffer, buf, nwritten);
+    memcpy(ctx->buffer + ctx->bufidx, buf, nwritten);
     ctx->bufidx += nwritten;
   }
   return nwritten;
index 4e70dcef0036299df4bb627968fdfbf7f91dd9c8..eb9df306b31c0405d673d780d7d97a956a703062 100644 (file)
@@ -129,3 +129,14 @@ class TestWebsockets:
         url = f'ws://localhost:{env.ws_port}/'
         r = client.run(args=[url, str(65535 - 5), str(65535 + 5)])
         r.check_exit_code(0)
+
+    # the python websocket server does not like 'large' control frames
+    def test_20_07_data_large_small_recv(self, env: Env, ws_echo, repeat):
+        client = LocalClient(env=env, name='ws-data', run_env={
+            'CURL_WS_CHUNK_SIZE': '1024',
+        })
+        if not client.exists():
+            pytest.skip(f'example client not built: {client.name}')
+        url = f'ws://localhost:{env.ws_port}/'
+        r = client.run(args=[url, str(65535 - 5), str(65535 + 5)])
+        r.check_exit_code(0)
index 098e55b9cdcb85f82144b489410ae143f80f8934..e8ffb040aaf74fc0ce0f42a51dd0d9dd544b35d1 100644 (file)
@@ -45,10 +45,12 @@ log = logging.getLogger(__name__)
 class LocalClient:
 
     def __init__(self, name: str, env: Env, run_dir: Optional[str] = None,
-                 timeout: Optional[float] = None):
+                 timeout: Optional[float] = None,
+                 run_env: Optional[Dict[str,str]] = None):
         self.name = name
         self.path = os.path.join(env.project_dir, f'tests/http/clients/{name}')
         self.env = env
+        self._run_env= run_env
         self._timeout = timeout if timeout else env.test_timeout
         self._curl = os.environ['CURL'] if 'CURL' in os.environ else env.curl
         self._run_dir = run_dir if run_dir else os.path.join(env.gen_dir, name)
@@ -95,7 +97,7 @@ class LocalClient:
                 with open(self._stderrfile, 'w') as cerr:
                     p = subprocess.run(myargs, stderr=cerr, stdout=cout,
                                        cwd=self._run_dir, shell=False,
-                                       input=None,
+                                       input=None, env=self._run_env,
                                        timeout=self._timeout)
                     exitcode = p.returncode
         except subprocess.TimeoutExpired: