|
@@ -36,7 +36,6 @@
|
|
|
#include "sendf.h"
|
|
|
#include "select.h"
|
|
|
#include "curlx/base64.h"
|
|
|
-#include "strcase.h"
|
|
|
#include "multiif.h"
|
|
|
#include "url.h"
|
|
|
#include "urlapi-int.h"
|
|
@@ -205,6 +204,9 @@ static void cf_h2_ctx_close(struct cf_h2_ctx *ctx)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static CURLcode nw_out_flush(struct Curl_cfilter *cf,
|
|
|
+ struct Curl_easy *data);
|
|
|
+
|
|
|
static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
|
|
|
struct Curl_easy *data);
|
|
|
|
|
@@ -374,27 +376,6 @@ static CURLcode cf_h2_update_local_win(struct Curl_cfilter *cf,
|
|
|
}
|
|
|
#endif /* !NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE */
|
|
|
|
|
|
-/*
|
|
|
- * Mark this transfer to get "drained".
|
|
|
- */
|
|
|
-static void drain_stream(struct Curl_cfilter *cf,
|
|
|
- struct Curl_easy *data,
|
|
|
- struct h2_stream_ctx *stream)
|
|
|
-{
|
|
|
- unsigned char bits;
|
|
|
-
|
|
|
- (void)cf;
|
|
|
- bits = CURL_CSELECT_IN;
|
|
|
- if(!stream->closed &&
|
|
|
- (!stream->body_eos || !Curl_bufq_is_empty(&stream->sendbuf)))
|
|
|
- bits |= CURL_CSELECT_OUT;
|
|
|
- if(stream->closed || (data->state.select_bits != bits)) {
|
|
|
- CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x",
|
|
|
- stream->id, bits);
|
|
|
- data->state.select_bits = bits;
|
|
|
- Curl_expire(data, 0, EXPIRE_RUN_NOW);
|
|
|
- }
|
|
|
-}
|
|
|
|
|
|
static CURLcode http2_data_setup(struct Curl_cfilter *cf,
|
|
|
struct Curl_easy *data,
|
|
@@ -449,8 +430,10 @@ static void http2_data_done(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|
|
flush_egress = TRUE;
|
|
|
}
|
|
|
|
|
|
- if(flush_egress)
|
|
|
- nghttp2_session_send(ctx->h2);
|
|
|
+ if(flush_egress) {
|
|
|
+ (void)nghttp2_session_send(ctx->h2);
|
|
|
+ (void)nw_out_flush(cf, data);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
Curl_uint_hash_remove(&ctx->streams, data->mid);
|
|
@@ -480,33 +463,6 @@ static int h2_client_new(struct Curl_cfilter *cf,
|
|
|
return rc;
|
|
|
}
|
|
|
|
|
|
-static ssize_t nw_in_reader(void *reader_ctx,
|
|
|
- unsigned char *buf, size_t buflen,
|
|
|
- CURLcode *err)
|
|
|
-{
|
|
|
- struct Curl_cfilter *cf = reader_ctx;
|
|
|
- struct Curl_easy *data = CF_DATA_CURRENT(cf);
|
|
|
-
|
|
|
- return Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err);
|
|
|
-}
|
|
|
-
|
|
|
-static ssize_t nw_out_writer(void *writer_ctx,
|
|
|
- const unsigned char *buf, size_t buflen,
|
|
|
- CURLcode *err)
|
|
|
-{
|
|
|
- struct Curl_cfilter *cf = writer_ctx;
|
|
|
- struct Curl_easy *data = CF_DATA_CURRENT(cf);
|
|
|
-
|
|
|
- if(data) {
|
|
|
- ssize_t nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf,
|
|
|
- buflen, FALSE, err);
|
|
|
- if(nwritten > 0)
|
|
|
- CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten);
|
|
|
- return nwritten;
|
|
|
- }
|
|
|
- return 0;
|
|
|
-}
|
|
|
-
|
|
|
static ssize_t send_callback(nghttp2_session *h2,
|
|
|
const uint8_t *mem, size_t length, int flags,
|
|
|
void *userp);
|
|
@@ -727,12 +683,12 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
not in use by any other transfer, there should not be any data here,
|
|
|
only "protocol frames" */
|
|
|
CURLcode result;
|
|
|
- ssize_t nread = -1;
|
|
|
+ size_t nread;
|
|
|
|
|
|
*input_pending = FALSE;
|
|
|
- nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result);
|
|
|
- if(nread != -1) {
|
|
|
- CURL_TRC_CF(data, cf, "%zd bytes stray data read before trying "
|
|
|
+ result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread);
|
|
|
+ if(!result) {
|
|
|
+ CURL_TRC_CF(data, cf, "%zu bytes stray data read before trying "
|
|
|
"h2 connection", nread);
|
|
|
if(h2_process_pending_input(cf, data, &result) < 0)
|
|
|
/* immediate error, considered dead */
|
|
@@ -785,15 +741,16 @@ static CURLcode nw_out_flush(struct Curl_cfilter *cf,
|
|
|
struct Curl_easy *data)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- ssize_t nwritten;
|
|
|
+ size_t nwritten;
|
|
|
CURLcode result;
|
|
|
|
|
|
(void)data;
|
|
|
if(Curl_bufq_is_empty(&ctx->outbufq))
|
|
|
return CURLE_OK;
|
|
|
|
|
|
- nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result);
|
|
|
- if(nwritten < 0) {
|
|
|
+ result = Curl_cf_send_bufq(cf->next, data, &ctx->outbufq, NULL, 0,
|
|
|
+ &nwritten);
|
|
|
+ if(result) {
|
|
|
if(result == CURLE_AGAIN) {
|
|
|
CURL_TRC_CF(data, cf, "flush nw send buffer(%zu) -> EAGAIN",
|
|
|
Curl_bufq_len(&ctx->outbufq));
|
|
@@ -816,7 +773,7 @@ static ssize_t send_callback(nghttp2_session *h2,
|
|
|
struct Curl_cfilter *cf = userp;
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct Curl_easy *data = CF_DATA_CURRENT(cf);
|
|
|
- ssize_t nwritten;
|
|
|
+ size_t nwritten;
|
|
|
CURLcode result = CURLE_OK;
|
|
|
|
|
|
(void)h2;
|
|
@@ -824,11 +781,12 @@ static ssize_t send_callback(nghttp2_session *h2,
|
|
|
DEBUGASSERT(data);
|
|
|
|
|
|
if(!cf->connected)
|
|
|
- nwritten = Curl_bufq_write(&ctx->outbufq, buf, blen, &result);
|
|
|
+ result = Curl_bufq_write(&ctx->outbufq, buf, blen, &nwritten);
|
|
|
else
|
|
|
- nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
|
|
|
- nw_out_writer, cf, &result);
|
|
|
- if(nwritten < 0) {
|
|
|
+ result = Curl_cf_send_bufq(cf->next, data, &ctx->outbufq, buf, blen,
|
|
|
+ &nwritten);
|
|
|
+
|
|
|
+ if(result) {
|
|
|
if(result == CURLE_AGAIN) {
|
|
|
ctx->nw_out_blocked = 1;
|
|
|
return NGHTTP2_ERR_WOULDBLOCK;
|
|
@@ -841,7 +799,8 @@ static ssize_t send_callback(nghttp2_session *h2,
|
|
|
ctx->nw_out_blocked = 1;
|
|
|
return NGHTTP2_ERR_WOULDBLOCK;
|
|
|
}
|
|
|
- return nwritten;
|
|
|
+ return (nwritten > SSIZE_T_MAX) ?
|
|
|
+ NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nwritten;
|
|
|
}
|
|
|
|
|
|
|
|
@@ -1191,7 +1150,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
|
|
if(stream->status_code / 100 != 1) {
|
|
|
stream->resp_hds_complete = TRUE;
|
|
|
}
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
break;
|
|
|
case NGHTTP2_PUSH_PROMISE:
|
|
|
rv = push_promise(cf, data, &frame->push_promise);
|
|
@@ -1214,12 +1173,12 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
|
|
if(frame->rst_stream.error_code) {
|
|
|
stream->reset = TRUE;
|
|
|
}
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
break;
|
|
|
case NGHTTP2_WINDOW_UPDATE:
|
|
|
if(CURL_WANT_SEND(data) && Curl_bufq_is_empty(&stream->sendbuf)) {
|
|
|
/* need more data, force processing of transfer */
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
}
|
|
|
else if(!Curl_bufq_is_empty(&stream->sendbuf)) {
|
|
|
/* resume the potentially suspended stream */
|
|
@@ -1245,7 +1204,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf,
|
|
|
stream->id, NGHTTP2_STREAM_CLOSED);
|
|
|
stream->closed = TRUE;
|
|
|
}
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
}
|
|
|
return CURLE_OK;
|
|
|
}
|
|
@@ -1394,11 +1353,8 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
* window and *assume* that we treat this like a WINDOW_UPDATE. Some
|
|
|
* servers send an explicit WINDOW_UPDATE, but not all seem to do that.
|
|
|
* To be safe, we UNHOLD a stream in order not to stall. */
|
|
|
- if(CURL_WANT_SEND(data)) {
|
|
|
- struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
- if(stream)
|
|
|
- drain_stream(cf, data, stream);
|
|
|
- }
|
|
|
+ if(CURL_WANT_SEND(data))
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
}
|
|
|
break;
|
|
|
}
|
|
@@ -1543,7 +1499,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
|
|
|
stream_id, nghttp2_http2_strerror(error_code), error_code);
|
|
|
else
|
|
|
CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id);
|
|
|
- drain_stream(cf, data_s, stream);
|
|
|
+ Curl_multi_mark_dirty(data_s);
|
|
|
|
|
|
/* remove `data_s` from the nghttp2 stream */
|
|
|
rv = nghttp2_session_set_stream_user_data(session, stream_id, 0);
|
|
@@ -1641,9 +1597,9 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
if(!check)
|
|
|
/* no memory */
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
- if(!strcasecompare(check, (const char *)value) &&
|
|
|
+ if(!curl_strequal(check, (const char *)value) &&
|
|
|
((cf->conn->remote_port != cf->conn->given->defport) ||
|
|
|
- !strcasecompare(cf->conn->host.name, (const char *)value))) {
|
|
|
+ !curl_strequal(cf->conn->host.name, (const char *)value))) {
|
|
|
/* This is push is not for the same authority that was asked for in
|
|
|
* the URL. RFC 7540 section 8.2 says: "A client MUST treat a
|
|
|
* PUSH_PROMISE for which the server is not authoritative as a stream
|
|
@@ -1737,7 +1693,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
}
|
|
|
/* if we receive data for another handle, wake that up */
|
|
|
if(CF_DATA_CURRENT(cf) != data_s)
|
|
|
- Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
|
|
|
+ Curl_multi_mark_dirty(data_s);
|
|
|
|
|
|
CURL_TRC_CF(data_s, cf, "[%d] status: HTTP/2 %03d",
|
|
|
stream->id, stream->status_code);
|
|
@@ -1764,7 +1720,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
|
|
|
}
|
|
|
/* if we receive data for another handle, wake that up */
|
|
|
if(CF_DATA_CURRENT(cf) != data_s)
|
|
|
- Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
|
|
|
+ Curl_multi_mark_dirty(data_s);
|
|
|
|
|
|
CURL_TRC_CF(data_s, cf, "[%d] header: %.*s: %.*s",
|
|
|
stream->id, (int)namelen, name, (int)valuelen, value);
|
|
@@ -1785,6 +1741,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session,
|
|
|
struct h2_stream_ctx *stream = NULL;
|
|
|
CURLcode result;
|
|
|
ssize_t nread;
|
|
|
+ size_t n;
|
|
|
(void)source;
|
|
|
|
|
|
(void)cf;
|
|
@@ -1803,12 +1760,14 @@ static ssize_t req_body_read_callback(nghttp2_session *session,
|
|
|
if(!stream)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
|
|
|
- nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result);
|
|
|
- if(nread < 0) {
|
|
|
+ result = Curl_bufq_read(&stream->sendbuf, buf, length, &n);
|
|
|
+ if(result) {
|
|
|
if(result != CURLE_AGAIN)
|
|
|
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
|
|
nread = 0;
|
|
|
}
|
|
|
+ else
|
|
|
+ nread = (ssize_t)n;
|
|
|
|
|
|
CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) eos=%d -> %zd, %d",
|
|
|
stream_id, length, stream->body_eos, nread, result);
|
|
@@ -1874,20 +1833,20 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
-static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
|
|
|
- struct Curl_easy *data,
|
|
|
- struct h2_stream_ctx *stream,
|
|
|
- CURLcode *err)
|
|
|
+static CURLcode http2_handle_stream_close(struct Curl_cfilter *cf,
|
|
|
+ struct Curl_easy *data,
|
|
|
+ struct h2_stream_ctx *stream,
|
|
|
+ size_t *pnlen)
|
|
|
{
|
|
|
- ssize_t rv = 0;
|
|
|
+ CURLcode result;
|
|
|
|
|
|
+ *pnlen = 0;
|
|
|
if(stream->error == NGHTTP2_REFUSED_STREAM) {
|
|
|
CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
|
|
|
"connection", stream->id);
|
|
|
connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */
|
|
|
data->state.refused_stream = TRUE;
|
|
|
- *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
|
|
|
- return -1;
|
|
|
+ return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
|
|
|
}
|
|
|
else if(stream->error != NGHTTP2_NO_ERROR) {
|
|
|
if(stream->resp_hds_complete && data->req.no_body) {
|
|
@@ -1896,27 +1855,23 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
|
|
|
stream->id, nghttp2_http2_strerror(stream->error),
|
|
|
stream->error);
|
|
|
stream->close_handled = TRUE;
|
|
|
- *err = CURLE_OK;
|
|
|
- goto out;
|
|
|
+ return CURLE_OK;
|
|
|
}
|
|
|
failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
|
|
|
stream->id, nghttp2_http2_strerror(stream->error),
|
|
|
stream->error);
|
|
|
- *err = CURLE_HTTP2_STREAM;
|
|
|
- return -1;
|
|
|
+ return CURLE_HTTP2_STREAM;
|
|
|
}
|
|
|
else if(stream->reset) {
|
|
|
failf(data, "HTTP/2 stream %u was reset", stream->id);
|
|
|
- *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2;
|
|
|
- return -1;
|
|
|
+ return data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2;
|
|
|
}
|
|
|
|
|
|
if(!stream->bodystarted) {
|
|
|
failf(data, "HTTP/2 stream %u was closed cleanly, but before getting "
|
|
|
" all response header fields, treated as error",
|
|
|
stream->id);
|
|
|
- *err = CURLE_HTTP2_STREAM;
|
|
|
- return -1;
|
|
|
+ return CURLE_HTTP2_STREAM;
|
|
|
}
|
|
|
|
|
|
if(Curl_dynhds_count(&stream->resp_trailers)) {
|
|
@@ -1924,37 +1879,36 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
|
|
|
struct dynbuf dbuf;
|
|
|
size_t i;
|
|
|
|
|
|
- *err = CURLE_OK;
|
|
|
+ result = CURLE_OK;
|
|
|
curlx_dyn_init(&dbuf, DYN_TRAILERS);
|
|
|
for(i = 0; i < Curl_dynhds_count(&stream->resp_trailers); ++i) {
|
|
|
e = Curl_dynhds_getn(&stream->resp_trailers, i);
|
|
|
if(!e)
|
|
|
break;
|
|
|
curlx_dyn_reset(&dbuf);
|
|
|
- *err = curlx_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a",
|
|
|
+ result = curlx_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a",
|
|
|
(int)e->namelen, e->name,
|
|
|
(int)e->valuelen, e->value);
|
|
|
- if(*err)
|
|
|
+ if(result)
|
|
|
break;
|
|
|
Curl_debug(data, CURLINFO_HEADER_IN, curlx_dyn_ptr(&dbuf),
|
|
|
curlx_dyn_len(&dbuf));
|
|
|
- *err = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER,
|
|
|
- curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf));
|
|
|
- if(*err)
|
|
|
+ result = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER,
|
|
|
+ curlx_dyn_ptr(&dbuf), curlx_dyn_len(&dbuf));
|
|
|
+ if(result)
|
|
|
break;
|
|
|
}
|
|
|
curlx_dyn_free(&dbuf);
|
|
|
- if(*err)
|
|
|
+ if(result)
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
stream->close_handled = TRUE;
|
|
|
- *err = CURLE_OK;
|
|
|
- rv = 0;
|
|
|
+ result = CURLE_OK;
|
|
|
|
|
|
out:
|
|
|
- CURL_TRC_CF(data, cf, "handle_stream_close -> %zd, %d", rv, *err);
|
|
|
- return rv;
|
|
|
+ CURL_TRC_CF(data, cf, "handle_stream_close -> %d, %zu", result, *pnlen);
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
static int sweight_wanted(const struct Curl_easy *data)
|
|
@@ -1997,7 +1951,7 @@ static void h2_pri_spec(struct cf_h2_ctx *ctx,
|
|
|
* Flush any out data pending in the network buffer.
|
|
|
*/
|
|
|
static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
|
|
|
- struct Curl_easy *data)
|
|
|
+ struct Curl_easy *data)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
@@ -2037,36 +1991,36 @@ out:
|
|
|
return nw_out_flush(cf, data);
|
|
|
}
|
|
|
|
|
|
-static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
- struct h2_stream_ctx *stream,
|
|
|
- char *buf, size_t len, CURLcode *err)
|
|
|
+static CURLcode stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
+ struct h2_stream_ctx *stream,
|
|
|
+ char *buf, size_t len, size_t *pnread)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- ssize_t nread = -1;
|
|
|
+ CURLcode result = CURLE_AGAIN;
|
|
|
|
|
|
(void)buf;
|
|
|
- *err = CURLE_AGAIN;
|
|
|
+ (void)len;
|
|
|
+ *pnread = 0;
|
|
|
+
|
|
|
if(stream->xfer_result) {
|
|
|
CURL_TRC_CF(data, cf, "[%d] xfer write failed", stream->id);
|
|
|
- *err = stream->xfer_result;
|
|
|
- nread = -1;
|
|
|
+ result = stream->xfer_result;
|
|
|
}
|
|
|
else if(stream->closed) {
|
|
|
CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id);
|
|
|
- nread = http2_handle_stream_close(cf, data, stream, err);
|
|
|
+ result = http2_handle_stream_close(cf, data, stream, pnread);
|
|
|
}
|
|
|
else if(stream->reset ||
|
|
|
(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
|
|
|
(ctx->rcvd_goaway && ctx->remote_max_sid < stream->id)) {
|
|
|
CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id);
|
|
|
- *err = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2;
|
|
|
- nread = -1;
|
|
|
+ result = data->req.bytecount ? CURLE_PARTIAL_FILE : CURLE_HTTP2;
|
|
|
}
|
|
|
|
|
|
- if(nread < 0 && *err != CURLE_AGAIN)
|
|
|
- CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d",
|
|
|
- stream->id, len, nread, *err);
|
|
|
- return nread;
|
|
|
+ if(result && (result != CURLE_AGAIN))
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %d, %zu",
|
|
|
+ stream->id, len, result, *pnread);
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
|
@@ -2076,7 +2030,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream;
|
|
|
CURLcode result = CURLE_OK;
|
|
|
- ssize_t nread;
|
|
|
+ size_t nread;
|
|
|
|
|
|
if(should_close_session(ctx)) {
|
|
|
CURL_TRC_CF(data, cf, "progress ingress, session is closed");
|
|
@@ -2102,12 +2056,12 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
|
|
* this may leave data in underlying buffers that will not
|
|
|
* be consumed. */
|
|
|
if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data))
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
- nread = Curl_bufq_sipn(&ctx->inbufq, 0, nw_in_reader, cf, &result);
|
|
|
- if(nread < 0) {
|
|
|
+ result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread);
|
|
|
+ if(result) {
|
|
|
if(result != CURLE_AGAIN) {
|
|
|
failf(data, "Failed receiving HTTP2 data: %d(%s)", result,
|
|
|
curl_easy_strerror(result));
|
|
@@ -2121,9 +2075,8 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
|
|
break;
|
|
|
}
|
|
|
else {
|
|
|
- CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", nread);
|
|
|
- data_max_bytes = (data_max_bytes > (size_t)nread) ?
|
|
|
- (data_max_bytes - (size_t)nread) : 0;
|
|
|
+ CURL_TRC_CF(data, cf, "[0] ingress: read %zu bytes", nread);
|
|
|
+ data_max_bytes = (data_max_bytes > nread) ? (data_max_bytes - nread) : 0;
|
|
|
}
|
|
|
|
|
|
if(h2_process_pending_input(cf, data, &result))
|
|
@@ -2140,15 +2093,15 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
|
|
|
return CURLE_OK;
|
|
|
}
|
|
|
|
|
|
-static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
- char *buf, size_t len, CURLcode *err)
|
|
|
+static CURLcode cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
+ char *buf, size_t len, size_t *pnread)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
- ssize_t nread = -1;
|
|
|
- CURLcode result;
|
|
|
+ CURLcode result, r2;
|
|
|
struct cf_call_data save;
|
|
|
|
|
|
+ *pnread = 0;
|
|
|
if(!stream) {
|
|
|
/* Abnormal call sequence: either this transfer has never opened a stream
|
|
|
* (unlikely) or the transfer has been done, cleaned up its resources, but
|
|
@@ -2156,51 +2109,49 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
* is for such a case. */
|
|
|
failf(data, "http/2 recv on a transfer never opened "
|
|
|
"or already cleared, mid=%u", data->mid);
|
|
|
- *err = CURLE_HTTP2;
|
|
|
- return -1;
|
|
|
+ return CURLE_HTTP2;
|
|
|
}
|
|
|
|
|
|
CF_DATA_SAVE(save, cf, data);
|
|
|
|
|
|
- nread = stream_recv(cf, data, stream, buf, len, err);
|
|
|
- if(nread < 0 && *err != CURLE_AGAIN)
|
|
|
+ result = stream_recv(cf, data, stream, buf, len, pnread);
|
|
|
+ if(result && (result != CURLE_AGAIN))
|
|
|
goto out;
|
|
|
|
|
|
- if(nread < 0) {
|
|
|
- *err = h2_progress_ingress(cf, data, len);
|
|
|
- if(*err)
|
|
|
+ if(result) {
|
|
|
+ result = h2_progress_ingress(cf, data, len);
|
|
|
+ if(result)
|
|
|
goto out;
|
|
|
|
|
|
- nread = stream_recv(cf, data, stream, buf, len, err);
|
|
|
+ result = stream_recv(cf, data, stream, buf, len, pnread);
|
|
|
}
|
|
|
|
|
|
- if(nread > 0) {
|
|
|
+ if(*pnread > 0) {
|
|
|
/* Now that we transferred this to the upper layer, we report
|
|
|
* the actual amount of DATA consumed to the H2 session, so
|
|
|
* that it adjusts stream flow control */
|
|
|
- nghttp2_session_consume(ctx->h2, stream->id, (size_t)nread);
|
|
|
+ nghttp2_session_consume(ctx->h2, stream->id, *pnread);
|
|
|
if(stream->closed) {
|
|
|
CURL_TRC_CF(data, cf, "[%d] DRAIN closed stream", stream->id);
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
out:
|
|
|
- result = h2_progress_egress(cf, data);
|
|
|
- if(result == CURLE_AGAIN) {
|
|
|
+ r2 = h2_progress_egress(cf, data);
|
|
|
+ if(r2 == CURLE_AGAIN) {
|
|
|
/* pending data to send, need to be called again. Ideally, we
|
|
|
* monitor the socket for POLLOUT, but when not SENDING
|
|
|
* any more, we force processing of the transfer. */
|
|
|
if(!CURL_WANT_SEND(data))
|
|
|
- drain_stream(cf, data, stream);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
}
|
|
|
- else if(result) {
|
|
|
- *err = result;
|
|
|
- nread = -1;
|
|
|
+ else if(r2) {
|
|
|
+ result = r2;
|
|
|
}
|
|
|
- CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, "
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %d, %zu, "
|
|
|
"window=%d/%d, connection %d/%d",
|
|
|
- stream->id, len, nread, *err,
|
|
|
+ stream->id, len, result, *pnread,
|
|
|
nghttp2_session_get_stream_effective_recv_data_length(
|
|
|
ctx->h2, stream->id),
|
|
|
nghttp2_session_get_stream_effective_local_window_size(
|
|
@@ -2209,7 +2160,7 @@ out:
|
|
|
HTTP2_HUGE_WINDOW_SIZE);
|
|
|
|
|
|
CF_DATA_RESTORE(cf, save);
|
|
|
- return nread;
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
static ssize_t cf_h2_body_send(struct Curl_cfilter *cf,
|
|
@@ -2219,7 +2170,7 @@ static ssize_t cf_h2_body_send(struct Curl_cfilter *cf,
|
|
|
CURLcode *err)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- ssize_t nwritten;
|
|
|
+ size_t nwritten;
|
|
|
|
|
|
if(stream->closed) {
|
|
|
if(stream->resp_hds_complete) {
|
|
@@ -2241,11 +2192,11 @@ static ssize_t cf_h2_body_send(struct Curl_cfilter *cf,
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
- nwritten = Curl_bufq_write(&stream->sendbuf, buf, blen, err);
|
|
|
- if(nwritten < 0)
|
|
|
+ *err = Curl_bufq_write(&stream->sendbuf, buf, blen, &nwritten);
|
|
|
+ if(*err)
|
|
|
return -1;
|
|
|
|
|
|
- if(eos && (blen == (size_t)nwritten))
|
|
|
+ if(eos && (blen == nwritten))
|
|
|
stream->body_eos = TRUE;
|
|
|
|
|
|
if(eos || !Curl_bufq_is_empty(&stream->sendbuf)) {
|
|
@@ -2256,13 +2207,13 @@ static ssize_t cf_h2_body_send(struct Curl_cfilter *cf,
|
|
|
return -1;
|
|
|
}
|
|
|
}
|
|
|
- return nwritten;
|
|
|
+ return (ssize_t)nwritten;
|
|
|
}
|
|
|
|
|
|
-static ssize_t h2_submit(struct h2_stream_ctx **pstream,
|
|
|
- struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
- const void *buf, size_t len,
|
|
|
- bool eos, CURLcode *err)
|
|
|
+static CURLcode h2_submit(struct h2_stream_ctx **pstream,
|
|
|
+ struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
+ const void *buf, size_t len,
|
|
|
+ bool eos, size_t *pnwritten)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream = NULL;
|
|
@@ -2274,36 +2225,34 @@ static ssize_t h2_submit(struct h2_stream_ctx **pstream,
|
|
|
int32_t stream_id;
|
|
|
nghttp2_priority_spec pri_spec;
|
|
|
ssize_t nwritten;
|
|
|
+ CURLcode result = CURLE_OK;
|
|
|
|
|
|
+ *pnwritten = 0;
|
|
|
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
|
|
|
|
|
|
- *err = http2_data_setup(cf, data, &stream);
|
|
|
- if(*err) {
|
|
|
- nwritten = -1;
|
|
|
+ result = http2_data_setup(cf, data, &stream);
|
|
|
+ if(result)
|
|
|
goto out;
|
|
|
- }
|
|
|
|
|
|
- nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
|
|
|
+ nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, &result);
|
|
|
if(nwritten < 0)
|
|
|
goto out;
|
|
|
+ *pnwritten = (size_t)nwritten;
|
|
|
if(!stream->h1.done) {
|
|
|
/* need more data */
|
|
|
goto out;
|
|
|
}
|
|
|
DEBUGASSERT(stream->h1.req);
|
|
|
|
|
|
- *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
|
|
|
- if(*err) {
|
|
|
- nwritten = -1;
|
|
|
+ result = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
|
|
|
+ if(result)
|
|
|
goto out;
|
|
|
- }
|
|
|
/* no longer needed */
|
|
|
Curl_h1_req_parse_free(&stream->h1);
|
|
|
|
|
|
nva = Curl_dynhds_to_nva(&h2_headers, &nheader);
|
|
|
if(!nva) {
|
|
|
- *err = CURLE_OUT_OF_MEMORY;
|
|
|
- nwritten = -1;
|
|
|
+ result = CURLE_OUT_OF_MEMORY;
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -2329,8 +2278,7 @@ static ssize_t h2_submit(struct h2_stream_ctx **pstream,
|
|
|
if(stream_id < 0) {
|
|
|
CURL_TRC_CF(data, cf, "send: nghttp2_submit_request error (%s)%u",
|
|
|
nghttp2_strerror(stream_id), stream_id);
|
|
|
- *err = CURLE_SEND_ERROR;
|
|
|
- nwritten = -1;
|
|
|
+ result = CURLE_SEND_ERROR;
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -2357,48 +2305,46 @@ static ssize_t h2_submit(struct h2_stream_ctx **pstream,
|
|
|
|
|
|
stream->id = stream_id;
|
|
|
|
|
|
- body = (const char *)buf + nwritten;
|
|
|
- bodylen = len - nwritten;
|
|
|
+ body = (const char *)buf + *pnwritten;
|
|
|
+ bodylen = len - *pnwritten;
|
|
|
|
|
|
if(bodylen || eos) {
|
|
|
- ssize_t n = cf_h2_body_send(cf, data, stream, body, bodylen, eos, err);
|
|
|
+ ssize_t n = cf_h2_body_send(cf, data, stream, body, bodylen, eos, &result);
|
|
|
if(n >= 0)
|
|
|
- nwritten += n;
|
|
|
- else if(*err == CURLE_AGAIN)
|
|
|
- *err = CURLE_OK;
|
|
|
- else if(*err != CURLE_AGAIN) {
|
|
|
- *err = CURLE_SEND_ERROR;
|
|
|
- nwritten = -1;
|
|
|
- goto out;
|
|
|
+ *pnwritten += n;
|
|
|
+ else if(result == CURLE_AGAIN)
|
|
|
+ result = CURLE_OK;
|
|
|
+ else {
|
|
|
+ result = CURLE_SEND_ERROR;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
out:
|
|
|
- CURL_TRC_CF(data, cf, "[%d] submit -> %zd, %d",
|
|
|
- stream ? stream->id : -1, nwritten, *err);
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] submit -> %d, %zu",
|
|
|
+ stream ? stream->id : -1, result, *pnwritten);
|
|
|
Curl_safefree(nva);
|
|
|
*pstream = stream;
|
|
|
Curl_dynhds_free(&h2_headers);
|
|
|
- return nwritten;
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
-static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
- const void *buf, size_t len, bool eos,
|
|
|
- CURLcode *err)
|
|
|
+static CURLcode cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
+ const void *buf, size_t len, bool eos,
|
|
|
+ size_t *pnwritten)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data);
|
|
|
struct cf_call_data save;
|
|
|
ssize_t nwritten;
|
|
|
- CURLcode result;
|
|
|
+ CURLcode result = CURLE_OK, r2;
|
|
|
|
|
|
CF_DATA_SAVE(save, cf, data);
|
|
|
+ *pnwritten = 0;
|
|
|
|
|
|
if(!stream || stream->id == -1) {
|
|
|
- nwritten = h2_submit(&stream, cf, data, buf, len, eos, err);
|
|
|
- if(nwritten < 0) {
|
|
|
+ result = h2_submit(&stream, cf, data, buf, len, eos, pnwritten);
|
|
|
+ if(result)
|
|
|
goto out;
|
|
|
- }
|
|
|
DEBUGASSERT(stream);
|
|
|
}
|
|
|
else if(stream->body_eos) {
|
|
@@ -2407,35 +2353,35 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
* to trigger flushing again.
|
|
|
* If this works, we report to have written `len` bytes. */
|
|
|
DEBUGASSERT(eos);
|
|
|
- nwritten = cf_h2_body_send(cf, data, stream, buf, 0, eos, err);
|
|
|
+ nwritten = cf_h2_body_send(cf, data, stream, buf, 0, eos, &result);
|
|
|
CURL_TRC_CF(data, cf, "[%d] cf_body_send last CHUNK -> %zd, %d, eos=%d",
|
|
|
- stream->id, nwritten, *err, eos);
|
|
|
+ stream->id, nwritten, result, eos);
|
|
|
if(nwritten < 0) {
|
|
|
goto out;
|
|
|
}
|
|
|
- nwritten = len;
|
|
|
+ *pnwritten = len;
|
|
|
}
|
|
|
else {
|
|
|
- nwritten = cf_h2_body_send(cf, data, stream, buf, len, eos, err);
|
|
|
+ nwritten = cf_h2_body_send(cf, data, stream, buf, len, eos, &result);
|
|
|
CURL_TRC_CF(data, cf, "[%d] cf_body_send(len=%zu) -> %zd, %d, eos=%d",
|
|
|
- stream->id, len, nwritten, *err, eos);
|
|
|
+ stream->id, len, nwritten, result, eos);
|
|
|
+ if(nwritten >= 0)
|
|
|
+ *pnwritten = (size_t)nwritten;
|
|
|
}
|
|
|
|
|
|
/* Call the nghttp2 send loop and flush to write ALL buffered data,
|
|
|
* headers and/or request body completely out to the network */
|
|
|
- result = h2_progress_egress(cf, data);
|
|
|
+ r2 = h2_progress_egress(cf, data);
|
|
|
|
|
|
/* if the stream has been closed in egress handling (nghttp2 does that
|
|
|
* when it does not like the headers, for example */
|
|
|
if(stream && stream->closed) {
|
|
|
infof(data, "stream %u closed", stream->id);
|
|
|
- *err = CURLE_SEND_ERROR;
|
|
|
- nwritten = -1;
|
|
|
+ result = CURLE_SEND_ERROR;
|
|
|
goto out;
|
|
|
}
|
|
|
- else if(result && (result != CURLE_AGAIN)) {
|
|
|
- *err = result;
|
|
|
- nwritten = -1;
|
|
|
+ else if(r2 && (r2 != CURLE_AGAIN)) {
|
|
|
+ result = r2;
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
@@ -2443,21 +2389,20 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
|
|
|
/* nghttp2 thinks this session is done. If the stream has not been
|
|
|
* closed, this is an error state for out transfer */
|
|
|
if(stream && stream->closed) {
|
|
|
- nwritten = http2_handle_stream_close(cf, data, stream, err);
|
|
|
+ result = http2_handle_stream_close(cf, data, stream, pnwritten);
|
|
|
}
|
|
|
else {
|
|
|
CURL_TRC_CF(data, cf, "send: nothing to do in this session");
|
|
|
- *err = CURLE_HTTP2;
|
|
|
- nwritten = -1;
|
|
|
+ result = CURLE_HTTP2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
out:
|
|
|
if(stream) {
|
|
|
- CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, "
|
|
|
+ CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %d, %zu, "
|
|
|
"eos=%d, h2 windows %d-%d (stream-conn), "
|
|
|
"buffers %zu-%zu (stream-conn)",
|
|
|
- stream->id, len, nwritten, *err,
|
|
|
+ stream->id, len, result, *pnwritten,
|
|
|
stream->body_eos,
|
|
|
nghttp2_session_get_stream_remote_window_size(
|
|
|
ctx->h2, stream->id),
|
|
@@ -2466,14 +2411,14 @@ out:
|
|
|
Curl_bufq_len(&ctx->outbufq));
|
|
|
}
|
|
|
else {
|
|
|
- CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, "
|
|
|
+ CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %d, %zu, "
|
|
|
"connection-window=%d, nw_send_buffer(%zu)",
|
|
|
- len, nwritten, *err,
|
|
|
+ len, result, *pnwritten,
|
|
|
nghttp2_session_get_remote_window_size(ctx->h2),
|
|
|
Curl_bufq_len(&ctx->outbufq));
|
|
|
}
|
|
|
CF_DATA_RESTORE(cf, save);
|
|
|
- return nwritten;
|
|
|
+ return result;
|
|
|
}
|
|
|
|
|
|
static CURLcode cf_h2_flush(struct Curl_cfilter *cf,
|
|
@@ -2716,8 +2661,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
|
|
|
* not. We may have already buffered and exhausted the new window
|
|
|
* by operating on things in flight during the handling of other
|
|
|
* transfers. */
|
|
|
- drain_stream(cf, data, stream);
|
|
|
- Curl_expire(data, 0, EXPIRE_RUN_NOW);
|
|
|
+ Curl_multi_mark_dirty(data);
|
|
|
}
|
|
|
CURL_TRC_CF(data, cf, "[%d] stream now %spaused", stream->id,
|
|
|
pause ? "" : "un");
|
|
@@ -2769,15 +2713,16 @@ static bool cf_h2_is_alive(struct Curl_cfilter *cf,
|
|
|
bool *input_pending)
|
|
|
{
|
|
|
struct cf_h2_ctx *ctx = cf->ctx;
|
|
|
- CURLcode result;
|
|
|
+ bool alive;
|
|
|
struct cf_call_data save;
|
|
|
|
|
|
+ *input_pending = FALSE;
|
|
|
CF_DATA_SAVE(save, cf, data);
|
|
|
- result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending));
|
|
|
+ alive = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending));
|
|
|
CURL_TRC_CF(data, cf, "conn alive -> %d, input_pending=%d",
|
|
|
- result, *input_pending);
|
|
|
+ alive, *input_pending);
|
|
|
CF_DATA_RESTORE(cf, save);
|
|
|
- return result;
|
|
|
+ return alive;
|
|
|
}
|
|
|
|
|
|
static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf,
|
|
@@ -2805,7 +2750,7 @@ static CURLcode cf_h2_query(struct Curl_cfilter *cf,
|
|
|
DEBUGASSERT(pres1);
|
|
|
|
|
|
CF_DATA_SAVE(save, cf, data);
|
|
|
- if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
|
|
|
+ if(!ctx->h2 || !nghttp2_session_check_request_allowed(ctx->h2)) {
|
|
|
/* the limit is what we have in use right now */
|
|
|
effective_max = CONN_ATTACHED(cf->conn);
|
|
|
}
|
|
@@ -2848,7 +2793,6 @@ struct Curl_cftype Curl_cft_nghttp2 = {
|
|
|
cf_h2_connect,
|
|
|
cf_h2_close,
|
|
|
cf_h2_shutdown,
|
|
|
- Curl_cf_def_get_host,
|
|
|
cf_h2_adjust_pollset,
|
|
|
cf_h2_data_pending,
|
|
|
cf_h2_send,
|
|
@@ -3001,17 +2945,17 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data,
|
|
|
/* Remaining data from the protocol switch reply is already using
|
|
|
* the switched protocol, ie. HTTP/2. We add that to the network
|
|
|
* inbufq. */
|
|
|
- ssize_t copied;
|
|
|
+ size_t copied;
|
|
|
|
|
|
- copied = Curl_bufq_write(&ctx->inbufq,
|
|
|
- (const unsigned char *)mem, nread, &result);
|
|
|
- if(copied < 0) {
|
|
|
+ result = Curl_bufq_write(&ctx->inbufq,
|
|
|
+ (const unsigned char *)mem, nread, &copied);
|
|
|
+ if(result) {
|
|
|
failf(data, "error on copying HTTP Upgrade response: %d", result);
|
|
|
return CURLE_RECV_ERROR;
|
|
|
}
|
|
|
- if((size_t)copied < nread) {
|
|
|
+ if(copied < nread) {
|
|
|
failf(data, "connection buffer size could not take all data "
|
|
|
- "from HTTP Upgrade response header: copied=%zd, datalen=%zu",
|
|
|
+ "from HTTP Upgrade response header: copied=%zu, datalen=%zu",
|
|
|
copied, nread);
|
|
|
return CURLE_HTTP2;
|
|
|
}
|