diff --git a/lib/Makefile.inc b/lib/Makefile.inc index 061a317b8d..266ba52af3 100644 --- a/lib/Makefile.inc +++ b/lib/Makefile.inc @@ -165,6 +165,7 @@ LIB_CFILES = \ cf-haproxy.c \ cf-https-connect.c \ cf-ip-happy.c \ + cf-recvbuf.c \ cf-setup.c \ cf-socket.c \ cfilters.c \ @@ -298,6 +299,7 @@ LIB_HFILES = \ cf-haproxy.h \ cf-https-connect.h \ cf-ip-happy.h \ + cf-recvbuf.h \ cf-setup.h \ cf-socket.h \ cfilters.h \ diff --git a/lib/cf-recvbuf.c b/lib/cf-recvbuf.c new file mode 100644 index 0000000000..8ccc148bd3 --- /dev/null +++ b/lib/cf-recvbuf.c @@ -0,0 +1,157 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "curl_setup.h" + +#ifndef CURL_DISABLE_WEBSOCKETS +/* only used for this protocol, so far */ + +#include "urldata.h" +#include "bufq.h" +#include "cfilters.h" +#include "cf-recvbuf.h" +#include "curl_trc.h" + +#define CURL_CF_RECVBUF_CHUNK (16 * 1024) + +struct cf_recvbuf_ctx { + struct bufq recvbuf; +}; + +static void cf_recvbuf_destroy(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + struct cf_recvbuf_ctx *ctx = cf->ctx; + (void)data; + if(ctx) { + Curl_bufq_free(&ctx->recvbuf); + curlx_free(ctx); + } +} + +static CURLcode cf_recvbuf_recv(struct Curl_cfilter *cf, + struct Curl_easy *data, + char *buf, size_t len, + size_t *pnread) +{ + struct cf_recvbuf_ctx *ctx = cf->ctx; + + if(!Curl_bufq_is_empty(&ctx->recvbuf)) { + return Curl_bufq_cread(&ctx->recvbuf, buf, len, pnread); + } + + if(cf->next) + return cf->next->cft->do_recv(cf->next, data, buf, len, pnread); + *pnread = 0; + return CURLE_RECV_ERROR; +} + +static bool cf_recvbuf_data_pending(struct Curl_cfilter *cf, + const struct Curl_easy *data) +{ + struct cf_recvbuf_ctx *ctx = cf->ctx; + + if(!Curl_bufq_is_empty(&ctx->recvbuf)) + return TRUE; + + return cf->next ? + cf->next->cft->has_data_pending(cf->next, data) : FALSE; +} + +struct Curl_cftype Curl_cft_recvbuf = { + "RECVBUF", + 0, + CURL_LOG_LVL_NONE, + cf_recvbuf_destroy, + Curl_cf_def_connect, + Curl_cf_def_shutdown, + Curl_cf_def_adjust_pollset, + cf_recvbuf_data_pending, + Curl_cf_def_send, + cf_recvbuf_recv, + Curl_cf_def_cntrl, + Curl_cf_def_conn_is_alive, + Curl_cf_def_conn_keep_alive, + Curl_cf_def_query, +}; + +static CURLcode cf_recvbuf_create(struct Curl_cfilter **pcf, + struct Curl_easy *data, + const uint8_t *buf, size_t blen) +{ + struct Curl_cfilter *cf = NULL; + struct cf_recvbuf_ctx *ctx; + CURLcode result = CURLE_OK; + size_t nwritten = 0; + + (void)data; + ctx = curlx_calloc(1, sizeof(*ctx)); + if(!ctx) { + result = CURLE_OUT_OF_MEMORY; + goto out; + } + Curl_bufq_init2(&ctx->recvbuf, CURL_CF_RECVBUF_CHUNK, + (blen / CURL_CF_RECVBUF_CHUNK) + 1, + (BUFQ_OPT_SOFT_LIMIT | BUFQ_OPT_NO_SPARES)); + result = Curl_bufq_write(&ctx->recvbuf, buf, blen, &nwritten); + if(result) + goto out; + if(nwritten != blen) { + result = CURLE_FAILED_INIT; + goto out; + } + + result = Curl_cf_create(&cf, &Curl_cft_recvbuf, ctx); + if(result) + goto out; + ctx = NULL; + +out: + *pcf = result ? NULL : cf; + if(ctx) { + Curl_bufq_free(&ctx->recvbuf); + curlx_free(ctx); + } + return result; +} + +CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const uint8_t *buf, size_t blen) +{ + struct Curl_cfilter *cf; + CURLcode result = CURLE_OK; + + DEBUGASSERT(data); + result = cf_recvbuf_create(&cf, data, buf, blen); + if(result) + goto out; + + cf->connected = Curl_conn_is_connected(conn, sockindex); + Curl_conn_cf_add(data, conn, sockindex, cf); +out: + return result; +} + +#endif /* !CURL_DISABLE_WEBSOCKETS */ diff --git a/lib/cf-recvbuf.h b/lib/cf-recvbuf.h new file mode 100644 index 0000000000..1b08fcc3be --- /dev/null +++ b/lib/cf-recvbuf.h @@ -0,0 +1,40 @@ +#ifndef HEADER_CURL_CF_RECVBUF_H +#define HEADER_CURL_CF_RECVBUF_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , 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 "curl_setup.h" + +#ifndef CURL_DISABLE_WEBSOCKETS +/* only used for this protocol, so far */ + +CURLcode Curl_cf_recvbuf_add(struct Curl_easy *data, + struct connectdata *conn, + int sockindex, + const uint8_t *buf, size_t blen); + +extern struct Curl_cftype Curl_cft_recvbuf; + +#endif /* !CURL_DISABLE_WEBSOCKETS */ + +#endif /* HEADER_CURL_CF_RECVBUF_H */ diff --git a/lib/cfilters.c b/lib/cfilters.c index b6278eff12..fb1914d133 100644 --- a/lib/cfilters.c +++ b/lib/cfilters.c @@ -33,6 +33,27 @@ #include "select.h" #include "curlx/strparse.h" +CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + CURLcode result; + + if(cf->connected) { + *done = TRUE; + return CURLE_OK; + } + + if(cf->next) { + result = cf->next->cft->do_connect(cf->next, data, done); + if(result || !*done) + return result; + } + + cf->connected = TRUE; + *done = TRUE; + return CURLE_OK; +} + CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done) { diff --git a/lib/cfilters.h b/lib/cfilters.h index a9e00d4899..b1aad029e0 100644 --- a/lib/cfilters.h +++ b/lib/cfilters.h @@ -265,6 +265,8 @@ CURLcode Curl_cf_def_query(struct Curl_cfilter *cf, int query, int *pres1, void *pres2); CURLcode Curl_cf_def_shutdown(struct Curl_cfilter *cf, struct Curl_easy *data, bool *done); +CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done); /** * Create a new filter instance, unattached to the filter chain. diff --git a/lib/curl_trc.c b/lib/curl_trc.c index 127bc8c5cc..f8287e420f 100644 --- a/lib/curl_trc.c +++ b/lib/curl_trc.c @@ -29,6 +29,7 @@ #include "multiif.h" #include "cf-dns.h" +#include "cf-recvbuf.h" #include "cf-socket.h" #include "cf-setup.h" #include "http2.h" @@ -563,6 +564,9 @@ static struct trc_cft_def trc_cfts[] = { { &Curl_cft_unix, TRC_CT_NETWORK }, { &Curl_cft_tcp_accept, TRC_CT_NETWORK }, { &Curl_cft_ip_happy, TRC_CT_NETWORK }, +#ifndef CURL_DISABLE_WEBSOCKETS + { &Curl_cft_recvbuf, TRC_CT_PROTOCOL }, +#endif { &Curl_cft_setup, TRC_CT_PROTOCOL }, #if !defined(CURL_DISABLE_HTTP) && defined(USE_NGHTTP2) { &Curl_cft_nghttp2, TRC_CT_PROTOCOL }, diff --git a/lib/ws.c b/lib/ws.c index 3f3a07466d..9820c3e4bd 100644 --- a/lib/ws.c +++ b/lib/ws.c @@ -32,6 +32,7 @@ #include "curlx/dynbuf.h" #include "rand.h" #include "curlx/base64.h" +#include "cf-recvbuf.h" #include "connect.h" #include "sendf.h" #include "curl_trc.h" @@ -1392,15 +1393,17 @@ CURLcode Curl_ws_accept(struct Curl_easy *data, k->header = FALSE; /* we will not get more response headers */ if(data->set.connect_only) { - size_t nwritten; /* In CONNECT_ONLY setup, the payloads from `mem` need to be received - * when using `curl_ws_recv` later on after this transfer is already - * marked as DONE. */ - result = Curl_bufq_write(&ws->recvbuf, (const uint8_t *)mem, - nread, &nwritten); - if(result) - goto out; - DEBUGASSERT(nread == nwritten); + * when using `curl_ws_recv/curl_easy_recv` later on, after this transfer + * is already marked as DONE. + * Since `curl_easy_recv()` is also supposed to work, we need + * to buffer the data at connection level. See #22107 */ + if(nread) { + result = Curl_cf_recvbuf_add(data, data->conn, FIRSTSOCKET, + (const uint8_t *)mem, nread); + if(result) + goto out; + } CURL_REQ_CLEAR_RECV(data); /* read no more content */ } else { /* !connect_only */