CVE-2026-107928: info leak uploading over WebSockets with GnuTLS
What the report claims
A malicious WebSocket server can cause curl built with GnuTLS to leak memory during large file uploads over wss:// by triggering an out-of-bounds read in gtls_send() when handling PING frames.
Compare with curl's recorded outcome
Known vulnerability
A real vulnerability: CVE-2026-107928 info leak uploading over WebSockets with GnuTLS
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 release curl-8_22_0 code in gtls_send() has only a DEBUGASSERT checking nwritten <= remain; in release builds this check disappears, allowing buf to advance past the provided buffer and remain to wrap.
- The trigger scenario is plausible: a blocked upload leaves a 16384-byte TLS record queued in GnuTLS; a server-sent PING causes curl to send a PONG (10 bytes) via ws_flush(), which calls gtls_send with a small buffer; gnutls_record_send returns the queued 16384 bytes, the unchecked arithmetic wraps remain, and subsequent iterations read heap memory.
- Master branch has since added an explicit check (lines 2098-2102 in master gtls_send) that returns CURLE_SEND_ERROR when nwritten > remain, confirming the flaw was recognized and fixed in master but remains in curl-8_22_0.
- The issue is a remote information leak (memory disclosure to a malicious server), which is not listed in the project's 'Not security issues' policy sections.
How this analysis ran 6 claims · 11 citations verified
Evidence check: judge quoted an external source that does not say that (https://man7.org/linux/man-pages/man3/gnutls_record_send.3.html)
Checked against curl master on 2026-10-09 (424aa994ac22). 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 the detailed report. The flaw is confirmed in curl-8.22.0: gtls_send() lacks a release-build check that nwritten <= remain, so when GnuTLS returns a queued record size larger than the current buffer (triggered by a PONG interleaving with a blocked upload), the buffer pointer wraps and reads heap memory. Master has since added the check (gtls.c:2098-2102). We will backport the fix and assign a CVE.
Inspect all claims and source code 6 claims · 30 code excerpts
This model judgment was withdrawn. The claim assessments below are preserved for inspection and may be wrong.
Report claims
gtls_send() performs an out-of-bounds read when gnutls_record_send() returns a byte count greater than the provided buffer length, causing memory disclosure and potential heap corruption.
Read the reasoning
The release code at curl-8_22_0 has only a DEBUGASSERT guarding against nwritten > remain. In release builds, if gnutls_record_send returns a value larger than remain, the buffer pointer is advanced past the provided buffer and remain wraps to a very large size_t value, causing subsequent iterations to read out of bounds.
The flaw occurs in vtls.c at line 2095 where buf is set to out + 16384 without checking bounds.
Read the reasoning
Line 2095 in the release version advances the buffer pointer by nwritten without verifying nwritten <= remain. The master branch added a check at lines 2098-2102, but it is not present in curl-8_22_0.
A malicious server sends a WebSocket PING after curl's upload blocks, causing the next gtls_send() call to process a PONG with a buffer length of 1304 while the internal remain variable wraps to a large value.
Read the reasoning
The code path exists: ws_flush() sends pending control frames (PONG) via gtls_send() with a small buffer (1304 bytes in the report) while GnuTLS still has a queued 16384-byte record from the blocked upload. The deterministic checks confirm ws_flush, ws_enc_add_pending, and ws_enc_add_cntrl exist.
The server can read arbitrary memory from curl's process, leading to disclosure of client process memory to a malicious wss:// server.
Read the reasoning
Once remain wraps and buf points past the send buffer, the while loop continues calling gnutls_record_send with pointers into adjacent heap memory, which gets encrypted and sent to the server.
Upload a 32 MB file to a malicious wss:// server using curl built with GnuTLS; the server must send a PING that triggers the out-of-bounds read in gtls_send().
Read the reasoning
The reproduction steps describe a concrete sequence: large upload blocks, server sends PING, curl's PONG send interleaves with the queued TLS record. The deterministic checks confirm all symbols in the call chain exist.
Applications using curl_easy_send() with a shorter buffer after CURLE_AGAIN may experience the same out-of-bounds behavior, returning CURLE_OK with an incorrect nwritten value.
Read the reasoning
If an application calls curl_easy_send with a shorter buffer after CURLE_AGAIN, the same gtls_send logic applies: gnutls_record_send may return the queued record size, the DEBUGASSERT is absent in release, and the buffer pointer wraps.
Deterministic checks that failed
- quote only 0/12 quoted lines appear anywhere in curl at 424aa994ac22
What the report says about other projects
confirmed GnuTLS gnutls_record_send can return a number of bytes sent greater than the data_size provided, when resuming a previously interrupted send with a queued record.
“If GNUTLS_E_INTERRUPTED or GNUTLS_E_AGAIN is returned, you must call this function again with the exact same parameters, or provide a NULL pointer for data and 0 for data_size, in order to write the same data as before.” source
confirmed GnuTLS keeps a private buffer per session for queued records.
“Could it be that you are resuming a previously interrupted send? gnutls keep a private buffer per session” source
Read the original report (excerpt)
Source code
| 2072 | static CURLcode gtls_send(struct Curl_cfilter *cf, |
| C5: gtls_send entry point for the flaw | |
| 2073 | struct Curl_easy *data, |
| 2074 | const void *buf, |
| 2075 | size_t blen, |
| 2076 | size_t *pnwritten) |
| 2077 | { |
| 2078 | struct ssl_connect_data *connssl = cf->ctx; |
| 2079 | struct gtls_ssl_backend_data *backend = |
| 2080 | (struct gtls_ssl_backend_data *)connssl->backend; |
| 2081 | CURLcode result = CURLE_OK; |
| 2082 | ssize_t nwritten; |
| 2083 | size_t remain = blen; |
| 2084 | |
| 2085 | DEBUGASSERT(backend); |
| 2086 | *pnwritten = 0; |
| 2087 | |
| 2088 | while(remain) { |
| 2089 | backend->gtls.io_result = CURLE_OK; |
| 2090 | nwritten = gnutls_record_send(backend->gtls.session, buf, remain); |
| C4: gnutls_record_send called with out-of-bounds buf in subsequent loop iterations | |
| 2091 | |
| 2092 | if(nwritten >= 0) { |
| 2093 | *pnwritten += (size_t)nwritten; |
| 2094 | DEBUGASSERT((size_t)nwritten <= remain); |
| C1: Only DEBUGASSERT checks nwritten <= remain; in release builds this check is absent | |
| 2095 | buf = (char *)CURL_UNCONST(buf) + (size_t)nwritten; |
| C1: buf advanced by nwritten without bounds check | |
| C2: buf = (char *)CURL_UNCONST(buf) + (size_t)nwritten; no bounds check in release | |
| C4: buf advanced past provided buffer | |
| 2096 | remain -= (size_t)nwritten; |
| C1: remain decreased by nwritten; wraps to large value if nwritten > remain | |
| 2097 | } |
| 2098 | else { |
| 2099 | if(*pnwritten && (nwritten == GNUTLS_E_AGAIN)) { |
| 2100 | result = CURLE_OK; |
| 2101 | goto out; |
| 2102 | } |
| 2103 | result = (nwritten == GNUTLS_E_AGAIN) ? |
| 2104 | CURLE_AGAIN : |
| 2105 | (backend->gtls.io_result ? backend->gtls.io_result : CURLE_SEND_ERROR); |
| 2106 | goto out; |
| 2107 | } |
| 2108 | } |
| 2109 | |
| 2110 | out: |
| 2111 | CURL_TRC_CF(data, cf, "gtls_send(len=%zu) -> %d, %zu", |
| 2112 | blen, (int)result, *pnwritten); |
| 2113 | return result; |
| 2114 | } |
| 1339 | CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, |
| 1340 | size_t *n) |
| 1341 | { |
| 1342 | struct Curl_eapi_guard guard; |
| 1343 | CURLcode result; |
| 1344 | |
| 1345 | if(CURL_EAPI_ENTER(&guard, curl, easy_send, &result)) { |
| 1346 | struct Curl_easy *data = curl; |
| 1347 | size_t written = 0; |
| 1348 | |
| 1349 | result = Curl_senddata(data, buffer, buflen, &written); |
| C6: curl_easy_send calls Curl_senddata which reaches gtls_send | |
| 1350 | *n = written; |
| 1351 | } |
| 1352 | CURL_EAPI_LEAVE(&guard); |
| 1353 | return result; |
| 1354 | } |
| 619 | static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, |
| 620 | bool blocking); |
| 621 | static CURLcode ws_enc_add_pending(struct Curl_easy *data, |
| 622 | struct websocket *ws); |
| 623 | |
| 624 | static CURLcode ws_enc_add_cntrl(struct Curl_easy *data, |
| 625 | struct websocket *ws, |
| 626 | const uint8_t *payload, |
| 627 | size_t plen, |
| C3: ws_enc_add_pending moves pending control frame (PONG) to sendbuf | |
| 628 | unsigned int frame_type) |
| 629 | { |
| 630 | (void)data; |
| 631 | DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN); |
| 632 | if(plen > WS_MAX_CNTRL_LEN) |
| 633 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 634 | |
| 635 | /* Overwrite any pending frame with the new one, we keep |
| 636 | * only one. */ |
| 637 | ws->pending.type = frame_type; |
| 638 | ws->pending.payload_len = plen; |
| 639 | memcpy(ws->pending.payload, payload, plen); |
| 640 | return CURLE_OK; |
| 641 | } |
| 618 | static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, |
| C3: ws_flush calls ws_enc_add_pending then sends via gtls_send | |
| C5: ws_flush triggers the interleaved send | |
| 619 | bool blocking); |
| 620 | static CURLcode ws_enc_send(struct Curl_easy *data, |
| 621 | struct websocket *ws, |
| 622 | const uint8_t *buffer, |
| 623 | size_t buflen, |
| 624 | curl_off_t fragsize, |
| 625 | unsigned int flags, |
| 626 | size_t *pnsent); |
| 627 | static CURLcode ws_enc_add_pending(struct Curl_easy *data, |
| 628 | struct websocket *ws); |
| 629 | |
| 630 | static CURLcode ws_enc_add_cntrl(struct Curl_easy *data, |
| 631 | struct websocket *ws, |
| 632 | const uint8_t *payload, |
| 633 | size_t plen, |
| 634 | unsigned int frame_type) |
| 635 | { |
| 636 | (void)data; |
| 637 | DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN); |
| 638 | if(plen > WS_MAX_CNTRL_LEN) |
| 639 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 640 | |
| 641 | /* Overwrite any pending frame with the new one, we keep |
| 642 | * only one. */ |
| 643 | ws->pending.type = frame_type; |
| 644 | ws->pending.payload_len = plen; |
| 645 | memcpy(ws->pending.payload, payload, plen); |
| 646 | return CURLE_OK; |
| 647 | } |
Show 48 lines (not cited by the judge)
| 2077 | static CURLcode gtls_send(struct Curl_cfilter *cf, |
| 2078 | struct Curl_easy *data, |
| 2079 | const void *buf, |
| 2080 | size_t blen, |
| 2081 | size_t *pnwritten) |
| 2082 | { |
| 2083 | struct ssl_connect_data *connssl = cf->ctx; |
| 2084 | struct gtls_ssl_backend_data *backend = |
| 2085 | (struct gtls_ssl_backend_data *)connssl->backend; |
| 2086 | CURLcode result = CURLE_OK; |
| 2087 | ssize_t nwritten; |
| 2088 | size_t remain = blen; |
| 2089 | |
| 2090 | DEBUGASSERT(backend); |
| 2091 | *pnwritten = 0; |
| 2092 | |
| 2093 | while(remain) { |
| 2094 | backend->gtls.io_result = CURLE_OK; |
| 2095 | nwritten = gnutls_record_send(backend->gtls.session, buf, remain); |
| 2096 | |
| 2097 | if(nwritten >= 0) { |
| 2098 | if((size_t)nwritten > remain) { |
| 2099 | DEBUGASSERT(0); |
| 2100 | failf(data, "gnutls_record_send(len=%zu) returned %zd", |
| 2101 | remain, nwritten); |
| 2102 | return CURLE_SEND_ERROR; |
| 2103 | } |
| 2104 | *pnwritten += (size_t)nwritten; |
| 2105 | buf = (char *)CURL_UNCONST(buf) + (size_t)nwritten; |
| 2106 | remain -= (size_t)nwritten; |
| 2107 | } |
| 2108 | else { |
| 2109 | if(*pnwritten && (nwritten == GNUTLS_E_AGAIN)) { |
| 2110 | result = CURLE_OK; |
| 2111 | goto out; |
| 2112 | } |
| 2113 | result = (nwritten == GNUTLS_E_AGAIN) ? |
| 2114 | CURLE_AGAIN : |
| 2115 | (backend->gtls.io_result ? backend->gtls.io_result : CURLE_SEND_ERROR); |
| 2116 | goto out; |
| 2117 | } |
| 2118 | } |
| 2119 | |
| 2120 | out: |
| 2121 | CURL_TRC_CF(data, cf, "gtls_send(len=%zu) -> %d, %zu", |
| 2122 | blen, (int)result, *pnwritten); |
| 2123 | return result; |
| 2124 | } |
Show 21 lines (not cited by the judge)
| 1371 | CURLcode curl_easy_send(CURL *curl, const void *buffer, size_t buflen, |
| 1372 | size_t *n) |
| 1373 | { |
| 1374 | struct Curl_eapi_guard guard; |
| 1375 | CURLcode result; |
| 1376 | |
| 1377 | if(CURL_EAPI_ENTER(&guard, curl, easy_send, &result)) { |
| 1378 | struct Curl_easy *data = curl; |
| 1379 | size_t written = 0; |
| 1380 | |
| 1381 | if(!n || (buflen && !buffer)) { |
| 1382 | result = CURLE_BAD_FUNCTION_ARGUMENT; |
| 1383 | goto out; |
| 1384 | } |
| 1385 | result = Curl_senddata(data, buffer, buflen, &written); |
| 1386 | *n = written; |
| 1387 | } |
| 1388 | out: |
| 1389 | CURL_EAPI_LEAVE(&guard); |
| 1390 | return result; |
| 1391 | } |
Show 5 lines (not cited by the judge)
| 417 | CURLcode Curl_bufq_cread(struct bufq *q, char *buf, size_t len, |
| 418 | size_t *pnread) |
| 419 | { |
| 420 | return Curl_bufq_read(q, (uint8_t *)buf, len, pnread); |
| 421 | } |
Show 14 lines (not cited by the judge)
| 423 | bool Curl_bufq_peek(struct bufq *q, |
| 424 | const uint8_t **pbuf, size_t *plen) |
| 425 | { |
| 426 | if(q->head && chunk_is_empty(q->head)) { |
| 427 | prune_head(q); |
| 428 | } |
| 429 | if(q->head && !chunk_is_empty(q->head)) { |
| 430 | chunk_peek(q->head, pbuf, plen); |
| 431 | return TRUE; |
| 432 | } |
| 433 | *pbuf = NULL; |
| 434 | *plen = 0; |
| 435 | return FALSE; |
| 436 | } |
Show 10 lines (not cited by the judge)
| 461 | void Curl_bufq_skip(struct bufq *q, size_t amount) |
| 462 | { |
| 463 | size_t n; |
| 464 | |
| 465 | while(amount && q->head) { |
| 466 | n = chunk_skip(q->head, amount); |
| 467 | amount -= n; |
| 468 | prune_head(q); |
| 469 | } |
| 470 | } |
Show 22 lines (not cited by the judge)
| 458 | CURLcode Curl_req_send_more(struct Curl_easy *data) |
| 459 | { |
| 460 | CURLcode result; |
| 461 | |
| 462 | /* Fill our send buffer if more from client can be read. */ |
| 463 | if(!data->req.upload_aborted && |
| 464 | !data->req.eos_read && |
| 465 | !Curl_xfer_send_is_paused(data) && |
| 466 | !Curl_bufq_is_full(&data->req.sendbuf)) { |
| 467 | size_t nread; |
| 468 | result = Curl_bufq_sipn(&data->req.sendbuf, 0, |
| 469 | add_from_client, data, &nread); |
| 470 | if(result && result != CURLE_AGAIN) |
| 471 | return result; |
| 472 | } |
| 473 | |
| 474 | result = req_flush(data); |
| 475 | if(result == CURLE_AGAIN) |
| 476 | result = CURLE_OK; |
| 477 | |
| 478 | return result; |
| 479 | } |
Show 71 lines (not cited by the judge)
| 354 | CURLcode Curl_sendrecv(struct Curl_easy *data) |
| 355 | { |
| 356 | struct SingleRequest *k = &data->req; |
| 357 | const struct curltime *pnow = NULL; |
| 358 | CURLcode result = CURLE_OK; |
| 359 | |
| 360 | if(Curl_xfer_is_blocked(data)) { |
| 361 | result = CURLE_OK; |
| 362 | goto out; |
| 363 | } |
| 364 | |
| 365 | /* We go ahead and do a read if we have a readable socket or if the stream |
| 366 | was rewound (in which case we have data in a buffer) */ |
| 367 | if(CURL_REQ_WANT_RECV(data)) { |
| 368 | result = sendrecv_dl(data, k); |
| 369 | if(result || data->req.done) |
| 370 | goto out; |
| 371 | } |
| 372 | |
| 373 | /* If we still have writing to do, we check if we have a writable socket. */ |
| 374 | if(Curl_req_want_send(data)) { |
| 375 | result = sendrecv_ul(data); |
| 376 | if(result) |
| 377 | goto out; |
| 378 | } |
| 379 | |
| 380 | pnow = Curl_pgrs_now(data); |
| 381 | if(CURL_REQ_WANT_IO(data)) { |
| 382 | if(Curl_timeleft_now_ms(data, pnow) < 0) { |
| 383 | if(k->size != -1) { |
| 384 | failf(data, "Operation timed out after %" FMT_TIMEDIFF_T |
| 385 | " milliseconds with %" FMT_OFF_T " out of %" |
| 386 | FMT_OFF_T " bytes received", |
| 387 | Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), |
| 388 | k->bytecount, k->size); |
| 389 | } |
| 390 | else { |
| 391 | failf(data, "Operation timed out after %" FMT_TIMEDIFF_T |
| 392 | " milliseconds with %" FMT_OFF_T " bytes received", |
| 393 | Curl_pgrs_since_ms(data, NULL, TIMER_STARTSINGLE), |
| 394 | k->bytecount); |
| 395 | } |
| 396 | result = CURLE_OPERATION_TIMEDOUT; |
| 397 | goto out; |
| 398 | } |
| 399 | } |
| 400 | else { |
| 401 | /* |
| 402 | * The transfer has been performed. Make some general checks before |
| 403 | * returning. |
| 404 | */ |
| 405 | if(!data->req.no_body && (k->size != -1) && |
| 406 | (k->bytecount != k->size) && !k->newurl) { |
| 407 | failf(data, "transfer closed with %" FMT_OFF_T |
| 408 | " bytes remaining to read", k->size - k->bytecount); |
| 409 | result = CURLE_PARTIAL_FILE; |
| 410 | goto out; |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | /* If there is nothing more to send/recv, the request is done */ |
| 415 | if(!CURL_REQ_WANT_IO(data)) |
| 416 | data->req.done = TRUE; |
| 417 | |
| 418 | result = Curl_pgrsCheckX(data, pnow); |
| 419 | |
| 420 | out: |
| 421 | if(result) |
| 422 | DEBUGF(infof(data, "Curl_sendrecv() -> %d", (int)result)); |
| 423 | return result; |
| 424 | } |
Show 22 lines (not cited by the judge)
| 811 | CURLcode Curl_xfer_send(struct Curl_easy *data, |
| 812 | const void *buf, size_t blen, bool eos, |
| 813 | size_t *pnwritten) |
| 814 | { |
| 815 | CURLcode result; |
| 816 | |
| 817 | DEBUGASSERT(data); |
| 818 | DEBUGASSERT(data->conn); |
| 819 | |
| 820 | result = Curl_conn_send(data, data->conn->send_idx, |
| 821 | buf, blen, eos, pnwritten); |
| 822 | if(result == CURLE_AGAIN) { |
| 823 | result = CURLE_OK; |
| 824 | *pnwritten = 0; |
| 825 | } |
| 826 | else if(!result && *pnwritten) |
| 827 | data->info.request_size += *pnwritten; |
| 828 | |
| 829 | DEBUGF(infof(data, "Curl_xfer_send(len=%zu, eos=%d) -> %d, %zu", |
| 830 | blen, eos, (int)result, *pnwritten)); |
| 831 | return result; |
| 832 | } |
Show 31 lines (not cited by the judge)
| 745 | CURLcode Curl_xfer_write_resp(struct Curl_easy *data, |
| 746 | const char *buf, size_t blen, |
| 747 | bool is_eos) |
| 748 | { |
| 749 | CURLcode result = CURLE_OK; |
| 750 | |
| 751 | if(data->conn->scheme->run->write_resp) { |
| 752 | /* protocol handlers offering this function take full responsibility |
| 753 | * for writing all received download data to the client. */ |
| 754 | result = data->conn->scheme->run->write_resp(data, buf, blen, is_eos); |
| 755 | } |
| 756 | else { |
| 757 | /* No special handling by protocol handler, write all received data |
| 758 | * as BODY to the client. */ |
| 759 | if(blen || is_eos) { |
| 760 | int cwtype = CLIENTWRITE_BODY; |
| 761 | if(is_eos) |
| 762 | cwtype |= CLIENTWRITE_EOS; |
| 763 | result = Curl_client_write(data, cwtype, buf, blen); |
| 764 | } |
| 765 | } |
| 766 | |
| 767 | if(!result && is_eos) { |
| 768 | /* If we wrote the EOS, we are definitely done */ |
| 769 | data->req.eos_written = TRUE; |
| 770 | data->req.download_done = TRUE; |
| 771 | } |
| 772 | CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d", |
| 773 | blen, is_eos, (int)result); |
| 774 | return result; |
| 775 | } |
Show 1 lines (not cited by the judge)
| 1044 | #define DEBUGASSERT(x) CURL_DEBUGASSERT(x) |
Show 13 lines (not cited by the judge)
| 351 | static CURLcode add_from_client(void *reader_ctx, |
| 352 | unsigned char *buf, size_t buflen, |
| 353 | size_t *pnread) |
| 354 | { |
| 355 | struct Curl_easy *data = reader_ctx; |
| 356 | CURLcode result; |
| 357 | bool eos; |
| 358 | |
| 359 | result = Curl_client_read(data, (char *)buf, buflen, pnread, &eos); |
| 360 | if(!result && eos) |
| 361 | data->req.eos_read = TRUE; |
| 362 | return result; |
| 363 | } |
Show 84 lines (not cited by the judge)
| 1196 | static CURLcode cr_ws_read(struct Curl_easy *data, |
| 1197 | struct Curl_creader *reader, |
| 1198 | char *buf, size_t blen, |
| 1199 | size_t *pnread, bool *peos) |
| 1200 | { |
| 1201 | struct cr_ws_ctx *ctx = reader->ctx; |
| 1202 | CURLcode result = CURLE_OK; |
| 1203 | size_t nread, n; |
| 1204 | struct websocket *ws; |
| 1205 | bool eos; |
| 1206 | |
| 1207 | *pnread = 0; |
| 1208 | if(ctx->eos) { |
| 1209 | *peos = TRUE; |
| 1210 | return CURLE_OK; |
| 1211 | } |
| 1212 | |
| 1213 | ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); |
| 1214 | if(!ws) { |
| 1215 | failf(data, "[WS] not a websocket transfer"); |
| 1216 | return CURLE_FAILED_INIT; |
| 1217 | } |
| 1218 | |
| 1219 | if(Curl_bufq_is_empty(&ws->sendbuf)) { |
| 1220 | if(ctx->read_eos) { |
| 1221 | ctx->eos = TRUE; |
| 1222 | *peos = TRUE; |
| 1223 | return CURLE_OK; |
| 1224 | } |
| 1225 | |
| 1226 | if(ws->enc.payload_remain) { |
| 1227 | CURL_TRC_WS(data, "current frame, %" FMT_OFF_T " remaining", |
| 1228 | ws->enc.payload_remain); |
| 1229 | blen = curlx_sotouz_range(ws->enc.payload_remain, 0, blen); |
| 1230 | } |
| 1231 | |
| 1232 | result = Curl_creader_read(data, reader->next, buf, blen, &nread, &eos); |
| 1233 | if(result) |
| 1234 | return result; |
| 1235 | ctx->read_eos = eos; |
| 1236 | |
| 1237 | if(!Curl_bufq_is_empty(&ws->sendbuf)) { |
| 1238 | /* client_read started a new frame, we disregard any eos reported */ |
| 1239 | ctx->read_eos = FALSE; |
| 1240 | Curl_creader_clear_eos(data, reader->next); |
| 1241 | } |
| 1242 | else if(!nread) { |
| 1243 | /* nothing to convert, return this right away */ |
| 1244 | if(ctx->read_eos) |
| 1245 | ctx->eos = TRUE; |
| 1246 | *pnread = nread; |
| 1247 | *peos = (bool)ctx->eos; |
| 1248 | goto out; |
| 1249 | } |
| 1250 | |
| 1251 | if(!ws->enc.payload_remain && Curl_bufq_is_empty(&ws->sendbuf)) { |
| 1252 | /* encode the data as a new BINARY frame */ |
| 1253 | result = ws_enc_write_head(data, ws, &ws->enc, CURLWS_BINARY, nread, |
| 1254 | &ws->sendbuf); |
| 1255 | if(result) |
| 1256 | goto out; |
| 1257 | } |
| 1258 | |
| 1259 | result = ws_enc_write_payload(&ws->enc, data, (uint8_t *)buf, |
| 1260 | nread, &ws->sendbuf, &n); |
| 1261 | if(result) |
| 1262 | goto out; |
| 1263 | CURL_TRC_READ(data, "cr_ws_read, added %zu payload, len=%zu", nread, n); |
| 1264 | } |
| 1265 | |
| 1266 | DEBUGASSERT(!Curl_bufq_is_empty(&ws->sendbuf)); |
| 1267 | *peos = FALSE; |
| 1268 | result = Curl_bufq_cread(&ws->sendbuf, buf, blen, pnread); |
| 1269 | if(!result && ctx->read_eos && Curl_bufq_is_empty(&ws->sendbuf)) { |
| 1270 | /* no more data, read all, done. */ |
| 1271 | ctx->eos = TRUE; |
| 1272 | *peos = TRUE; |
| 1273 | } |
| 1274 | |
| 1275 | out: |
| 1276 | CURL_TRC_READ(data, "cr_ws_read(len=%zu) -> %d, nread=%zu, eos=%d", |
| 1277 | blen, (int)result, *pnread, *peos); |
| 1278 | return result; |
| 1279 | } |
Show 23 lines (not cited by the judge)
| 85 | static ssize_t gtls_push(void *s, const void *buf, size_t blen) |
| 86 | { |
| 87 | struct Curl_cfilter *cf = s; |
| 88 | struct ssl_connect_data *connssl = cf->ctx; |
| 89 | struct gtls_ssl_backend_data *backend = |
| 90 | (struct gtls_ssl_backend_data *)connssl->backend; |
| 91 | struct Curl_easy *data = CF_DATA_CURRENT(cf); |
| 92 | size_t nwritten; |
| 93 | CURLcode result; |
| 94 | |
| 95 | DEBUGASSERT(data); |
| 96 | result = Curl_conn_cf_send(cf->next, data, buf, blen, FALSE, &nwritten); |
| 97 | CURL_TRC_CF(data, cf, "gtls_push(len=%zu) -> %d, %zu", |
| 98 | blen, (int)result, nwritten); |
| 99 | backend->gtls.io_result = result; |
| 100 | if(result) { |
| 101 | /* !checksrc! disable ERRNOVAR 1 */ |
| 102 | gnutls_transport_set_errno(backend->gtls.session, |
| 103 | (result == CURLE_AGAIN) ? EAGAIN : EINVAL); |
| 104 | return -1; |
| 105 | } |
| 106 | return (ssize_t)nwritten; |
| 107 | } |
Show 52 lines (not cited by the judge)
| 298 | static CURLcode req_flush(struct Curl_easy *data) |
| 299 | { |
| 300 | CURLcode result; |
| 301 | |
| 302 | if(!data || !data->conn) |
| 303 | return CURLE_FAILED_INIT; |
| 304 | |
| 305 | if(!Curl_bufq_is_empty(&data->req.sendbuf)) { |
| 306 | result = req_send_buffer_flush(data); |
| 307 | if(result) |
| 308 | return result; |
| 309 | if(!Curl_bufq_is_empty(&data->req.sendbuf)) { |
| 310 | DEBUGF(infof(data, "Curl_req_flush(len=%zu) -> EAGAIN", |
| 311 | Curl_bufq_len(&data->req.sendbuf))); |
| 312 | return CURLE_AGAIN; |
| 313 | } |
| 314 | } |
| 315 | else if(Curl_xfer_needs_flush(data)) { |
| 316 | DEBUGF(infof(data, "Curl_req_flush(), xfer send_pending")); |
| 317 | return Curl_xfer_flush(data); |
| 318 | } |
| 319 | |
| 320 | if(data->req.eos_read && !data->req.eos_sent) { |
| 321 | char tmp = 0; |
| 322 | size_t nwritten; |
| 323 | result = xfer_send(data, &tmp, 0, 0, &nwritten); |
| 324 | if(result) |
| 325 | return result; |
| 326 | DEBUGASSERT(data->req.eos_sent); |
| 327 | } |
| 328 | |
| 329 | if(!data->req.upload_done && data->req.eos_read && data->req.eos_sent) { |
| 330 | DEBUGASSERT(Curl_bufq_is_empty(&data->req.sendbuf)); |
| 331 | if(data->req.shutdown) { |
| 332 | bool done; |
| 333 | result = Curl_xfer_send_shutdown(data, &done); |
| 334 | if(result && data->req.shutdown_err_ignore) { |
| 335 | infof(data, "Shutdown send direction error: %d. Broken server? " |
| 336 | "Proceeding as if everything is ok.", (int)result); |
| 337 | result = CURLE_OK; |
| 338 | done = TRUE; |
| 339 | } |
| 340 | |
| 341 | if(result) |
| 342 | return result; |
| 343 | if(!done) |
| 344 | return CURLE_AGAIN; |
| 345 | } |
| 346 | return req_set_upload_done(data); |
| 347 | } |
| 348 | return CURLE_OK; |
| 349 | } |
Show 23 lines (not cited by the judge)
| 243 | static CURLcode req_send_buffer_flush(struct Curl_easy *data) |
| 244 | { |
| 245 | CURLcode result = CURLE_OK; |
| 246 | const unsigned char *buf; |
| 247 | size_t blen; |
| 248 | |
| 249 | while(Curl_bufq_peek(&data->req.sendbuf, &buf, &blen)) { |
| 250 | size_t nwritten, hds_len = CURLMIN(data->req.sendbuf_hds_len, blen); |
| 251 | result = xfer_send(data, (const char *)buf, blen, hds_len, &nwritten); |
| 252 | if(result) |
| 253 | break; |
| 254 | |
| 255 | Curl_bufq_skip(&data->req.sendbuf, nwritten); |
| 256 | if(hds_len) { |
| 257 | data->req.sendbuf_hds_len -= CURLMIN(hds_len, nwritten); |
| 258 | } |
| 259 | /* leave if we could not send all. Maybe network blocking or |
| 260 | * speed limits on transfer */ |
| 261 | if(nwritten < blen) |
| 262 | break; |
| 263 | } |
| 264 | return result; |
| 265 | } |
Show 116 lines (not cited by the judge)
| 220 | static CURLcode sendrecv_dl(struct Curl_easy *data, |
| 221 | struct SingleRequest *k) |
| 222 | { |
| 223 | struct connectdata *conn = data->conn; |
| 224 | CURLcode result = CURLE_OK; |
| 225 | char *buf, *xfer_buf; |
| 226 | size_t blen, xfer_blen; |
| 227 | int maxloops = 10; |
| 228 | bool is_multiplex = FALSE; |
| 229 | bool rcvd_eagain = FALSE; |
| 230 | bool is_eos = FALSE, rate_limited = FALSE; |
| 231 | |
| 232 | result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen); |
| 233 | if(result) |
| 234 | goto out; |
| 235 | |
| 236 | /* This is where we loop until we have read everything there is to |
| 237 | read or we get a CURLE_AGAIN */ |
| 238 | do { |
| 239 | size_t bytestoread; |
| 240 | |
| 241 | if(!is_multiplex) { |
| 242 | /* Multiplexed connection have inherent handling of EOF and we do not |
| 243 | * have to carefully restrict the amount we try to read. |
| 244 | * Multiplexed changes only in one direction. */ |
| 245 | is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET); |
| 246 | } |
| 247 | |
| 248 | buf = xfer_buf; |
| 249 | bytestoread = xfer_blen; |
| 250 | |
| 251 | if(bytestoread && Curl_rlimit_active(&data->progress.dl.rlimit)) { |
| 252 | curl_off_t dl_avail = Curl_rlimit_avail(&data->progress.dl.rlimit, NULL); |
| 253 | #if 0 |
| 254 | DEBUGF(infof(data, "dl_rlimit, available=%" FMT_OFF_T, dl_avail)); |
| 255 | #endif |
| 256 | /* In case of rate limited downloads: if this loop already got data and |
| 257 | * less than 16k is left in the limit, break out. We want to stutter a |
| 258 | * bit to keep in the limit, but too small receives will cost cpu |
| 259 | * unnecessarily. */ |
| 260 | if(dl_avail <= 0) { |
| 261 | rate_limited = TRUE; |
| 262 | break; |
| 263 | } |
| 264 | if(dl_avail < (curl_off_t)bytestoread) |
| 265 | bytestoread = (size_t)dl_avail; |
| 266 | } |
| 267 | |
| 268 | rcvd_eagain = FALSE; |
| 269 | result = xfer_recv_resp(data, buf, bytestoread, is_multiplex, &blen); |
| 270 | if(result) { |
| 271 | if(result != CURLE_AGAIN) |
| 272 | goto out; /* real error */ |
| 273 | rcvd_eagain = TRUE; |
| 274 | result = CURLE_OK; |
| 275 | if(data->req.download_done && data->req.no_body && |
| 276 | !data->req.resp_trailer) { |
| 277 | DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, " |
| 278 | "not waiting for EOS")); |
| 279 | blen = 0; |
| 280 | /* continue as if we received the EOS */ |
| 281 | } |
| 282 | else |
| 283 | break; /* get out of loop */ |
| 284 | } |
| 285 | |
| 286 | /* We only get a 0-length receive at the end of the response */ |
| 287 | is_eos = (blen == 0); |
| 288 | |
| 289 | if(!blen) { |
| 290 | result = Curl_req_stop_send_recv(data); |
| 291 | if(result) |
| 292 | goto out; |
| 293 | if(k->eos_written) /* already did write this to client, leave */ |
| 294 | break; |
| 295 | } |
| 296 | |
| 297 | result = Curl_xfer_write_resp(data, buf, blen, is_eos); |
| 298 | if(result || data->req.done) |
| 299 | goto out; |
| 300 | |
| 301 | /* if we are done, we stop receiving. On multiplexed connections, |
| 302 | * we should read the EOS. Which may arrive as meta data after |
| 303 | * the bytes. Not taking it in might lead to RST of streams. */ |
| 304 | if((!is_multiplex && data->req.download_done) || is_eos) { |
| 305 | CURL_REQ_CLEAR_RECV(data); |
| 306 | } |
| 307 | /* if we stopped receiving, leave the loop */ |
| 308 | if(!CURL_REQ_WANT_RECV(data)) |
| 309 | break; |
| 310 | |
| 311 | } while(maxloops--); |
| 312 | |
| 313 | if(!is_eos && !rate_limited && CURL_REQ_WANT_RECV(data) && |
| 314 | (!rcvd_eagain || data_pending(data, rcvd_eagain))) { |
| 315 | /* Did not read until EAGAIN/EOS or there is still data pending |
| 316 | * in buffers. Mark as read-again via simulated SELECT results. */ |
| 317 | Curl_multi_mark_dirty(data); |
| 318 | CURL_TRC_M(data, "sendrecv_dl() no EAGAIN/pending data, mark as dirty"); |
| 319 | } |
| 320 | |
| 321 | if(!CURL_REQ_WANT_RECV(data) && CURL_REQ_WANT_SEND(data) && |
| 322 | (conn->bits.close || is_multiplex)) { |
| 323 | /* When we have read the entire thing and the close bit is set, the server |
| 324 | may now close the connection. If there is now any kind of sending going |
| 325 | on from our side, we need to stop that immediately. */ |
| 326 | infof(data, "we are done reading and this is set to close, stop send"); |
| 327 | Curl_req_abort_sending(data); |
| 328 | } |
| 329 | |
| 330 | out: |
| 331 | Curl_multi_xfer_buf_release(data, xfer_buf); |
| 332 | if(result) |
| 333 | DEBUGF(infof(data, "sendrecv_dl() -> %d", (int)result)); |
| 334 | return result; |
| 335 | } |
Show 9 lines (not cited by the judge)
| 340 | static CURLcode sendrecv_ul(struct Curl_easy *data) |
| 341 | { |
| 342 | /* We should not get here when the sending is already done. */ |
| 343 | DEBUGASSERT(!Curl_req_done_sending(data)); |
| 344 | |
| 345 | if(!Curl_req_done_sending(data)) |
| 346 | return Curl_req_send_more(data); |
| 347 | return CURLE_OK; |
| 348 | } |
Show 52 lines (not cited by the judge)
| 1114 | static CURLcode ssl_cf_send(struct Curl_cfilter *cf, |
| 1115 | struct Curl_easy *data, |
| 1116 | const uint8_t *buf, size_t blen, |
| 1117 | bool eos, size_t *pnwritten) |
| 1118 | { |
| 1119 | struct ssl_connect_data *connssl = cf->ctx; |
| 1120 | struct cf_call_data save; |
| 1121 | CURLcode result = CURLE_OK; |
| 1122 | |
| 1123 | (void)eos; |
| 1124 | *pnwritten = 0; |
| 1125 | CF_DATA_SAVE(save, cf, data); |
| 1126 | |
| 1127 | if(connssl->state == ssl_connection_deferred) { |
| 1128 | bool done = FALSE; |
| 1129 | result = ssl_cf_connect_deferred(cf, data, buf, blen, &done); |
| 1130 | if(result) |
| 1131 | goto out; |
| 1132 | else if(!done) { |
| 1133 | result = CURLE_AGAIN; |
| 1134 | goto out; |
| 1135 | } |
| 1136 | DEBUGASSERT(connssl->state == ssl_connection_complete); |
| 1137 | } |
| 1138 | |
| 1139 | if(connssl->earlydata_skip) { |
| 1140 | if(connssl->earlydata_skip >= blen) { |
| 1141 | connssl->earlydata_skip -= blen; |
| 1142 | result = CURLE_OK; |
| 1143 | *pnwritten = blen; |
| 1144 | goto out; |
| 1145 | } |
| 1146 | else { |
| 1147 | *pnwritten = connssl->earlydata_skip; |
| 1148 | buf = buf + connssl->earlydata_skip; |
| 1149 | blen -= connssl->earlydata_skip; |
| 1150 | connssl->earlydata_skip = 0; |
| 1151 | } |
| 1152 | } |
| 1153 | |
| 1154 | /* OpenSSL and maybe other TLS libs do not like 0-length writes. Skip. */ |
| 1155 | if(blen > 0) { |
| 1156 | size_t nwritten; |
| 1157 | result = connssl->ssl_impl->send_plain(cf, data, buf, blen, &nwritten); |
| 1158 | if(!result) |
| 1159 | *pnwritten += nwritten; |
| 1160 | } |
| 1161 | |
| 1162 | out: |
| 1163 | CF_DATA_RESTORE(cf, save); |
| 1164 | return result; |
| 1165 | } |
Show 46 lines (not cited by the judge)
| 656 | static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen, |
| 657 | int frame_age, int frame_flags, |
| 658 | curl_off_t payload_offset, |
| 659 | curl_off_t payload_len, |
| 660 | void *user_data, |
| 661 | size_t *pnwritten) |
| 662 | { |
| 663 | struct ws_cw_dec_ctx *ctx = user_data; |
| 664 | struct Curl_easy *data = ctx->data; |
| 665 | struct websocket *ws = ctx->ws; |
| 666 | bool auto_pong = !data->set.ws_raw_mode && !data->set.ws_no_auto_pong; |
| 667 | curl_off_t remain; |
| 668 | CURLcode result; |
| 669 | |
| 670 | (void)frame_age; |
| 671 | *pnwritten = 0; |
| 672 | remain = ws_payload_remain(payload_len, payload_offset, buflen); |
| 673 | if(remain < 0) { |
| 674 | DEBUGASSERT(0); /* parameter mismatch */ |
| 675 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 676 | } |
| 677 | |
| 678 | if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { |
| 679 | /* auto-respond to PINGs, only works for single-frame payloads atm */ |
| 680 | CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T |
| 681 | "/%" FMT_OFF_T "]", payload_offset, payload_len); |
| 682 | /* send back the exact same content as a PONG */ |
| 683 | result = ws_enc_add_cntrl(data, ws, buf, buflen, CURLWS_PONG); |
| 684 | if(result) |
| 685 | return result; |
| 686 | } |
| 687 | else if(buflen || !remain) { |
| 688 | /* forward the decoded frame to the next client writer. */ |
| 689 | update_meta(ws, frame_age, frame_flags, payload_offset, |
| 690 | payload_len, buflen); |
| 691 | |
| 692 | CURL_TRC_WRITE(data, "[WS] pass %zu decoded bytes", buflen); |
| 693 | result = Curl_cwriter_write(data, ctx->next_writer, |
| 694 | (ctx->cw_type | CLIENTWRITE_0LEN), |
| 695 | (const char *)buf, buflen); |
| 696 | if(result) |
| 697 | return result; |
| 698 | } |
| 699 | *pnwritten = buflen; |
| 700 | return CURLE_OK; |
| 701 | } |
Show 46 lines (not cited by the judge)
| 662 | static CURLcode ws_cw_dec_next(const uint8_t *buf, size_t buflen, |
| 663 | int frame_age, int frame_flags, |
| 664 | curl_off_t payload_offset, |
| 665 | curl_off_t payload_len, |
| 666 | void *user_data, |
| 667 | size_t *pnwritten) |
| 668 | { |
| 669 | struct ws_cw_dec_ctx *ctx = user_data; |
| 670 | struct Curl_easy *data = ctx->data; |
| 671 | struct websocket *ws = ctx->ws; |
| 672 | bool auto_pong = !data->set.ws_no_auto_pong; |
| 673 | curl_off_t remain; |
| 674 | CURLcode result; |
| 675 | |
| 676 | (void)frame_age; |
| 677 | *pnwritten = 0; |
| 678 | remain = ws_payload_remain(payload_len, payload_offset, buflen); |
| 679 | if(remain < 0) { |
| 680 | DEBUGASSERT(0); /* parameter mismatch */ |
| 681 | return CURLE_BAD_FUNCTION_ARGUMENT; |
| 682 | } |
| 683 | |
| 684 | if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { |
| 685 | /* auto-respond to PINGs, only works for single-frame payloads atm */ |
| 686 | CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T |
| 687 | "/%" FMT_OFF_T "]", payload_offset, payload_len); |
| 688 | /* send back the exact same content as a PONG */ |
| 689 | result = ws_enc_add_cntrl(data, ws, buf, buflen, CURLWS_PONG); |
| 690 | if(result) |
| 691 | return result; |
| 692 | } |
| 693 | else if(buflen || !remain) { |
| 694 | /* forward the decoded frame to the next client writer. */ |
| 695 | update_meta(ws, frame_age, frame_flags, payload_offset, |
| 696 | payload_len, buflen); |
| 697 | |
| 698 | CURL_TRC_WRITE(data, "[WS] pass %zu decoded bytes", buflen); |
| 699 | result = Curl_cwriter_write(data, ctx->next_writer, |
| 700 | (ctx->cw_type | CLIENTWRITE_0LEN), |
| 701 | (const char *)buf, buflen); |
| 702 | if(result) |
| 703 | return result; |
| 704 | } |
| 705 | *pnwritten = buflen; |
| 706 | return CURLE_OK; |
| 707 | } |
Show 67 lines (not cited by the judge)
| 703 | static CURLcode ws_cw_write(struct Curl_easy *data, |
| 704 | struct Curl_cwriter *writer, int type, |
| 705 | const char *buf, size_t nbytes) |
| 706 | { |
| 707 | struct ws_cw_ctx *ctx = writer->ctx; |
| 708 | struct websocket *ws; |
| 709 | CURLcode result = CURLE_OK; |
| 710 | |
| 711 | CURL_TRC_WRITE(data, "[WS] write(len=%zu, type=%d)", nbytes, type); |
| 712 | if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) |
| 713 | return Curl_cwriter_write(data, writer->next, type, buf, nbytes); |
| 714 | |
| 715 | ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); |
| 716 | if(!ws) { |
| 717 | failf(data, "[WS] not a websocket transfer"); |
| 718 | return CURLE_FAILED_INIT; |
| 719 | } |
| 720 | |
| 721 | if(nbytes) { |
| 722 | size_t nwritten; |
| 723 | result = Curl_bufq_write(&ctx->buf, (const uint8_t *)buf, |
| 724 | nbytes, &nwritten); |
| 725 | if(result) { |
| 726 | infof(data, "[WS] error adding data to buffer %d", (int)result); |
| 727 | return result; |
| 728 | } |
| 729 | } |
| 730 | |
| 731 | result = Curl_cwriter_flush(data, writer->next); |
| 732 | if(result) |
| 733 | goto out; |
| 734 | |
| 735 | while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) { |
| 736 | struct ws_cw_dec_ctx pass_ctx; |
| 737 | pass_ctx.data = data; |
| 738 | pass_ctx.ws = ws; |
| 739 | pass_ctx.next_writer = writer->next; |
| 740 | pass_ctx.cw_type = type; |
| 741 | result = ws_dec_pass(&ws->dec, data, &ctx->buf, |
| 742 | ws_cw_dec_next, &pass_ctx); |
| 743 | if(result == CURLE_AGAIN) { |
| 744 | /* insufficient amount of data, keep it for later. |
| 745 | * we pretend to have written all since we have a copy */ |
| 746 | result = CURLE_OK; |
| 747 | goto out; |
| 748 | } |
| 749 | else if(result) { |
| 750 | failf(data, "[WS] decode payload error %d", (int)result); |
| 751 | Curl_bufq_reset(&ctx->buf); |
| 752 | goto out; |
| 753 | } |
| 754 | } |
| 755 | |
| 756 | out: |
| 757 | if(!result && !ws->using_reader) { |
| 758 | result = ws_flush(data, ws, Curl_api_is_in_callback(data)); |
| 759 | if(result == CURLE_AGAIN) |
| 760 | result = CURLE_OK; |
| 761 | } |
| 762 | |
| 763 | if(!result && (type & CLIENTWRITE_EOS) && |
| 764 | (!Curl_bufq_is_empty(&ctx->buf) || (ws->dec.state != WS_DEC_INIT))) { |
| 765 | failf(data, "[WS] decode ended with incomplete frame"); |
| 766 | result = CURLE_RECV_ERROR; |
| 767 | } |
| 768 | return result; |
| 769 | } |
Show 67 lines (not cited by the judge)
| 709 | static CURLcode ws_cw_write(struct Curl_easy *data, |
| 710 | struct Curl_cwriter *writer, int type, |
| 711 | const char *buf, size_t nbytes) |
| 712 | { |
| 713 | struct ws_cw_ctx *ctx = writer->ctx; |
| 714 | struct websocket *ws; |
| 715 | CURLcode result = CURLE_OK; |
| 716 | |
| 717 | CURL_TRC_WRITE(data, "[WS] write(len=%zu, type=%d)", nbytes, type); |
| 718 | if(!(type & CLIENTWRITE_BODY) || data->set.ws_raw_mode) |
| 719 | return Curl_cwriter_write(data, writer->next, type, buf, nbytes); |
| 720 | |
| 721 | ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN); |
| 722 | if(!ws) { |
| 723 | failf(data, "[WS] not a websocket transfer"); |
| 724 | return CURLE_FAILED_INIT; |
| 725 | } |
| 726 | |
| 727 | if(nbytes) { |
| 728 | size_t nwritten; |
| 729 | result = Curl_bufq_write(&ctx->buf, (const uint8_t *)buf, |
| 730 | nbytes, &nwritten); |
| 731 | if(result) { |
| 732 | infof(data, "[WS] error adding data to buffer %d", (int)result); |
| 733 | return result; |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | result = Curl_cwriter_flush(data, writer->next); |
| 738 | if(result) |
| 739 | goto out; |
| 740 | |
| 741 | while(!Curl_bufq_is_empty(&ctx->buf) && !Curl_cwriter_is_paused(data)) { |
| 742 | struct ws_cw_dec_ctx pass_ctx; |
| 743 | pass_ctx.data = data; |
| 744 | pass_ctx.ws = ws; |
| 745 | pass_ctx.next_writer = writer->next; |
| 746 | pass_ctx.cw_type = type; |
| 747 | result = ws_dec_pass(&ws->dec, data, &ctx->buf, |
| 748 | ws_cw_dec_next, &pass_ctx); |
| 749 | if(result == CURLE_AGAIN) { |
| 750 | /* insufficient amount of data, keep it for later. |
| 751 | * we pretend to have written all since we have a copy */ |
| 752 | result = CURLE_OK; |
| 753 | goto out; |
| 754 | } |
| 755 | else if(result) { |
| 756 | failf(data, "[WS] decode payload error %d", (int)result); |
| 757 | Curl_bufq_reset(&ctx->buf); |
| 758 | goto out; |
| 759 | } |
| 760 | } |
| 761 | |
| 762 | if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) { |
| 763 | failf(data, "[WS] decode ending with %zu frame bytes remaining", |
| 764 | Curl_bufq_len(&ctx->buf)); |
| 765 | result = CURLE_RECV_ERROR; |
| 766 | } |
| 767 | |
| 768 | out: |
| 769 | if(!result) { |
| 770 | result = ws_flush(data, ws, Curl_api_is_in_callback(data)); |
| 771 | if(result == CURLE_AGAIN) |
| 772 | result = CURLE_OK; |
| 773 | } |
| 774 | return result; |
| 775 | } |
Show 55 lines (not cited by the judge)
| 519 | static CURLcode ws_dec_pass(struct ws_decoder *dec, |
| 520 | struct Curl_easy *data, |
| 521 | struct bufq *inraw, |
| 522 | ws_write_payload *write_cb, |
| 523 | void *write_ctx) |
| 524 | { |
| 525 | CURLcode result; |
| 526 | |
| 527 | if(Curl_bufq_is_empty(inraw)) |
| 528 | return CURLE_AGAIN; |
| 529 | |
| 530 | switch(dec->state) { |
| 531 | case WS_DEC_INIT: |
| 532 | ws_dec_next_frame(dec); |
| 533 | dec->state = WS_DEC_HEAD; |
| 534 | FALLTHROUGH(); |
| 535 | case WS_DEC_HEAD: |
| 536 | result = ws_dec_read_head(dec, data, inraw); |
| 537 | if(result) { |
| 538 | if(result != CURLE_AGAIN) { |
| 539 | failf(data, "[WS] decode frame error %d", (int)result); |
| 540 | break; /* real error */ |
| 541 | } |
| 542 | /* incomplete ws frame head */ |
| 543 | DEBUGASSERT(Curl_bufq_is_empty(inraw)); |
| 544 | break; |
| 545 | } |
| 546 | /* head parsing done */ |
| 547 | dec->state = WS_DEC_PAYLOAD; |
| 548 | if(dec->payload_len == 0) { |
| 549 | size_t nwritten; |
| 550 | const uint8_t tmp = '\0'; |
| 551 | /* special case of a 0 length frame, need to write once */ |
| 552 | result = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags, |
| 553 | 0, 0, write_ctx, &nwritten); |
| 554 | if(result) |
| 555 | return result; |
| 556 | dec->state = WS_DEC_INIT; |
| 557 | break; |
| 558 | } |
| 559 | FALLTHROUGH(); |
| 560 | case WS_DEC_PAYLOAD: |
| 561 | result = ws_dec_pass_payload(dec, data, inraw, write_cb, write_ctx); |
| 562 | ws_dec_info(dec, data, "passing"); |
| 563 | if(result) |
| 564 | return result; |
| 565 | /* payload parsing done */ |
| 566 | dec->state = WS_DEC_INIT; |
| 567 | break; |
| 568 | default: |
| 569 | /* we covered all enums above, but some code analyzers are wimps */ |
| 570 | result = CURLE_FAILED_INIT; |
| 571 | } |
| 572 | return result; |
| 573 | } |
Show 16 lines (not cited by the judge)
| 984 | static CURLcode ws_enc_write_head(struct Curl_easy *data, |
| 985 | struct websocket *ws, |
| 986 | struct ws_encoder *enc, |
| 987 | unsigned int flags, |
| 988 | curl_off_t payload_len, |
| 989 | struct bufq *out) |
| 990 | { |
| 991 | /* starting a new frame, we want a clean sendbuf. |
| 992 | * Any pending control frame we can add now as part of the flush. */ |
| 993 | if(ws->pending.type) { |
| 994 | CURLcode result = ws_enc_add_pending(data, ws); |
| 995 | if(result) |
| 996 | return result; |
| 997 | } |
| 998 | return ws_enc_add_frame(data, enc, flags, payload_len, out); |
| 999 | } |
Show 34 lines (not cited by the judge)
| 1001 | static CURLcode ws_enc_write_payload(struct ws_encoder *enc, |
| 1002 | struct Curl_easy *data, |
| 1003 | const uint8_t *buf, size_t buflen, |
| 1004 | struct bufq *out, size_t *pnwritten) |
| 1005 | { |
| 1006 | CURLcode result; |
| 1007 | size_t i, len, n, remain; |
| 1008 | |
| 1009 | *pnwritten = 0; |
| 1010 | if(Curl_bufq_is_full(out)) |
| 1011 | return CURLE_AGAIN; |
| 1012 | |
| 1013 | /* not the most performant way to do this */ |
| 1014 | len = buflen; |
| 1015 | remain = curlx_sotouz_range(enc->payload_remain, 0, SIZE_MAX); |
| 1016 | if(remain < len) |
| 1017 | len = remain; |
| 1018 | |
| 1019 | for(i = 0; i < len; ++i) { |
| 1020 | uint8_t c = buf[i] ^ enc->mask[enc->xori]; |
| 1021 | result = Curl_bufq_write(out, &c, 1, &n); |
| 1022 | if(result) { |
| 1023 | if((result != CURLE_AGAIN) || !i) |
| 1024 | return result; |
| 1025 | break; |
| 1026 | } |
| 1027 | enc->xori++; |
| 1028 | enc->xori &= 3; |
| 1029 | } |
| 1030 | *pnwritten = i; |
| 1031 | enc->payload_remain -= (curl_off_t)i; |
| 1032 | ws_enc_info(enc, data, "buffered"); |
| 1033 | return CURLE_OK; |
| 1034 | } |
Show 56 lines (not cited by the judge)
| 186 | static CURLcode xfer_send(struct Curl_easy *data, |
| 187 | const char *buf, size_t blen, |
| 188 | size_t hds_len, size_t *pnwritten) |
| 189 | { |
| 190 | CURLcode result = CURLE_OK; |
| 191 | bool eos = FALSE; |
| 192 | |
| 193 | *pnwritten = 0; |
| 194 | DEBUGASSERT(hds_len <= blen); |
| 195 | #ifdef DEBUGBUILD |
| 196 | { |
| 197 | /* Allow debug builds to override this logic to force short initial |
| 198 | sends */ |
| 199 | size_t body_len = blen - hds_len; |
| 200 | if(body_len) { |
| 201 | const char *p = getenv("CURL_SMALLREQSEND"); |
| 202 | if(p) { |
| 203 | curl_off_t body_small; |
| 204 | if(!curlx_str_number(&p, &body_small, body_len)) |
| 205 | blen = hds_len + (size_t)body_small; |
| 206 | } |
| 207 | } |
| 208 | } |
| 209 | #endif |
| 210 | /* Make sure this does not send more body bytes than what the max send |
| 211 | speed says. The headers do not count to the max speed. */ |
| 212 | if(data->set.max_send_speed) { |
| 213 | size_t body_bytes = blen - hds_len; |
| 214 | if((curl_off_t)body_bytes > data->set.max_send_speed) |
| 215 | blen = hds_len + (size_t)data->set.max_send_speed; |
| 216 | } |
| 217 | |
| 218 | if(data->req.eos_read && |
| 219 | (Curl_bufq_is_empty(&data->req.sendbuf) || |
| 220 | Curl_bufq_len(&data->req.sendbuf) == blen)) { |
| 221 | DEBUGF(infof(data, "sending last upload chunk of %zu bytes", blen)); |
| 222 | eos = TRUE; |
| 223 | } |
| 224 | result = Curl_xfer_send(data, buf, blen, eos, pnwritten); |
| 225 | if(!result) { |
| 226 | if(eos && (blen == *pnwritten)) |
| 227 | data->req.eos_sent = TRUE; |
| 228 | if(*pnwritten) { |
| 229 | if(hds_len) |
| 230 | Curl_debug(data, CURLINFO_HEADER_OUT, buf, |
| 231 | CURLMIN(hds_len, *pnwritten)); |
| 232 | if(*pnwritten > hds_len) { |
| 233 | size_t body_len = *pnwritten - hds_len; |
| 234 | Curl_debug(data, CURLINFO_DATA_OUT, buf + hds_len, body_len); |
| 235 | data->req.writebytecount += body_len; |
| 236 | Curl_pgrs_upload_inc(data, body_len); |
| 237 | } |
| 238 | } |
| 239 | } |
| 240 | return result; |
| 241 | } |