Skip to content

WebSocket write callback serves multiple concatenated messages after pausing #22273

Description

@HendrikHuebner

I did this

Follow up to #22227. I'm fairly confident this is an actual bug now.

I expected the following

A receiver using the CURL_WRITEFUNC callback API reads chunks of incoming WebSocket frames using curl_ws_meta, which provides the information required to reconstruct the frames. We'd expect to receive exactly one such chunk per write callback invocation, so curl_ws_meta()->len should be equal to nmemb (number of bytes in the write callback buffer).

My reproducer sends three messages to be received: Hello 1, Hello 2, Hello 3, so I expect three callbacks. My receiver pauses before and after receiving Hello 1. During the fourth callback (after pausing) I observed nmemb == 14 and the buffer contains Hello 2Hello 3. For this callback, curl_ws_meta()->len returns 7, the length of the second message Hello 2.

There seems to be no way to handle this situation correctly, since it's impossible to get the metadata for the bytes [7; 13] with curl_ws_meta. If the write callback returns just 7, it results in CURLE_WRITE_ERROR since curl seems to expect the entire buffer to be consumed. Perhaps curl appends more bytes to the receive buffer internally, after unpausing in some lower level API that isn't aware of WebSocket frame boundaries?

client.c
server.py

Reproducer logs:

hhuebner@hhuebnerMacBookPro ws-meta-repro % python3 server.py &
[15] 90424
hhuebner@hhuebnerMacBookPro ws-meta-repro % listening on ws://127.0.0.1:8765
./client           
client from 127.0.0.1:58981
handshake complete
sent frame 1: b'Hello 1'
callback #1 bytes=7 data='Hello 1'
meta age=0 flags=0x1 offset=0 bytesleft=0 len=7
returning CURL_WRITEFUNC_PAUSE
sent frame 2: b'Hello 2'
sent frame 3: b'Hello 3'
unpause #1 with curl_easy_pause(..., CURLPAUSE_CONT)
callback #2 bytes=7 data='Hello 1'
meta age=0 flags=0x1 offset=0 bytesleft=0 len=7
accepted callback #2
returning 7
callback #3 bytes=7 data='Hello 2'
meta age=0 flags=0x1 offset=0 bytesleft=0 len=7
returning CURL_WRITEFUNC_PAUSE
unpause #2 with curl_easy_pause(..., CURLPAUSE_CONT)
callback #4 bytes=14 data='Hello 2Hello 3'
meta age=0 flags=0x1 offset=0 bytesleft=0 len=7
accepted callback #4
returning 14
server done
done result=0 (No error)
hhuebner@hhuebnerMacBookPro ws-meta-repro % 
[15]    done       python3 server.py

curl/libcurl version

8.21.0

operating system

macOS 26.3, Darwin 25.3.0 on arm64

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions