CVE-2025-10148: predictable WebSocket mask
What the report claims
The curl WebSocket implementation reuses a fixed masking key for all frames, violating RFC 6455's requirement for unpredictable per-frame masking.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2025-10148 predictable WebSocket mask
Shown for comparison after analysis. The pipeline did not see this label.
ProveIt's finding
The available evidence does not settle the report. A maintainer should investigate.
See the withdrawn model reasoning
The following reasoning failed the citation check and does not support the final finding.
- The code generates a single 32-bit mask at connection establishment (Curl_ws_accept line 1340) and reuses it for every frame's header and payload masking (ws_enc_write_head line 879, ws_enc_write_payload line 930).
- RFC 6455 §5.3 explicitly requires a fresh, unpredictable masking key for each frame; the current implementation violates this MUST requirement.
- The mask is transmitted in clear in every frame header, making it trivial for any observer to learn and predict all subsequent masking keys.
- This enables the cache poisoning attacks described in RFC 6455 §10.3, where an attacker can craft WebSocket frames that mimic HTTP requests to poison intermediary caches.
- The flaw exists in both the current master and the latest release (curl-8_15_0), as shown by the provided code for both versions.
How this analysis ran 6 claims · 9 citations verified
Evidence check: judge quoted an external source that does not say that (https://www.ietf.org/rfc/rfc6455.txt)
Checked against curl permalink 455afa1de518 (455afa1de518). Dev evaluation split. Citation verification checks that a referenced line was in the evidence; it does not prove the model interpreted it correctly.
Inspect the withdrawn draft reply
This draft belongs to the withdrawn judgment. It may contain unsupported claims.
Thank you for this clear and well-documented report. The code confirms that curl generates one masking key at WebSocket connection establishment and reuses it for all frames, violating RFC 6455 §5.3's requirement for a fresh, unpredictable key per frame. Since the mask is sent in clear in every frame header, an attacker can trivially predict all subsequent masking keys, enabling the cache poisoning attacks described in RFC 6455 §10.3. This is a valid security issue affecting all versions with WebSocket support. We will work on a fix that generates a new random mask for each outgoing frame.
Inspect all claims and source code 6 claims · 5 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
generates a fixed masking key at the beginning of a connection and re-uses it for every frame
Read the reasoning
The mask is generated once at connection acceptance (line 1340) and then reused unchanged in both ws_enc_write_head (line 879) and ws_enc_write_payload (line 930) for all frames sent on that connection.
masking key generation occurs in Curl_ws_accept at lib/ws.c line 1340
Read the reasoning
Line 1340-1341 in master (line 1028-1029 in curl-8_15_0) shows the masking key generation in Curl_ws_accept.
masking key usage occurs in ws_enc_write_head at lib/ws.c line 879
Read the reasoning
Line 879 in master shows the mask being copied into each outgoing frame's header.
masking key usage occurs in ws_enc_write_payload at lib/ws.c line 930
Read the reasoning
Line 930 in master shows the same mask being used to XOR each payload byte.
trivial for an attacker to guess the masking key of all subsequent frames
Read the reasoning
Since the 32-bit mask is sent in clear in every frame header (line 879) and reused for all payload masking (line 930), an observer who sees one frame learns the mask for all subsequent frames.
None
Read the reasoning
The report accurately states no reproduction steps were provided.
What the report says about other projects
confirmed RFC 6455 §5.3 requires a fresh masking key for each frame: 'When preparing a masked frame, the client MUST pick a fresh masking key from the set of allowed 32-bit values. The masking key needs to be unpredictable; thus, the masking key MUST be derived from a strong source of entropy, and the masking key for a given frame MUST NOT make it simple for a server/proxy to predict the masking key for a subsequent frame.'
“The masking key is a 32-bit value chosen at random by the client. When preparing a masked frame, the client MUST pick a fresh masking key from the set of allowed 32-bit values. The masking key needs to be unpredictable; thus, the masking key MUST be derived from a strong source of entropy, and the masking key for a given frame MUST NOT make it simple for a server/proxy to predict the masking key for a subsequent frame.” source
Read the original report (excerpt)
Source code
| 1276 | CURLcode Curl_ws_accept(struct Curl_easy *data, |
| 1277 | const char *mem, size_t nread) |
| 1278 | { |
| 1279 | struct SingleRequest *k = &data->req; |
| 1280 | struct websocket *ws; |
| 1281 | struct Curl_cwriter *ws_dec_writer = NULL; |
| 1282 | struct Curl_creader *ws_enc_reader = NULL; |
| 1283 | CURLcode result; |
| 1284 | |
| 1285 | DEBUGASSERT(data->conn); |
| 1286 | ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); |
| 1287 | if(!ws) { |
| 1288 | size_t chunk_size = WS_CHUNK_SIZE; |
| 1289 | ws = calloc(1, sizeof(*ws)); |
| 1290 | if(!ws) |
| 1291 | return CURLE_OUT_OF_MEMORY; |
| 1292 | #ifdef DEBUGBUILD |
| 1293 | { |
| 1294 | const char *p = getenv("CURL_WS_CHUNK_SIZE"); |
| 1295 | if(p) { |
| 1296 | curl_off_t l; |
| 1297 | if(!curlx_str_number(&p, &l, 1*1024*1024)) |
| 1298 | chunk_size = (size_t)l; |
| 1299 | } |
| 1300 | } |
| 1301 | #endif |
| 1302 | CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size); |
| 1303 | Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT, |
| 1304 | BUFQ_OPT_SOFT_LIMIT); |
| 1305 | Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT, |
| 1306 | BUFQ_OPT_SOFT_LIMIT); |
| 1307 | ws_dec_init(&ws->dec); |
| 1308 | ws_enc_init(&ws->enc); |
| 1309 | result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN, |
| 1310 | ws, ws_conn_dtor); |
| 1311 | if(result) |
| 1312 | return result; |
| 1313 | } |
| 1314 | else { |
| 1315 | Curl_bufq_reset(&ws->recvbuf); |
| 1316 | ws_dec_reset(&ws->dec); |
| 1317 | ws_enc_reset(&ws->enc); |
| 1318 | } |
| 1319 | /* Verify the Sec-WebSocket-Accept response. |
| 1320 | |
| 1321 | The sent value is the base64 encoded version of a SHA-1 hash done on the |
| 1322 | |Sec-WebSocket-Key| header field concatenated with |
| 1323 | the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". |
| 1324 | */ |
| 1325 | |
| 1326 | /* If the response includes a |Sec-WebSocket-Extensions| header field and |
| 1327 | this header field indicates the use of an extension that was not present |
| 1328 | in the client's handshake (the server has indicated an extension not |
| 1329 | requested by the client), the client MUST Fail the WebSocket Connection. |
| 1330 | */ |
| 1331 | |
| 1332 | /* If the response includes a |Sec-WebSocket-Protocol| header field |
| 1333 | and this header field indicates the use of a subprotocol that was |
| 1334 | not present in the client's handshake (the server has indicated a |
| 1335 | subprotocol not requested by the client), the client MUST Fail |
| 1336 | the WebSocket Connection. */ |
| 1337 | |
| 1338 | /* 4 bytes random */ |
| 1339 | |
| 1340 | result = Curl_rand(data, (unsigned char *)&ws->enc.mask, |
| C1: Curl_rand generates mask once in Curl_ws_accept | |
| C2: Curl_rand call generating ws->enc.mask | |
| C5: Single 32-bit mask generated once | |
| 1341 | sizeof(ws->enc.mask)); |
| 1342 | if(result) |
| 1343 | return result; |
| 1344 | |
| 1345 | #ifdef DEBUGBUILD |
| 1346 | if(getenv("CURL_WS_FORCE_ZERO_MASK")) |
| 1347 | /* force the bit mask to 0x00000000, effectively disabling masking */ |
| 1348 | memset(ws->enc.mask, 0, sizeof(ws->enc.mask)); |
| 1349 | #endif |
| 1350 | |
| 1351 | infof(data, "[WS] Received 101, switch to WebSocket; mask %02x%02x%02x%02x", |
| 1352 | ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]); |
| 1353 | |
| 1354 | /* Install our client writer that decodes WS frames payload */ |
| 1355 | result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode, |
| 1356 | CURL_CW_CONTENT_DECODE); |
| 1357 | if(result) |
| 1358 | goto out; |
| 1359 | result = Curl_cwriter_add(data, ws_dec_writer); |
| 1360 | if(result) |
| 1361 | goto out; |
| 1362 | ws_dec_writer = NULL; /* owned by transfer now */ |
| 1363 | |
| 1364 | if(data->set.connect_only) { |
| 1365 | size_t nwritten; |
| 1366 | /* In CONNECT_ONLY setup, the payloads from `mem` need to be received |
| 1367 | * when using `curl_ws_recv` later on after this transfer is already |
| 1368 | * marked as DONE. */ |
| 1369 | result = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem, |
| 1370 | nread, &nwritten); |
| 1371 | if(result) |
| 1372 | goto out; |
| 1373 | DEBUGASSERT(nread == nwritten); |
| 1374 | k->keepon &= ~KEEP_RECV; /* read no more content */ |
| 1375 | } |
| 1376 | else { /* !connect_only */ |
| 1377 | if(data->set.method == HTTPREQ_PUT) { |
| 1378 | CURL_TRC_WS(data, "UPLOAD set, add ws-encode reader"); |
| 1379 | result = Curl_creader_set_fread(data, -1); |
| 1380 | if(result) |
| 1381 | goto out; |
| 1382 | |
| 1383 | if(!data->set.ws_raw_mode) { |
| 1384 | /* Add our client readerr encoding WS BINARY frames */ |
| 1385 | result = Curl_creader_create(&ws_enc_reader, data, &ws_cr_encode, |
| 1386 | CURL_CR_CONTENT_ENCODE); |
| 1387 | if(result) |
| 1388 | goto out; |
| 1389 | result = Curl_creader_add(data, ws_enc_reader); |
| 1390 | if(result) |
| 1391 | goto out; |
| 1392 | ws_enc_reader = NULL; /* owned by transfer now */ |
| 1393 | } |
| 1394 | |
| 1395 | /* start over with sending */ |
| 1396 | data->req.eos_read = FALSE; |
| 1397 | data->req.upload_done = FALSE; |
| 1398 | k->keepon |= KEEP_SEND; |
| 1399 | } |
| 1400 | |
| 1401 | /* And pass any additional data to the writers */ |
| 1402 | if(nread) { |
| 1403 | result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread); |
| 1404 | if(result) |
| 1405 | goto out; |
| 1406 | } |
| 1407 | } |
| 1408 | |
| 1409 | k->upgr101 = UPGR101_RECEIVED; |
| 1410 | k->header = FALSE; /* we will not get more responses */ |
| 1411 | |
| 1412 | out: |
| 1413 | if(ws_dec_writer) |
| 1414 | Curl_cwriter_free(data, ws_dec_writer); |
| 1415 | if(ws_enc_reader) |
| 1416 | Curl_creader_free(data, ws_enc_reader); |
| 1417 | if(result) |
| 1418 | CURL_TRC_WS(data, "Curl_ws_accept() failed -> %d", result); |
| 1419 | else |
| 1420 | CURL_TRC_WS(data, "websocket established, %s mode", |
| 1421 | data->set.connect_only ? "connect-only" : "callback"); |
| 1422 | return result; |
| 1423 | } |
| 854 | head[2] = (unsigned char)((payload_len >> 56) & 0xff); |
| 855 | head[3] = (unsigned char)((payload_len >> 48) & 0xff); |
| 856 | head[4] = (unsigned char)((payload_len >> 40) & 0xff); |
| 857 | head[5] = (unsigned char)((payload_len >> 32) & 0xff); |
| 858 | head[6] = (unsigned char)((payload_len >> 24) & 0xff); |
| 859 | head[7] = (unsigned char)((payload_len >> 16) & 0xff); |
| 860 | head[8] = (unsigned char)((payload_len >> 8) & 0xff); |
| 861 | head[9] = (unsigned char)(payload_len & 0xff); |
| 862 | hlen = 10; |
| 863 | } |
| 864 | else if(payload_len >= 126) { |
| 865 | head[1] = 126 | WSBIT_MASK; |
| 866 | head[2] = (unsigned char)((payload_len >> 8) & 0xff); |
| 867 | head[3] = (unsigned char)(payload_len & 0xff); |
| 868 | hlen = 4; |
| 869 | } |
| 870 | else { |
| 871 | head[1] = (unsigned char)payload_len | WSBIT_MASK; |
| 872 | hlen = 2; |
| 873 | } |
| 874 | |
| 875 | enc->payload_remain = enc->payload_len = payload_len; |
| 876 | ws_enc_info(enc, data, "sending"); |
| 877 | |
| 878 | /* add 4 bytes mask */ |
| 879 | memcpy(&head[hlen], &enc->mask, 4); |
| C1: ws_enc_write_head copies enc->mask into every frame header | |
| C3: memcpy(&head[hlen], &enc->mask, 4) adds mask to frame header | |
| C5: Same mask sent in every frame header (unmasked) | |
| 880 | hlen += 4; |
| 881 | /* reset for payload to come */ |
| 882 | enc->xori = 0; |
| 883 | |
| 884 | result = Curl_bufq_write(out, head, hlen, &nwritten); |
| 885 | if(result) |
| 886 | return result; |
| 887 | if(nwritten != hlen) { |
| 888 | /* We use a bufq with SOFT_LIMIT, writing should always succeed */ |
| 889 | DEBUGASSERT(0); |
| 890 | return CURLE_SEND_ERROR; |
| 891 | } |
| 892 | return CURLE_OK; |
| 893 | } |
| 894 | |
| 895 | static CURLcode ws_enc_write_head(struct Curl_easy *data, |
| 896 | struct websocket *ws, |
| 897 | struct ws_encoder *enc, |
| 898 | unsigned int flags, |
| 899 | curl_off_t payload_len, |
| 900 | struct bufq *out) |
| 901 | { |
| 902 | /* starting a new frame, we want a clean sendbuf. |
| 903 | * Any pending control frame we can add now as part of the flush. */ |
| 904 | if(ws->pending.type) { |
| 905 | CURLcode result = ws_enc_add_pending(data, ws); |
| 906 | if(result) |
| 907 | return result; |
| 908 | } |
| 909 | return ws_enc_add_frame(data, enc, flags, payload_len, out); |
| 910 | } |
| 911 | |
| 912 | static CURLcode ws_enc_write_payload(struct ws_encoder *enc, |
| 913 | struct Curl_easy *data, |
| 914 | const unsigned char *buf, size_t buflen, |
| 915 | struct bufq *out, size_t *pnwritten) |
| 916 | { |
| 917 | CURLcode result; |
| 918 | size_t i, len, n; |
| 919 | |
| 920 | *pnwritten = 0; |
| 921 | if(Curl_bufq_is_full(out)) |
| 922 | return CURLE_AGAIN; |
| 923 | |
| 924 | /* not the most performant way to do this */ |
| 925 | len = buflen; |
| 926 | if((curl_off_t)len > enc->payload_remain) |
| 927 | len = (size_t)enc->payload_remain; |
| 928 | |
| 929 | for(i = 0; i < len; ++i) { |
| 930 | unsigned char c = buf[i] ^ enc->mask[enc->xori]; |
| C1: ws_enc_write_payload XORs payload with same enc->mask for every frame | |
| C4: buf[i] ^ enc->mask[enc->xori] uses mask for payload masking | |
| C5: Same mask used for all payload XOR operations | |
| 931 | result = Curl_bufq_write(out, &c, 1, &n); |
| 932 | if(result) { |
| 933 | if((result != CURLE_AGAIN) || !i) |
| 934 | return result; |
| 935 | break; |
| 936 | } |
| 937 | enc->xori++; |
| 938 | enc->xori &= 3; |
| 939 | } |
| 940 | *pnwritten = i; |
| 941 | enc->payload_remain -= (curl_off_t)i; |
| 942 | ws_enc_info(enc, data, "buffered"); |
| 943 | return CURLE_OK; |
| 944 | } |
| 945 | |
| 946 | static CURLcode ws_enc_add_pending(struct Curl_easy *data, |
| 947 | struct websocket *ws) |
| 948 | { |
| 949 | CURLcode result; |
| 950 | size_t n; |
| 951 | |
| 952 | if(!ws->pending.type) /* no pending frame here */ |
| 953 | return CURLE_OK; |
| 954 | if(ws->enc.payload_remain) /* in the middle of another frame */ |
| 955 | return CURLE_AGAIN; |
Show 111 lines (not cited by the judge)
| 965 | CURLcode Curl_ws_accept(struct Curl_easy *data, |
| 966 | const char *mem, size_t nread) |
| 967 | { |
| 968 | struct SingleRequest *k = &data->req; |
| 969 | struct websocket *ws; |
| 970 | struct Curl_cwriter *ws_dec_writer; |
| 971 | CURLcode result; |
| 972 | |
| 973 | DEBUGASSERT(data->conn); |
| 974 | ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); |
| 975 | if(!ws) { |
| 976 | size_t chunk_size = WS_CHUNK_SIZE; |
| 977 | ws = calloc(1, sizeof(*ws)); |
| 978 | if(!ws) |
| 979 | return CURLE_OUT_OF_MEMORY; |
| 980 | #ifdef DEBUGBUILD |
| 981 | { |
| 982 | const char *p = getenv("CURL_WS_CHUNK_SIZE"); |
| 983 | if(p) { |
| 984 | curl_off_t l; |
| 985 | if(!curlx_str_number(&p, &l, 1*1024*1024)) |
| 986 | chunk_size = (size_t)l; |
| 987 | } |
| 988 | } |
| 989 | #endif |
| 990 | CURL_TRC_WS(data, "WS, using chunk size %zu", chunk_size); |
| 991 | Curl_bufq_init2(&ws->recvbuf, chunk_size, WS_CHUNK_COUNT, |
| 992 | BUFQ_OPT_SOFT_LIMIT); |
| 993 | Curl_bufq_init2(&ws->sendbuf, chunk_size, WS_CHUNK_COUNT, |
| 994 | BUFQ_OPT_SOFT_LIMIT); |
| 995 | ws_dec_init(&ws->dec); |
| 996 | ws_enc_init(&ws->enc); |
| 997 | result = Curl_conn_meta_set(data->conn, CURL_META_PROTO_WS_CONN, |
| 998 | ws, ws_conn_dtor); |
| 999 | if(result) |
| 1000 | return result; |
| 1001 | } |
| 1002 | else { |
| 1003 | Curl_bufq_reset(&ws->recvbuf); |
| 1004 | ws_dec_reset(&ws->dec); |
| 1005 | ws_enc_reset(&ws->enc); |
| 1006 | } |
| 1007 | /* Verify the Sec-WebSocket-Accept response. |
| 1008 | |
| 1009 | The sent value is the base64 encoded version of a SHA-1 hash done on the |
| 1010 | |Sec-WebSocket-Key| header field concatenated with |
| 1011 | the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11". |
| 1012 | */ |
| 1013 | |
| 1014 | /* If the response includes a |Sec-WebSocket-Extensions| header field and |
| 1015 | this header field indicates the use of an extension that was not present |
| 1016 | in the client's handshake (the server has indicated an extension not |
| 1017 | requested by the client), the client MUST Fail the WebSocket Connection. |
| 1018 | */ |
| 1019 | |
| 1020 | /* If the response includes a |Sec-WebSocket-Protocol| header field |
| 1021 | and this header field indicates the use of a subprotocol that was |
| 1022 | not present in the client's handshake (the server has indicated a |
| 1023 | subprotocol not requested by the client), the client MUST Fail |
| 1024 | the WebSocket Connection. */ |
| 1025 | |
| 1026 | /* 4 bytes random */ |
| 1027 | |
| 1028 | result = Curl_rand(data, (unsigned char *)&ws->enc.mask, |
| 1029 | sizeof(ws->enc.mask)); |
| 1030 | if(result) |
| 1031 | return result; |
| 1032 | |
| 1033 | #ifdef DEBUGBUILD |
| 1034 | if(getenv("CURL_WS_FORCE_ZERO_MASK")) |
| 1035 | /* force the bit mask to 0x00000000, effectively disabling masking */ |
| 1036 | memset(ws->enc.mask, 0, sizeof(ws->enc.mask)); |
| 1037 | #endif |
| 1038 | |
| 1039 | infof(data, "[WS] Received 101, switch to WebSocket; mask %02x%02x%02x%02x", |
| 1040 | ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]); |
| 1041 | |
| 1042 | /* Install our client writer that decodes WS frames payload */ |
| 1043 | result = Curl_cwriter_create(&ws_dec_writer, data, &ws_cw_decode, |
| 1044 | CURL_CW_CONTENT_DECODE); |
| 1045 | if(result) |
| 1046 | return result; |
| 1047 | |
| 1048 | result = Curl_cwriter_add(data, ws_dec_writer); |
| 1049 | if(result) { |
| 1050 | Curl_cwriter_free(data, ws_dec_writer); |
| 1051 | return result; |
| 1052 | } |
| 1053 | |
| 1054 | if(data->set.connect_only) { |
| 1055 | size_t nwritten; |
| 1056 | /* In CONNECT_ONLY setup, the payloads from `mem` need to be received |
| 1057 | * when using `curl_ws_recv` later on after this transfer is already |
| 1058 | * marked as DONE. */ |
| 1059 | result = Curl_bufq_write(&ws->recvbuf, (const unsigned char *)mem, |
| 1060 | nread, &nwritten); |
| 1061 | if(result) |
| 1062 | return result; |
| 1063 | DEBUGASSERT(nread == nwritten); |
| 1064 | infof(data, "%zu bytes websocket payload", nread); |
| 1065 | } |
| 1066 | else { /* !connect_only */ |
| 1067 | /* And pass any additional data to the writers */ |
| 1068 | if(nread) { |
| 1069 | result = Curl_client_write(data, CLIENTWRITE_BODY, mem, nread); |
| 1070 | } |
| 1071 | } |
| 1072 | k->upgr101 = UPGR101_RECEIVED; |
| 1073 | |
| 1074 | return result; |
| 1075 | } |
Show 100 lines (not cited by the judge)
| 750 | static ssize_t ws_enc_write_head(struct Curl_easy *data, |
| 751 | struct ws_encoder *enc, |
| 752 | unsigned int flags, |
| 753 | curl_off_t payload_len, |
| 754 | struct bufq *out, |
| 755 | CURLcode *err) |
| 756 | { |
| 757 | unsigned char firstbyte = 0; |
| 758 | unsigned char head[14]; |
| 759 | size_t hlen, n; |
| 760 | |
| 761 | if(payload_len < 0) { |
| 762 | failf(data, "[WS] starting new frame with negative payload length %" |
| 763 | FMT_OFF_T, payload_len); |
| 764 | *err = CURLE_SEND_ERROR; |
| 765 | return -1; |
| 766 | } |
| 767 | |
| 768 | if(enc->payload_remain > 0) { |
| 769 | /* trying to write a new frame before the previous one is finished */ |
| 770 | failf(data, "[WS] starting new frame with %zd bytes from last one " |
| 771 | "remaining to be sent", (ssize_t)enc->payload_remain); |
| 772 | *err = CURLE_SEND_ERROR; |
| 773 | return -1; |
| 774 | } |
| 775 | |
| 776 | firstbyte = ws_frame_flags2firstbyte(data, flags, enc->contfragment, err); |
| 777 | if(*err) { |
| 778 | return -1; |
| 779 | } |
| 780 | |
| 781 | /* fragmentation only applies to data frames (text/binary); |
| 782 | * control frames (close/ping/pong) do not affect the CONT status */ |
| 783 | if(flags & (CURLWS_TEXT | CURLWS_BINARY)) { |
| 784 | enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE; |
| 785 | } |
| 786 | |
| 787 | if(flags & CURLWS_PING && payload_len > 125) { |
| 788 | /* The maximum valid size of PING frames is 125 bytes. */ |
| 789 | failf(data, "[WS] given PING frame is too big"); |
| 790 | *err = CURLE_TOO_LARGE; |
| 791 | return -1; |
| 792 | } |
| 793 | if(flags & CURLWS_PONG && payload_len > 125) { |
| 794 | /* The maximum valid size of PONG frames is 125 bytes. */ |
| 795 | failf(data, "[WS] given PONG frame is too big"); |
| 796 | *err = CURLE_TOO_LARGE; |
| 797 | return -1; |
| 798 | } |
| 799 | if(flags & CURLWS_CLOSE && payload_len > 125) { |
| 800 | /* The maximum valid size of CLOSE frames is 125 bytes. */ |
| 801 | failf(data, "[WS] given CLOSE frame is too big"); |
| 802 | *err = CURLE_TOO_LARGE; |
| 803 | return -1; |
| 804 | } |
| 805 | |
| 806 | head[0] = enc->firstbyte = firstbyte; |
| 807 | if(payload_len > 65535) { |
| 808 | head[1] = 127 | WSBIT_MASK; |
| 809 | head[2] = (unsigned char)((payload_len >> 56) & 0xff); |
| 810 | head[3] = (unsigned char)((payload_len >> 48) & 0xff); |
| 811 | head[4] = (unsigned char)((payload_len >> 40) & 0xff); |
| 812 | head[5] = (unsigned char)((payload_len >> 32) & 0xff); |
| 813 | head[6] = (unsigned char)((payload_len >> 24) & 0xff); |
| 814 | head[7] = (unsigned char)((payload_len >> 16) & 0xff); |
| 815 | head[8] = (unsigned char)((payload_len >> 8) & 0xff); |
| 816 | head[9] = (unsigned char)(payload_len & 0xff); |
| 817 | hlen = 10; |
| 818 | } |
| 819 | else if(payload_len >= 126) { |
| 820 | head[1] = 126 | WSBIT_MASK; |
| 821 | head[2] = (unsigned char)((payload_len >> 8) & 0xff); |
| 822 | head[3] = (unsigned char)(payload_len & 0xff); |
| 823 | hlen = 4; |
| 824 | } |
| 825 | else { |
| 826 | head[1] = (unsigned char)payload_len | WSBIT_MASK; |
| 827 | hlen = 2; |
| 828 | } |
| 829 | |
| 830 | enc->payload_remain = enc->payload_len = payload_len; |
| 831 | ws_enc_info(enc, data, "sending"); |
| 832 | |
| 833 | /* add 4 bytes mask */ |
| 834 | memcpy(&head[hlen], &enc->mask, 4); |
| 835 | hlen += 4; |
| 836 | /* reset for payload to come */ |
| 837 | enc->xori = 0; |
| 838 | |
| 839 | *err = Curl_bufq_write(out, head, hlen, &n); |
| 840 | if(*err) |
| 841 | return -1; |
| 842 | if(n != hlen) { |
| 843 | /* We use a bufq with SOFT_LIMIT, writing should always succeed */ |
| 844 | DEBUGASSERT(0); |
| 845 | *err = CURLE_SEND_ERROR; |
| 846 | return -1; |
| 847 | } |
| 848 | return (ssize_t)n; |
| 849 | } |
Show 32 lines (not cited by the judge)
| 851 | static ssize_t ws_enc_write_payload(struct ws_encoder *enc, |
| 852 | struct Curl_easy *data, |
| 853 | const unsigned char *buf, size_t buflen, |
| 854 | struct bufq *out, CURLcode *err) |
| 855 | { |
| 856 | size_t i, len, n; |
| 857 | |
| 858 | if(Curl_bufq_is_full(out)) { |
| 859 | *err = CURLE_AGAIN; |
| 860 | return -1; |
| 861 | } |
| 862 | |
| 863 | /* not the most performant way to do this */ |
| 864 | len = buflen; |
| 865 | if((curl_off_t)len > enc->payload_remain) |
| 866 | len = (size_t)enc->payload_remain; |
| 867 | |
| 868 | for(i = 0; i < len; ++i) { |
| 869 | unsigned char c = buf[i] ^ enc->mask[enc->xori]; |
| 870 | *err = Curl_bufq_write(out, &c, 1, &n); |
| 871 | if(*err) { |
| 872 | if((*err != CURLE_AGAIN) || !i) |
| 873 | return -1; |
| 874 | break; |
| 875 | } |
| 876 | enc->xori++; |
| 877 | enc->xori &= 3; |
| 878 | } |
| 879 | enc->payload_remain -= (curl_off_t)i; |
| 880 | ws_enc_info(enc, data, "buffered"); |
| 881 | return (ssize_t)i; |
| 882 | } |