Skip to content

Commit

Permalink
websocket: fix curl_ws_recv()
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
icing authored and bagder committed Feb 20, 2024
1 parent e3461bb commit f0c446a
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 6 deletions.
5 changes: 5 additions & 0 deletions docs/libcurl/libcurl-env-dbg.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
21 changes: 17 additions & 4 deletions lib/ws.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions tests/http/test_20_websockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,14 @@ def test_20_06_data_large(self, env: Env, ws_echo, repeat):
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)
6 changes: 4 additions & 2 deletions tests/http/testenv/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
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)
Expand Down Expand Up @@ -95,7 +97,7 @@ def run(self, args):
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:
Expand Down

0 comments on commit f0c446a

Please sign in to comment.