Sinkevich Artem <artsin666@gmail.com>
Andrew Kirillov <akirillo@uk.ibm.com>
Stephen Farrell <stephen.farrell@cs.tcd.ie>
+Calvin Ruocco <calvin.ruocco@vector.com>
Used to simulate blocking sends after this chunk size for WebSocket
connections.
+## `CURL_WS_FORCE_ZERO_MASK`
+
+Used to set the bitmask of all sent WebSocket frames to zero. The value of the
+environment variable does not matter.
+
## `CURL_FORBID_REUSE`
Used to set the CURLOPT_FORBID_REUSE flag on each transfer initiated
unsigned char firstbyte, int cont_flags)
{
switch(firstbyte) {
+ /* 0x00 - intermediate TEXT/BINARY fragment */
case WSBIT_OPCODE_CONT:
- /* continuation of a previous fragment: restore stored flags */
+ if(!(cont_flags & CURLWS_CONT)) {
+ failf(data, "[WS] no ongoing fragmented message to resume");
+ return 0;
+ }
return cont_flags | CURLWS_CONT;
+ /* 0x80 - final TEXT/BIN fragment */
case (WSBIT_OPCODE_CONT | WSBIT_FIN):
- /* continuation of a previous fragment: restore stored flags */
+ if(!(cont_flags & CURLWS_CONT)) {
+ failf(data, "[WS] no ongoing fragmented message to resume");
+ return 0;
+ }
return cont_flags & ~CURLWS_CONT;
+ /* 0x01 - first TEXT fragment */
case WSBIT_OPCODE_TEXT:
+ if(cont_flags & CURLWS_CONT) {
+ failf(data, "[WS] fragmented message interrupted by new TEXT msg");
+ return 0;
+ }
return CURLWS_TEXT | CURLWS_CONT;
+ /* 0x81 - unfragmented TEXT msg */
case (WSBIT_OPCODE_TEXT | WSBIT_FIN):
+ if(cont_flags & CURLWS_CONT) {
+ failf(data, "[WS] fragmented message interrupted by new TEXT msg");
+ return 0;
+ }
return CURLWS_TEXT;
+ /* 0x02 - first BINARY fragment */
case WSBIT_OPCODE_BIN:
+ if(cont_flags & CURLWS_CONT) {
+ failf(data, "[WS] fragmented message interrupted by new BINARY msg");
+ return 0;
+ }
return CURLWS_BINARY | CURLWS_CONT;
+ /* 0x82 - unfragmented BINARY msg */
case (WSBIT_OPCODE_BIN | WSBIT_FIN):
+ if(cont_flags & CURLWS_CONT) {
+ failf(data, "[WS] fragmented message interrupted by new BINARY msg");
+ return 0;
+ }
return CURLWS_BINARY;
+ /* 0x08 - first CLOSE fragment */
+ case WSBIT_OPCODE_CLOSE:
+ failf(data, "[WS] invalid fragmented CLOSE frame");
+ return 0;
+ /* 0x88 - unfragmented CLOSE */
case (WSBIT_OPCODE_CLOSE | WSBIT_FIN):
return CURLWS_CLOSE;
+ /* 0x09 - first PING fragment */
+ case WSBIT_OPCODE_PING:
+ failf(data, "[WS] invalid fragmented PING frame");
+ return 0;
+ /* 0x89 - unfragmented PING */
case (WSBIT_OPCODE_PING | WSBIT_FIN):
return CURLWS_PING;
+ /* 0x0a - first PONG fragment */
+ case WSBIT_OPCODE_PONG:
+ failf(data, "[WS] invalid fragmented PONG frame");
+ return 0;
+ /* 0x8a - unfragmented PONG */
case (WSBIT_OPCODE_PONG | WSBIT_FIN):
return CURLWS_PONG;
+ /* invalid first byte */
default:
- if(firstbyte & WSBIT_RSV_MASK) {
- failf(data, "WS: unknown reserved bit: %x",
- firstbyte & WSBIT_RSV_MASK);
- }
- else {
- failf(data, "WS: unknown opcode: %x",
- firstbyte & WSBIT_OPCODE_MASK);
- }
+ if(firstbyte & WSBIT_RSV_MASK)
+ /* any of the reserved bits 0x40/0x20/0x10 are set */
+ failf(data, "[WS] invalid reserved bits: %02x", firstbyte);
+ else
+ /* any of the reserved opcodes 0x3-0x7 or 0xb-0xf is used */
+ failf(data, "[WS] invalid opcode: %02x", firstbyte);
return 0;
}
}
switch(flags & ~CURLWS_OFFSET) {
case 0:
if(contfragment) {
- infof(data, "WS: no flags given; interpreting as continuation "
+ infof(data, "[WS] no flags given; interpreting as continuation "
"fragment for compatibility");
return (WSBIT_OPCODE_CONT | WSBIT_FIN);
}
- failf(data, "WS: no flags given");
+ failf(data, "[WS] no flags given");
*err = CURLE_BAD_FUNCTION_ARGUMENT;
return 0xff;
case CURLWS_CONT:
if(contfragment) {
- infof(data, "WS: setting CURLWS_CONT flag without message type is "
+ infof(data, "[WS] setting CURLWS_CONT flag without message type is "
"supported for compatibility but highly discouraged");
return WSBIT_OPCODE_CONT;
}
- failf(data, "WS: No ongoing fragmented message to continue");
+ failf(data, "[WS] No ongoing fragmented message to continue");
*err = CURLE_BAD_FUNCTION_ARGUMENT;
return 0xff;
case CURLWS_TEXT:
case CURLWS_CLOSE:
return WSBIT_OPCODE_CLOSE | WSBIT_FIN;
case (CURLWS_CLOSE | CURLWS_CONT):
- failf(data, "WS: CLOSE frame must not be fragmented");
+ failf(data, "[WS] CLOSE frame must not be fragmented");
*err = CURLE_BAD_FUNCTION_ARGUMENT;
return 0xff;
case CURLWS_PING:
return WSBIT_OPCODE_PING | WSBIT_FIN;
case (CURLWS_PING | CURLWS_CONT):
- failf(data, "WS: PING frame must not be fragmented");
+ failf(data, "[WS] PING frame must not be fragmented");
*err = CURLE_BAD_FUNCTION_ARGUMENT;
return 0xff;
case CURLWS_PONG:
return WSBIT_OPCODE_PONG | WSBIT_FIN;
case (CURLWS_PONG | CURLWS_CONT):
- failf(data, "WS: PONG frame must not be fragmented");
+ failf(data, "[WS] PONG frame must not be fragmented");
*err = CURLE_BAD_FUNCTION_ARGUMENT;
return 0xff;
default:
- failf(data, "WS: unknown flags: %x", flags);
- *err = CURLE_SEND_ERROR;
+ failf(data, "[WS] unknown flags: %x", flags);
+ *err = CURLE_BAD_FUNCTION_ARGUMENT;
return 0xff;
}
}
case 0:
break;
case 1:
- CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s]", msg,
- ws_frame_name_of_op(dec->head[0]),
- (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
+ CURL_TRC_WS(data, "decoded %s [%s%s]", msg,
+ ws_frame_name_of_op(dec->head[0]),
+ (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
break;
default:
if(dec->head_len < dec->head_total) {
- CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s](%d/%d)", msg,
- ws_frame_name_of_op(dec->head[0]),
- (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
- dec->head_len, dec->head_total);
+ CURL_TRC_WS(data, "decoded %s [%s%s](%d/%d)", msg,
+ ws_frame_name_of_op(dec->head[0]),
+ (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
+ dec->head_len, dec->head_total);
}
else {
- CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s payload=%"
- FMT_OFF_T "/%" FMT_OFF_T "]",
- msg, ws_frame_name_of_op(dec->head[0]),
- (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
- dec->payload_offset, dec->payload_len);
+ CURL_TRC_WS(data, "decoded %s [%s%s payload=%"
+ FMT_OFF_T "/%" FMT_OFF_T "]",
+ msg, ws_frame_name_of_op(dec->head[0]),
+ (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
+ dec->payload_offset, dec->payload_len);
}
break;
}
dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0],
dec->cont_flags);
if(!dec->frame_flags) {
- failf(data, "WS: invalid first byte: %x", dec->head[0]);
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
- if(dec->frame_flags & CURLWS_CONT) {
+ /* fragmentation only applies to data frames (text/binary);
+ * control frames (close/ping/pong) do not affect the CONT status */
+ if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) {
dec->cont_flags = dec->frame_flags;
}
- else {
- dec->cont_flags = 0;
- }
dec->head_len = 1;
/* ws_dec_info(dec, data, "seeing opcode"); */
if(dec->head[1] & WSBIT_MASK) {
/* A client MUST close a connection if it detects a masked frame. */
- failf(data, "WS: masked input frame");
+ failf(data, "[WS] masked input frame");
+ ws_dec_reset(dec);
+ return CURLE_RECV_ERROR;
+ }
+ if(dec->frame_flags & CURLWS_PING && dec->head[1] > 125) {
+ /* The maximum valid size of PING frames is 125 bytes.
+ Accepting overlong pings would mean sending equivalent pongs! */
+ failf(data, "[WS] received PING frame is too big");
ws_dec_reset(dec);
return CURLE_RECV_ERROR;
}
+ if(dec->frame_flags & CURLWS_PONG && dec->head[1] > 125) {
+ /* The maximum valid size of PONG frames is 125 bytes. */
+ failf(data, "[WS] received PONG frame is too big");
+ ws_dec_reset(dec);
+ return CURLE_RECV_ERROR;
+ }
+ if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > 125) {
+ /* The maximum valid size of CLOSE frames is 125 bytes. */
+ failf(data, "[WS] received CLOSE frame is too big");
+ ws_dec_reset(dec);
+ return CURLE_RECV_ERROR;
+ }
+
/* How long is the frame head? */
if(dec->head[1] == 126) {
dec->head_total = 4;
break;
case 10:
if(dec->head[2] > 127) {
- failf(data, "WS: frame length longer than 64 signed not supported");
+ failf(data, "[WS] frame length longer than 64 signed not supported");
return CURLE_RECV_ERROR;
}
dec->payload_len = ((curl_off_t)dec->head[2] << 56) |
default:
/* this should never happen */
DEBUGASSERT(0);
- failf(data, "WS: unexpected frame header length");
+ failf(data, "[WS] unexpected frame header length");
return CURLE_RECV_ERROR;
}
Curl_bufq_skip(inraw, (size_t)nwritten);
dec->payload_offset += (curl_off_t)nwritten;
remain = dec->payload_len - dec->payload_offset;
- CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %"
- FMT_OFF_T " remain", nwritten, remain);
+ CURL_TRC_WS(data, "passed %zd bytes payload, %"
+ FMT_OFF_T " remain", nwritten, remain);
}
return remain ? CURLE_AGAIN : CURLE_OK;
result = ws_dec_read_head(dec, data, inraw);
if(result) {
if(result != CURLE_AGAIN) {
- infof(data, "WS: decode error %d", (int)result);
+ infof(data, "[WS] decode error %d", (int)result);
break; /* real error */
}
/* incomplete ws frame head */
if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
/* auto-respond to PINGs, only works for single-frame payloads atm */
size_t bytes;
- infof(data, "WS: auto-respond to PING with a PONG");
+ infof(data, "[WS] auto-respond to PING with a PONG");
/* send back the exact same content as a PONG */
*err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG);
if(*err)
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
- failf(data, "WS: not a websocket transfer");
+ failf(data, "[WS] not a websocket transfer");
return CURLE_FAILED_INIT;
}
nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf,
nbytes, &result);
if(nwritten < 0) {
- infof(data, "WS: error adding data to buffer %d", result);
+ infof(data, "[WS] error adding data to buffer %d", result);
return result;
}
}
if(result == CURLE_AGAIN) {
/* insufficient amount of data, keep it for later.
* we pretend to have written all since we have a copy */
- CURL_TRC_WRITE(data, "websocket, buffered incomplete frame head");
+ CURL_TRC_WS(data, "buffered incomplete frame head");
return CURLE_OK;
}
else if(result) {
- infof(data, "WS: decode error %d", (int)result);
+ infof(data, "[WS] decode error %d", (int)result);
return result;
}
}
if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
- infof(data, "WS: decode ending with %zd frame bytes remaining",
+ failf(data, "[WS] decode ending with %zd frame bytes remaining",
Curl_bufq_len(&ctx->buf));
return CURLE_RECV_ERROR;
}
static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
const char *msg)
{
- infof(data, "WS-ENC: %s [%s%s payload=%" FMT_OFF_T "/%" FMT_OFF_T "]",
- msg, ws_frame_name_of_op(enc->firstbyte),
- (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
- enc->payload_len - enc->payload_remain, enc->payload_len);
+ CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%"
+ FMT_OFF_T "/%" FMT_OFF_T "]",
+ msg, ws_frame_name_of_op(enc->firstbyte),
+ (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
+ enc->payload_len - enc->payload_remain, enc->payload_len);
}
static void ws_enc_reset(struct ws_encoder *enc)
ssize_t n;
if(payload_len < 0) {
- failf(data, "WS: starting new frame with negative payload length %"
+ failf(data, "[WS] starting new frame with negative payload length %"
FMT_OFF_T, payload_len);
*err = CURLE_SEND_ERROR;
return -1;
if(enc->payload_remain > 0) {
/* trying to write a new frame before the previous one is finished */
- failf(data, "WS: starting new frame with %zd bytes from last one "
+ failf(data, "[WS] starting new frame with %zd bytes from last one "
"remaining to be sent", (ssize_t)enc->payload_remain);
*err = CURLE_SEND_ERROR;
return -1;
firstbyte = ws_frame_flags2firstbyte(data, flags, enc->contfragment, err);
if(*err) {
- failf(data, "WS: provided flags not valid: %x", flags);
return -1;
}
enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE;
}
+ if(flags & CURLWS_PING && payload_len > 125) {
+ /* The maximum valid size of PING frames is 125 bytes. */
+ failf(data, "[WS] given PING frame is too big");
+ *err = CURLE_TOO_LARGE;
+ return -1;
+ }
+ if(flags & CURLWS_PONG && payload_len > 125) {
+ /* The maximum valid size of PONG frames is 125 bytes. */
+ failf(data, "[WS] given PONG frame is too big");
+ *err = CURLE_TOO_LARGE;
+ return -1;
+ }
+ if(flags & CURLWS_CLOSE && payload_len > 125) {
+ /* The maximum valid size of CLOSE frames is 125 bytes. */
+ failf(data, "[WS] given CLOSE frame is too big");
+ *err = CURLE_TOO_LARGE;
+ return -1;
+ }
+
head[0] = enc->firstbyte = firstbyte;
if(payload_len > 65535) {
head[1] = 127 | WSBIT_MASK;
sizeof(ws->enc.mask));
if(result)
return result;
- infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
+
+#ifdef DEBUGBUILD
+ if(getenv("CURL_WS_FORCE_ZERO_MASK"))
+ /* force the bit mask to 0x00000000, effectively disabling masking */
+ memset(ws->enc.mask, 0, sizeof(ws->enc.mask));
+#endif
+
+ infof(data, "[WS] Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
/* Install our client writer that decodes WS frames payload */
nread, &result);
if(nwritten < 0)
return result;
- infof(data, "%zu bytes websocket payload", nread);
+ CURL_TRC_WS(data, "%zu bytes payload", nread);
}
else { /* !connect_only */
/* And pass any additional data to the writers */
if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
/* auto-respond to PINGs, only works for single-frame payloads atm */
size_t bytes;
- infof(ctx->data, "WS: auto-respond to PING with a PONG");
+ infof(ctx->data, "[WS] auto-respond to PING with a PONG");
/* send back the exact same content as a PONG */
*err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
if(*err)
if(!conn) {
/* Unhappy hack with lifetimes of transfers and connection */
if(!data->set.connect_only) {
- failf(data, "CONNECT_ONLY is required");
+ failf(data, "[WS] CONNECT_ONLY is required");
return CURLE_UNSUPPORTED_PROTOCOL;
}
Curl_getconnectinfo(data, &conn);
if(!conn) {
- failf(data, "connection not found");
+ failf(data, "[WS] connection not found");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
- failf(data, "connection is not setup for websocket");
+ failf(data, "[WS] connection is not setup for websocket");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
}
else if(n == 0) {
/* connection closed */
- infof(data, "connection expectedly closed?");
+ infof(data, "[WS] connection expectedly closed?");
return CURLE_GOT_NOTHING;
}
CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
return result;
}
else if(result) {
- failf(data, "WS: flush, write error %d", result);
+ failf(data, "[WS] flush, write error %d", result);
return result;
}
else {
- infof(data, "WS: flushed %zu bytes", n);
+ CURL_TRC_WS(data, "flushed %zu bytes", n);
Curl_bufq_skip(&ws->sendbuf, n);
}
}
buflen);
left_ms = Curl_timeleft(data, NULL, FALSE);
if(left_ms < 0) {
- failf(data, "Timeout waiting for socket becoming writable");
+ failf(data, "[WS] Timeout waiting for socket becoming writable");
return CURLE_SEND_ERROR;
}
ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock,
left_ms ? left_ms : 500);
if(ev < 0) {
- failf(data, "Error while waiting for socket becoming writable");
+ failf(data, "[WS] Error while waiting for socket becoming writable");
return CURLE_SEND_ERROR;
}
}
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
- failf(data, "Not a websocket transfer");
+ failf(data, "[WS] Not a websocket transfer");
return CURLE_SEND_ERROR;
}
if(!buflen)
goto out;
}
if(!data->conn) {
- failf(data, "No associated connection");
+ failf(data, "[WS] No associated connection");
result = CURLE_SEND_ERROR;
goto out;
}
ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
if(!ws) {
- failf(data, "Not a websocket transfer");
+ failf(data, "[WS] Not a websocket transfer");
result = CURLE_SEND_ERROR;
goto out;
}
goto out;
if(fragsize || flags) {
- failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero");
+ failf(data, "[WS] fragsize and flags must be zero in raw mode");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
result = ws_send_raw(data, buffer, buflen, sent);
if(buflen < ws->sendbuf_payload) {
/* We have been called with LESS buffer data than before. This
* is not how it's supposed too work. */
- failf(data, "curl_ws_send() called with smaller 'buflen' than "
+ failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than "
"bytes already buffered in previous call, %zu vs %zu",
buflen, ws->sendbuf_payload);
result = CURLE_BAD_FUNCTION_ARGUMENT;
if((curl_off_t)buflen >
(ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) {
/* too large buflen beyond payload length of frame */
- infof(data, "WS: unaligned frame size (sending %zu instead of %"
+ failf(data, "[WS] unaligned frame size (sending %zu instead of %"
FMT_OFF_T ")",
buflen, ws->enc.payload_remain + ws->sendbuf_payload);
result = CURLE_BAD_FUNCTION_ARGUMENT;
/* flush, blocking when in callback */
result = ws_flush(data, ws, Curl_is_in_callback(data));
- if(!result) {
+ if(!result && ws->sendbuf_payload > 0) {
*sent += ws->sendbuf_payload;
buffer += ws->sendbuf_payload;
buflen -= ws->sendbuf_payload;
\
test2200 test2201 test2202 test2203 test2204 test2205 \
\
-test2300 test2301 test2302 test2303 test2304 test2305 test2306 test2307 \
-test2308 test2309 test2310 test2311 test2312 \
+test2300 test2301 test2302 test2303 test2304 test2306 \
+test2308 test2309 \
\
test2400 test2401 test2402 test2403 test2404 test2405 test2406 \
\
\
test2600 test2601 test2602 test2603 test2604 \
\
+test2700 test2701 test2702 test2703 test2704 test2705 test2706 test2707 \
+test2708 test2709 test2710 test2711 test2712 test2713 test2714 test2715 \
+test2716 test2717 test2718 test2719 test2720 test2721 test2722 test2723 \
+\
test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \
test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \
</info>
#
-# Sends a PING + a 5 byte hello TEXT
+# Close connection during websocket upgrade
<reply>
<data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets swsclose\r
-Server: test-server/fake\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Something: else\r
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=\r
-\r
-%hex[%89%00%81%05hello]hex%
+HTTP/1.1 101 Switching to WebSockets swsclose
+Server: test-server/fake
+Upgrade: websocket
+Connection: Upgrade
+Something: else
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
</data>
# allow upgrade
<servercmd>
<protocol nocheck="yes" crlf="yes">
GET /%TESTNUMBER HTTP/1.1
Host: %HOSTIP:%HTTPPORT
-User-Agent: websocket/2304
+User-Agent: websocket/%TESTNUMBER
Accept: */*
Upgrade: websocket
Connection: Upgrade
+++ /dev/null
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends three 4097 bytes TEXT frames, as one single message
-<reply>
-<data nocheck="yes">
-HTTP/1.1 101 Switching to WebSockets\r
-Server: test-server/fake\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Something: else\r
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=\r
-\r
-%hex[%01%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%01%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%81%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSocket curl_ws_recv() loop reading three larger frames
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/save%TESTNUMBER
-</command>
-</client>
-
-#
-<verify>
-<file name="%LOGDIR/save%TESTNUMBER" mode="text">
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-</file>
-</verify>
-</testcase>
+++ /dev/null
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends a PING with overlong payload
-<reply>
-<data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets
-Server: test-server/fake
-Upgrade: websocket
-Connection: Upgrade
-Something: else
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
-
-%hex[%19%7f%ff%30%30%30%30%30%30%30%30%30%30%30%30]hex%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSockets, overlong PING payload
-</name>
-<tool>
-lib2302
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
-</command>
-</client>
-
-#
-# PONG with no data and the 32 bit mask
-#
-<verify>
-<protocol nocheck="yes" nonewline="yes">
-GET /%TESTNUMBER HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-User-Agent: webbie-sox/3\r
-Accept: */*\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Version: 13\r
-Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==\r
-\r
-
-</protocol>
-# 56 == CURLE_RECV_ERROR
-<errorcode>
-56
-</errorcode>
-</verify>
-</testcase>
+++ /dev/null
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends a PING + a TEXT with RSV1 set
-<reply>
-<data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets\r
-Server: test-server/fake\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=\r
-\r
-%hex[%89%00%C1%05hello]hex%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSockets unknown reserved bit set in frame header
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
-</command>
-</client>
-
-#
-# PONG with no data and the 32 bit mask
-#
-<verify>
-<protocol nocheck="yes" nonewline="yes">
-GET /%TESTNUMBER HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-User-Agent: webbie-sox/3\r
-Accept: */*\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Version: 13\r
-Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==\r
-\r
-%hex[%8a%808321]hex%
-</protocol>
-<stdout mode="text">
-Returned 56, should be 56.
-</stdout>
-</verify>
-</testcase>
+++ /dev/null
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends three 4097 bytes TEXT frames, as fragments of one 12291 bytes message
-<reply>
-<data nocheck="yes">
-HTTP/1.1 101 Switching to WebSockets
-Server: test-server/fake
-Upgrade: websocket
-Connection: Upgrade
-Something: else
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
-
-%hex[%01%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%00%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%80%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSocket curl_ws_recv() read fragmented message
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/save%TESTNUMBER
-</command>
-</client>
-
-#
-<verify>
-<file name="%LOGDIR/save%TESTNUMBER" mode="text">
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-</file>
-</verify>
-</testcase>
+++ /dev/null
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Server-side
-<reply>
-<data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets swsclose
-Server: test-server/fake
-Upgrade: websocket
-Connection: Upgrade
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
-
-%hex[%89%00]hex%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# for the forced CURL_ENTROPY
-<features>
-debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSockets no auto ping
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
-</command>
-</client>
-
-#
-# Verify data after the test has been "shot"
-<verify>
-<protocol nocheck="yes">
-GET /%TESTNUMBER HTTP/1.1
-Host: %HOSTIP:%HTTPPORT
-User-Agent: webbie-sox/3
-Accept: */*
-Upgrade: websocket
-Connection: Upgrade
-Sec-WebSocket-Version: 13
-Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==
-
-</protocol>
-<errorcode>
-0
-</errorcode>
-</verify>
-</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Frame types
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A TEXT/BINARY/PING/PONG/CLOSE message with payload
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%81%03txt]hex%%hex[%82%03bin]hex%%hex[%89%04ping]hex%%hex[%8a%04pong]hex%%hex[%88%07%03%e8close]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%81%83%00%00%00%00txt]hex%%hex[%82%83%00%00%00%00bin]hex%%hex[%89%84%00%00%00%00ping]hex%%hex[%8a%84%00%00%00%00pong]hex%%hex[%88%87%00%00%00%00%03%e8close]hex%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+txt fin [3] txt
+bin fin [3] bin
+ping [4] ping
+pong [4] pong
+close [7] %hex[%03%e8]hex%close
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid opcode 0x3
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An empty frame with the reserved opcode 0x3
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%83%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid opcode 0xB
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An empty frame with the reserved opcode 0xB
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%8b%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid reserved bit RSV1
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An empty frame with the RSV1 bit set
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%C1%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid reserved bit RSV2
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An empty frame with the RSV2 bit set
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%A1%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid reserved bit RSV3
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An empty frame with the RSV3 bit set
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%91%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid masked server message
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An empty frame with masking
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%81%80%12%34%56%78]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Peculiar frame sizes
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# Frames with sizes around special cases of the frame encoding
+# see https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
+# - 0: empty frame
+# - 125: largest payload with 7-bit encoding
+# - 126: smallest payload with 16-bit encoding AND this value in first byte indicates 16-bit encoding
+# - 127: this value in first byte indicates 64-bit encoding
+# - 65535: largest payload with 16-bit length encoding
+# - 65536: smallest payload with 64-bit length encoding
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%81%00]hex%%hex[%81%7d]hex%%repeat[125 x _]%%hex[%81%7e%00%7e]hex%%repeat[126 x _]%%hex[%81%7e%00%7f]hex%%repeat[127 x _]%%hex[%81%7e%ff%ff]hex%%repeat[65535 x _]%%hex[%81%7f%00%00%00%00%00%01%00%00]hex%%repeat[65536 x _]%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%81%80%00%00%00%00]hex%%hex[%81%fd%00%00%00%00]hex%%repeat[125 x _]%%hex[%81%fe%00%7e%00%00%00%00]hex%%repeat[126 x _]%%hex[%81%fe%00%7f%00%00%00%00]hex%%repeat[127 x _]%%hex[%81%fe%ff%ff%00%00%00%00]hex%%repeat[65535 x _]%%hex[%81%ff%00%00%00%00%00%01%00%00%00%00%00%00]hex%%repeat[65536 x _]%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt fin [0]
+txt fin [125] %repeat[125 x _]%
+txt fin [126] %repeat[126 x _]%
+txt fin [127] %repeat[127 x _]%
+txt fin [65535] %repeat[65535 x _]%
+txt fin [65536] %repeat[65536 x _]%
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Automatic PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+LIB2700_AUTO_PONG=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# PING "test"
+# CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%04test]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# PONG "test" (payload MUST equal ping payload)
+# see https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
+# CLOSE
+<protocol nonewline="yes">
+%hex[%8A%84%00%00%00%00test%88%80%00%00%00%00]hex%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: No automatic PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# PING "test"
+# CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%04test]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%89%84%00%00%00%00test]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+# PING is given to application
+<stdout>
+ping [4] test
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Unsolicited PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# An unsolicited PONG with and without payload
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%8a%00]hex%%hex[%8a%04pong]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%8a%80%00%00%00%00]hex%%hex[%8a%84%00%00%00%00pong]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+pong [0]
+pong [4] pong
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Empty PING/PONG/CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A PING/PONG/CLOSE message without payload
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%00]hex%%hex[%8a%00]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%89%80%00%00%00%00]hex%%hex[%8a%80%00%00%00%00]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+ping [0]
+pong [0]
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Max sized PING/PONG/CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A PING/PONG/CLOSE with 125 bytes payload each
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%7d]hex%%repeat[125 x _]%%hex[%8a%7d]hex%%repeat[125 x _]%%hex[%88%7d%03%e8]hex%%repeat[123 x _]%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%89%fd%00%00%00%00]hex%%repeat[125 x _]%%hex[%8a%fd%00%00%00%00]hex%%repeat[125 x _]%%hex[%88%fd%00%00%00%00%03%e8]hex%%repeat[123 x _]%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+ping [125] %repeat[125 x _]%
+pong [125] %repeat[125 x _]%
+close [125] %hex[%03%e8]hex%%repeat[123 x _]%
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid oversized PING
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A 126 byte long PING
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%7e%00%7e]hex%%repeat[126 x _]%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid oversized PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A 126 byte long PONG
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%8a%7e%00%7e]hex%%repeat[126 x _]%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid oversized CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A 126 byte long CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%88%7e%00%7e%03%e8]hex%%repeat[124 x _]%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented PING
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A fragmented PING
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%09%02p1]hex%%hex[%80%02p2]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A fragmented PONG
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%0a%02p1]hex%%hex[%80%02p2]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A fragmented CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%08%02p1]hex%%hex[%80%02p2]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Fragmented messages
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# Fragmented TEXT/BINARY messages, each with 2/3 fragments
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%02t1]hex%%hex[%80%02t2]hex%%hex[%01%02t1]hex%%hex[%00%02t2]hex%%hex[%80%02t3]hex%%hex[%02%02b1]hex%%hex[%80%02b2]hex%%hex[%02%02b1]hex%%hex[%00%02b2]hex%%hex[%80%02b3]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%01%82%00%00%00%00t1]hex%%hex[%80%82%00%00%00%00t2]hex%%hex[%01%82%00%00%00%00t1]hex%%hex[%00%82%00%00%00%00t2]hex%%hex[%80%82%00%00%00%00t3]hex%%hex[%02%82%00%00%00%00b1]hex%%hex[%80%82%00%00%00%00b2]hex%%hex[%02%82%00%00%00%00b1]hex%%hex[%00%82%00%00%00%00b2]hex%%hex[%80%82%00%00%00%00b3]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt --- [2] t1
+txt fin [2] t2
+txt --- [2] t1
+txt --- [2] t2
+txt fin [2] t3
+bin --- [2] b1
+bin fin [2] b2
+bin --- [2] b1
+bin --- [2] b2
+bin fin [2] b3
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Fragmented messages with empty fragments
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# 1st a message with an emtpy fragment at the beginning
+# 2nd a message with an emtpy fragment in the middle
+# 3rd a message with an emtpy fragment at the end
+# 4th a message with only empty fragments
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%00]hex%%hex[%00%02a1]hex%%hex[%80%02a2]hex%%hex[%01%02b1]hex%%hex[%00%00]hex%%hex[%80%02b2]hex%%hex[%01%02c1]hex%%hex[%00%02c2]hex%%hex[%80%00]hex%%hex[%01%00]hex%%hex[%00%00]hex%%hex[%80%00]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%01%80%00%00%00%00]hex%%hex[%00%82%00%00%00%00a1]hex%%hex[%80%82%00%00%00%00a2]hex%%hex[%01%82%00%00%00%00b1]hex%%hex[%00%80%00%00%00%00]hex%%hex[%80%82%00%00%00%00b2]hex%%hex[%01%82%00%00%00%00c1]hex%%hex[%00%82%00%00%00%00c2]hex%%hex[%80%80%00%00%00%00]hex%%hex[%01%80%00%00%00%00]hex%%hex[%00%80%00%00%00%00]hex%%hex[%80%80%00%00%00%00]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt --- [0]
+txt --- [2] a1
+txt fin [2] a2
+txt --- [2] b1
+txt --- [0]
+txt fin [2] b2
+txt --- [2] c1
+txt --- [2] c2
+txt fin [0]
+txt --- [0]
+txt --- [0]
+txt fin [0]
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Fragmented messages with interleaved pong
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# A TEXT/BINARY message fragmented into two frames each, with pongs in the middle
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%02t1]hex%%hex[%8a%02p1]hex%%hex[%80%02t2]hex%%hex[%02%02b1]hex%%hex[%8a%02p2]hex%%hex[%80%02b2]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%01%82%00%00%00%00t1]hex%%hex[%8a%82%00%00%00%00p1]hex%%hex[%80%82%00%00%00%00t2]hex%%hex[%02%82%00%00%00%00b1]hex%%hex[%8a%82%00%00%00%00p2]hex%%hex[%80%82%00%00%00%00b2]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt --- [2] t1
+pong [2] p1
+txt fin [2] t2
+bin --- [2] b1
+pong [2] p2
+bin fin [2] b2
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented message without initial frame
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# First a valid BINARY frame
+# Second a fragmented message with the first frame missing
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%82%01b]hex%%hex[%00%02m2]hex%%hex[%80%02m3]hex%
+</data>
+</reply>
+
+<verify>
+
+# Echo of reply data with additional masking up to the missing initial frame
+<protocol nonewline="yes">
+%hex[%82%81%00%00%00%00b]hex%
+</protocol>
+
+# Only frames up to the missing initial frame
+<stdout>
+bin fin [1] b
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
--- /dev/null
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented message without final frame
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# First a fragmented TEXT message with the last frame missing
+# Second a valid BINARY frame
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%02t1]hex%%hex[%00%02t2]hex%%hex[%82%01b]hex%
+</data>
+</reply>
+
+<verify>
+
+# Echo of reply data with additional masking up to the missing final frame
+<protocol nonewline="yes">
+%hex[%01%82%00%00%00%00t1]hex%%hex[%00%82%00%00%00%00t2]hex%
+</protocol>
+
+# Only frames up to the missing final frame
+<stdout>
+txt --- [2] t1
+txt --- [2] t2
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
pytest.skip(f'example client not built: {client.name}')
url = f'ws://localhost:{env.ws_port}/'
r = client.run(args=[url, payload])
- r.check_exit_code(56)
+ r.check_exit_code(100) # CURLE_TOO_LARGE
def test_20_04_data_small(self, env: Env, ws_echo):
client = LocalClient(env=env, name='ws-data')
lib1945 lib1946 lib1947 lib1948 lib1955 lib1956 lib1957 lib1958 lib1959 \
lib1960 lib1964 \
lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 lib1978 \
- lib2301 lib2302 lib2304 lib2305 lib2306 lib2308 lib2309 lib2310 \
- lib2311 lib2312 \
+ lib2301 lib2302 lib2304 lib2306 lib2308 lib2309 \
lib2402 lib2404 lib2405 \
lib2502 \
+ lib2700 \
lib3010 lib3025 lib3026 lib3027 \
lib3100 lib3101 lib3102 lib3103 lib3104 lib3105 lib3207 lib3208
lib2304_SOURCES = lib2304.c $(SUPPORTFILES)
lib2304_LDADD = $(TESTUTIL_LIBS)
-lib2305_SOURCES = lib2305.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
-lib2305_LDADD = $(TESTUTIL_LIBS)
-
lib2306_SOURCES = lib2306.c $(SUPPORTFILES)
lib2306_LDADD = $(TESTUTIL_LIBS)
lib2309_SOURCES = lib2309.c $(SUPPORTFILES)
lib2309_LDADD = $(TESTUTIL_LIBS)
-lib2310_SOURCES = lib2310.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
-lib2310_LDADD = $(TESTUTIL_LIBS)
-
-lib2311_SOURCES = lib2311.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
-lib2311_LDADD = $(TESTUTIL_LIBS)
-
-lib2312_SOURCES = lib2312.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
-lib2312_LDADD = $(TESTUTIL_LIBS)
-
lib2402_SOURCES = lib2402.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib2402_LDADD = $(TESTUTIL_LIBS)
lib2502_SOURCES = lib2502.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS)
lib2502_LDADD = $(TESTUTIL_LIBS)
+lib2700_SOURCES = lib2700.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
+lib2700_LDADD = $(TESTUTIL_LIBS)
+
lib3010_SOURCES = lib3010.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib3010_LDADD = $(TESTUTIL_LIBS)
+++ /dev/null
-/***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "test.h"
-#include "testtrace.h"
-#include "memdebug.h"
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-
-/* just close the connection */
-static void websocket_close(CURL *curl)
-{
- size_t sent;
- CURLcode result =
- curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE);
- curl_mfprintf(stderr,
- "ws: curl_ws_send returned %d, sent %d\n", result, (int)sent);
-}
-
-static void websocket(CURL *curl)
-{
- char buffer[256];
- const struct curl_ws_frame *meta;
- size_t nread;
- size_t i = 0;
- FILE *save = fopen(libtest_arg2, FOPEN_WRITETEXT);
- if(!save)
- return;
-
- /* Three 4097-bytes frames are expected, 12291 bytes */
- while(i < 12291) {
- CURLcode result =
- curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta);
- if(result) {
- if(result == CURLE_AGAIN)
- /* crude busy-loop */
- continue;
- fclose(save);
- curl_mprintf("curl_ws_recv returned %d\n", result);
- return;
- }
- curl_mprintf("%d: nread %zu Age %d Flags %x "
- "Offset %" CURL_FORMAT_CURL_OFF_T " "
- "Bytesleft %" CURL_FORMAT_CURL_OFF_T "\n",
- (int)i,
- nread, meta->age, meta->flags, meta->offset, meta->bytesleft);
- i += meta->len;
- fwrite(buffer, 1, nread, save);
- }
- fclose(save);
-
- websocket_close(curl);
-}
-
-CURLcode test(char *URL)
-{
- CURL *curl;
- CURLcode res = CURLE_OK;
-
- global_init(CURL_GLOBAL_ALL);
-
- curl = curl_easy_init();
- if(curl) {
- curl_easy_setopt(curl, CURLOPT_URL, URL);
-
- /* use the callback style */
- curl_easy_setopt(curl, CURLOPT_USERAGENT, "websocket/2304");
- libtest_debug_config.nohex = 1;
- libtest_debug_config.tracetime = 1;
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &libtest_debug_config);
- curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
- curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */
- res = curl_easy_perform(curl);
- curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", res);
- if(res == CURLE_OK)
- websocket(curl);
-
- /* always cleanup */
- curl_easy_cleanup(curl);
- }
- curl_global_cleanup();
- return res;
-}
-
-#else
-NO_SUPPORT_BUILT_IN
-#endif
+++ /dev/null
-/***************************************************************************
-* _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "test.h"
-#include "testtrace.h"
-#include "memdebug.h"
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-
-static size_t writecb(char *b, size_t size, size_t nitems, void *p)
-{
- (void)b;
- (void)size;
- (void)nitems;
- (void)p;
- return 0;
-}
-
-CURLcode test(char *URL)
-{
- CURL *curl;
- CURLcode res = CURLE_OK;
-
- global_init(CURL_GLOBAL_ALL);
-
- curl = curl_easy_init();
- if(curl) {
- curl_easy_setopt(curl, CURLOPT_URL, URL);
-
- /* use the callback style */
- curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3");
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl);
- res = curl_easy_perform(curl);
- curl_mprintf("Returned %d, should be %d.\n", res, CURLE_RECV_ERROR);
-
- /* always cleanup */
- curl_easy_cleanup(curl);
- }
- curl_global_cleanup();
- return CURLE_OK;
-}
-
-#else
-NO_SUPPORT_BUILT_IN
-#endif
+++ /dev/null
-/***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "test.h"
-#include "testtrace.h"
-#include "memdebug.h"
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-
-/* just close the connection */
-static void websocket_close(CURL *curl)
-{
- size_t sent;
- CURLcode result =
- curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE);
- curl_mfprintf(stderr,
- "ws: curl_ws_send returned %d, sent %zu\n", result, sent);
-}
-
-static void websocket_frame(CURL *curl, FILE *save, int expected_flags)
-{
- char buffer[256];
- const struct curl_ws_frame *meta;
- size_t nread;
- size_t total_read = 0;
-
- /* silence "unused parameter" warning */
- (void)expected_flags;
-
- /* Frames are expected to have 4097 bytes */
- while(true) {
- CURLcode result =
- curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta);
- if(result) {
- if(result == CURLE_AGAIN)
- /* crude busy-loop */
- continue;
- curl_mprintf("curl_ws_recv returned %d\n", result);
- return;
- }
- curl_mprintf("%d: nread %zu Age %d Flags %x "
- "Offset %" CURL_FORMAT_CURL_OFF_T " "
- "Bytesleft %" CURL_FORMAT_CURL_OFF_T "\n",
- (int)total_read,
- nread, meta->age, meta->flags, meta->offset, meta->bytesleft);
- assert(meta->flags == expected_flags);
- total_read += nread;
- fwrite(buffer, 1, nread, save);
- /* exit condition */
- if(meta->bytesleft == 0) {
- break;
- }
- }
-
- assert(total_read == 4097);
-}
-
-static void websocket(CURL *curl)
-{
- FILE *save = fopen(libtest_arg2, FOPEN_WRITETEXT);
- if(!save)
- return;
-
- /* Three frames are expected */
- websocket_frame(curl, save, CURLWS_TEXT | CURLWS_CONT);
- websocket_frame(curl, save, CURLWS_TEXT | CURLWS_CONT);
- websocket_frame(curl, save, CURLWS_TEXT);
-
- fclose(save);
- websocket_close(curl);
-}
-
-CURLcode test(char *URL)
-{
- CURL *curl;
- CURLcode res = CURLE_OK;
-
- global_init(CURL_GLOBAL_ALL);
-
- curl = curl_easy_init();
- if(curl) {
- curl_easy_setopt(curl, CURLOPT_URL, URL);
-
- /* use the callback style */
- curl_easy_setopt(curl, CURLOPT_USERAGENT, "websocket/2311");
- libtest_debug_config.nohex = 1;
- libtest_debug_config.tracetime = 1;
- curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &libtest_debug_config);
- curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
- curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */
- res = curl_easy_perform(curl);
- curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", res);
- if(res == CURLE_OK)
- websocket(curl);
-
- /* always cleanup */
- curl_easy_cleanup(curl);
- }
- curl_global_cleanup();
- return res;
-}
-
-#else
-NO_SUPPORT_BUILT_IN
-#endif
+++ /dev/null
-/***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at https://curl.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- * SPDX-License-Identifier: curl
- *
- ***************************************************************************/
-
-#include "test.h"
-
-#ifdef USE_WEBSOCKETS
-
-struct ping_check {
- CURL *curl;
- int pinged;
-};
-
-static size_t write_cb(char *b, size_t size, size_t nitems, void *p)
-{
- struct ping_check *ping_check = p;
- CURL *curl = ping_check->curl;
- const struct curl_ws_frame *frame = curl_ws_meta(curl);
- size_t sent = 0;
- size_t i = 0;
-
- /* upon ping, respond with input data, disconnect, mark a success */
- if(frame->flags & CURLWS_PING) {
- curl_mfprintf(stderr, "write_cb received ping with %zd bytes\n",
- size * nitems);
- curl_mfprintf(stderr, "\n");
- for(i = 0; i < size * nitems; i++) {
- curl_mfprintf(stderr, "%02X%s", (int)b[i],
- (i % 10 == 0 && i != 0) ? "\n" : " ");
- }
- curl_mfprintf(stderr, "\n");
- curl_mfprintf(stderr, "write_cb sending pong response\n");
- curl_ws_send(curl, b, size * nitems, &sent, 0, CURLWS_PONG);
- curl_mfprintf(stderr, "write_cb closing websocket\n");
- curl_ws_send(curl, NULL, 0, &sent, 0, CURLWS_CLOSE);
- ping_check->pinged = 1;
- }
- else {
- curl_mfprintf(stderr, "ping_check_cb: non-ping message, frame->flags %x\n",
- frame->flags);
- }
-
- return size * nitems;
-}
-
-CURLcode test(char *URL)
-{
- CURL *curl;
- CURLcode res = CURLE_OK;
- struct ping_check state;
-
- global_init(CURL_GLOBAL_ALL);
-
- curl = curl_easy_init();
- if(curl) {
- state.curl = curl;
- state.pinged = 0;
-
- curl_easy_setopt(curl, CURLOPT_URL, URL);
-
- /* use the callback style, without auto-pong */
- curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3");
- curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
- curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, (long)CURLWS_NOAUTOPONG);
- curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
- curl_easy_setopt(curl, CURLOPT_WRITEDATA, &state);
-
- res = curl_easy_perform(curl);
- curl_mfprintf(stderr, "curl_easy_perform() returned %u\n", (int)res);
-
- res = state.pinged ? 0 : 1;
-
- /* always cleanup */
- curl_easy_cleanup(curl);
- }
- curl_global_cleanup();
- return res;
-}
-
-#else /* no websockets */
-NO_SUPPORT_BUILT_IN
-#endif
--- /dev/null
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "test.h"
+#include "testtrace.h"
+#include "memdebug.h"
+
+#ifndef CURL_DISABLE_WEBSOCKETS
+
+static const char *descr_flags(int flags)
+{
+ if(flags & CURLWS_TEXT)
+ return flags & CURLWS_CONT ? "txt ---" : "txt fin";
+ if(flags & CURLWS_BINARY)
+ return flags & CURLWS_CONT ? "bin ---" : "bin fin";
+ if(flags & CURLWS_PING)
+ return "ping";
+ if(flags & CURLWS_PONG)
+ return "pong";
+ if(flags & CURLWS_CLOSE)
+ return "close";
+ assert(false);
+ return "";
+}
+
+static CURLcode send_header(CURL *curl, int flags, size_t size)
+{
+ CURLcode res = CURLE_OK;
+ size_t nsent;
+
+retry:
+ res = curl_ws_send(curl, NULL, 0, &nsent, (curl_off_t)size,
+ flags | CURLWS_OFFSET);
+ if(res == CURLE_AGAIN) {
+ assert(nsent == 0);
+ goto retry;
+ }
+ if(res) {
+ curl_mfprintf(stderr, "%s:%d curl_ws_send() failed with code %d (%s)\n",
+ __FILE__, __LINE__, res, curl_easy_strerror(res));
+ assert(nsent == 0);
+ return res;
+ }
+
+ assert(nsent == 0);
+
+ return CURLE_OK;
+}
+
+static CURLcode recv_header(CURL *curl, int *flags, curl_off_t *offset,
+ curl_off_t *bytesleft)
+{
+ CURLcode res = CURLE_OK;
+ size_t nread;
+ const struct curl_ws_frame *meta;
+
+ *flags = 0;
+ *offset = 0;
+ *bytesleft = 0;
+
+retry:
+ res = curl_ws_recv(curl, NULL, 0, &nread, &meta);
+ if(res == CURLE_AGAIN) {
+ assert(nread == 0);
+ goto retry;
+ }
+ if(res) {
+ curl_mfprintf(stderr, "%s:%d curl_ws_recv() failed with code %d (%s)\n",
+ __FILE__, __LINE__, res, curl_easy_strerror(res));
+ assert(nread == 0);
+ return res;
+ }
+
+ assert(nread == 0);
+ assert(meta != NULL);
+ assert(meta->flags);
+ assert(meta->offset == 0);
+
+ *flags = meta->flags;
+ *offset = meta->offset;
+ *bytesleft = meta->bytesleft;
+
+ curl_mfprintf(stdout, "%s [%" FMT_OFF_T "]", descr_flags(meta->flags),
+ meta->bytesleft);
+
+ if(meta->bytesleft > 0)
+ curl_mfprintf(stdout, " ");
+
+ res = send_header(curl, meta->flags, (size_t)meta->bytesleft);
+ if(res)
+ return res;
+
+ return CURLE_OK;
+}
+
+static CURLcode send_chunk(CURL *curl, int flags, const char *buffer,
+ size_t size, size_t *offset)
+{
+ CURLcode res = CURLE_OK;
+ size_t nsent;
+
+retry:
+ res = curl_ws_send(curl, buffer + *offset, size - *offset, &nsent, 0,
+ flags);
+ if(res == CURLE_AGAIN) {
+ assert(nsent == 0);
+ goto retry;
+ }
+ if(res) {
+ curl_mfprintf(stderr, "%s:%d curl_ws_send() failed with code %d (%s)\n",
+ __FILE__, __LINE__, res, curl_easy_strerror(res));
+ assert(nsent == 0);
+ return res;
+ }
+
+ assert(nsent <= size - *offset);
+
+ *offset += nsent;
+
+ return CURLE_OK;
+}
+
+static CURLcode recv_chunk(CURL *curl, int flags, curl_off_t *offset,
+ curl_off_t *bytesleft)
+{
+ CURLcode res = CURLE_OK;
+ char buffer[256];
+ size_t nread;
+ const struct curl_ws_frame *meta;
+ size_t sendoffset = 0;
+
+retry:
+ res = curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta);
+ if(res == CURLE_AGAIN) {
+ assert(nread == 0);
+ goto retry;
+ }
+ if(res) {
+ curl_mfprintf(stderr, "%s:%d curl_ws_recv() failed with code %d (%s)\n",
+ __FILE__, __LINE__, res, curl_easy_strerror(res));
+ assert(nread == 0);
+ return res;
+ }
+
+ assert(nread <= sizeof(buffer));
+ assert(meta != NULL);
+ assert(meta->flags == flags);
+ assert(meta->offset == *offset);
+ assert(meta->bytesleft == (*bytesleft - (curl_off_t)nread));
+
+ *offset += nread;
+ *bytesleft -= nread;
+
+ fwrite(buffer, 1, nread, stdout);
+
+ while(sendoffset < nread) {
+ res = send_chunk(curl, flags, buffer, nread, &sendoffset);
+ if(res)
+ return res;
+ }
+
+ return CURLE_OK;
+}
+
+static CURLcode recv_frame(CURL *curl, bool *stop)
+{
+ CURLcode res = CURLE_OK;
+ int flags = 0;
+ curl_off_t offset = 0;
+ curl_off_t bytesleft = 0;
+
+ res = recv_header(curl, &flags, &offset, &bytesleft);
+ if(res)
+ return res;
+
+ while(bytesleft > 0) {
+ res = recv_chunk(curl, flags, &offset, &bytesleft);
+ if(res)
+ return res;
+ }
+
+ if(flags & CURLWS_CLOSE)
+ *stop = true;
+
+ curl_mfprintf(stdout, "\n");
+
+ return res;
+}
+
+CURLcode test(char *URL)
+{
+ CURLcode res = CURLE_OK;
+ bool stop = false;
+ CURL *curl;
+
+ global_init(CURL_GLOBAL_ALL);
+ curl_global_trace("ws");
+ easy_init(curl);
+
+ easy_setopt(curl, CURLOPT_URL, URL);
+ easy_setopt(curl, CURLOPT_USERAGENT, "client/test2700");
+ libtest_debug_config.nohex = 1;
+ libtest_debug_config.tracetime = 1;
+ easy_setopt(curl, CURLOPT_DEBUGDATA, &libtest_debug_config);
+ easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
+ easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+ easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
+ if(!getenv("LIB2700_AUTO_PONG"))
+ easy_setopt(curl, CURLOPT_WS_OPTIONS, (long)CURLWS_NOAUTOPONG);
+
+ res = curl_easy_perform(curl);
+ if(res) {
+ curl_mfprintf(stderr,
+ "%s:%d curl_easy_perform() failed with code %d (%s)\n",
+ __FILE__, __LINE__, res, curl_easy_strerror(res));
+ goto test_cleanup;
+ }
+
+ while(!stop) {
+ res = recv_frame(curl, &stop);
+ if(res)
+ goto test_cleanup;
+ }
+
+test_cleanup:
+ curl_easy_cleanup(curl);
+ curl_global_cleanup();
+ return res;
+}
+
+#else
+NO_SUPPORT_BUILT_IN
+#endif