mirror of
https://github.com/curl/curl.git
synced 2026-07-09 03:47:21 +03:00
websockets: buffer ugprade data at connection level
When the HTTP Upgrade to websockets already carries ws frame data, buffer that data at connection level and not in the ws decoder. Adding new cfilter `cf_recvbuf` to buffer a fixed amont of data to be received later. When the data is received, the filter passes further recv call through to its subfilter. Fixes #22107 Reported-by: sideshowbarker on github Closes #22111
This commit is contained in:
parent
810d9535e1
commit
8ed285f06d
7 changed files with 237 additions and 8 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
157
lib/cf-recvbuf.c
Normal file
157
lib/cf-recvbuf.c
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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 "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 */
|
||||
40
lib/cf-recvbuf.h
Normal file
40
lib/cf-recvbuf.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#ifndef HEADER_CURL_CF_RECVBUF_H
|
||||
#define HEADER_CURL_CF_RECVBUF_H
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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 "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 */
|
||||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
19
lib/ws.c
19
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 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue