diff --git a/lib/http.h b/lib/http.h index 0c67a774fa..bdd5524db9 100644 --- a/lib/http.h +++ b/lib/http.h @@ -44,7 +44,7 @@ typedef enum { #ifndef CURL_DISABLE_HTTP -#if defined(ENABLE_QUIC) || defined(USE_NGHTTP2) +#if defined(ENABLE_QUIC) #include #endif @@ -212,44 +212,17 @@ struct HTTP { HTTPSEND_BODY /* sending body */ } sending; - void *impl_ctx; /* context for actual HTTP implementation */ - #ifdef USE_WEBSOCKETS struct websocket ws; #endif #ifndef CURL_DISABLE_HTTP + void *h2_ctx; /* HTTP/2 implementation context */ + void *h3_ctx; /* HTTP/3 implementation context */ struct dynbuf send_buffer; /* used if the request couldn't be sent in one chunk, points to an allocated send_buffer struct */ #endif -#ifdef USE_NGHTTP2 - /*********** for HTTP/2 we store stream-local data here *************/ - int32_t stream_id; /* stream we are interested in */ - struct bufq h2_sendbuf; /* request body data buffere for sending */ - size_t h2_send_hds_len; /* amount of bytes in first cf_send() that - are header bytes. Or 0 if not known. */ - struct bufq h2_recvbuf; - size_t h2_recv_hds_len; /* how many bytes in recvbuf are headers */ - struct dynhds resp_trailers; - bool close_handled; /* TRUE if stream closure is handled by libcurl */ - - char **push_headers; /* allocated array */ - size_t push_headers_used; /* number of entries filled in */ - size_t push_headers_alloc; /* number of entries allocated */ - uint32_t error; /* HTTP/2 stream error code */ - bool bodystarted; - int status_code; /* HTTP status code */ - char *mem; /* points to a buffer in memory to store received data */ - size_t len; /* size of the buffer 'mem' points to */ - size_t memlen; /* size of data copied to mem */ - /* fields used by both HTTP/2 and HTTP/3 */ - const uint8_t *upload_mem; /* points to a buffer to read from */ - size_t upload_len; /* size of the buffer 'upload_mem' points to */ - curl_off_t upload_left; /* number of bytes left to upload */ - bool closed; /* TRUE on stream close */ - bool reset; /* TRUE on stream reset */ -#endif }; CURLcode Curl_http_size(struct Curl_easy *data); diff --git a/lib/http2.c b/lib/http2.c index 7de155bb62..17bab60659 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -25,6 +25,7 @@ #include "curl_setup.h" #ifdef USE_NGHTTP2 +#include #include #include "urldata.h" #include "bufq.h" @@ -131,7 +132,6 @@ struct cf_h2_ctx { #define CF_CTX_CALL_DATA(cf) \ ((struct cf_h2_ctx *)(cf)->ctx)->call_data - static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) { struct cf_call_data save = ctx->call_data; @@ -154,6 +154,158 @@ static void cf_h2_ctx_free(struct cf_h2_ctx *ctx) } } +/* + * This specific transfer on this connection has been "drained". + */ +static void drained_transfer(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + if(data->state.drain) { + struct cf_h2_ctx *ctx = cf->ctx; + DEBUGASSERT(ctx->drain_total > 0); + ctx->drain_total--; + data->state.drain = 0; + } +} + +/* + * Mark this transfer to get "drained". + */ +static void drain_this(struct Curl_cfilter *cf, + struct Curl_easy *data) +{ + if(!data->state.drain) { + struct cf_h2_ctx *ctx = cf->ctx; + data->state.drain = 1; + ctx->drain_total++; + DEBUGASSERT(ctx->drain_total > 0); + } +} + +/** + * All about the H3 internals of a stream + */ +struct stream_ctx { + /*********** for HTTP/2 we store stream-local data here *************/ + int32_t id; /* HTTP/2 protocol identifier for stream */ + struct bufq recvbuf; /* response buffer */ + struct bufq sendbuf; /* request buffer */ + struct dynhds resp_trailers; /* response trailer fields */ + size_t req_hds_len; /* amount of request header bytes in sendbuf. */ + size_t resp_hds_len; /* amount of response header bytes in recvbuf */ + curl_off_t upload_left; /* number of request bytes left to upload */ + + char **push_headers; /* allocated array */ + size_t push_headers_used; /* number of entries filled in */ + size_t push_headers_alloc; /* number of entries allocated */ + + int status_code; /* HTTP response status code */ + uint32_t error; /* stream error code */ + bool closed; /* TRUE on stream close */ + bool reset; /* TRUE on stream reset */ + bool close_handled; /* TRUE if stream closure is handled by libcurl */ + bool bodystarted; +}; + +#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ + ((struct HTTP *)(d)->req.p.http)->h2_ctx \ + : NULL)) +#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx +#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \ + H2_STREAM_CTX(d)->id : -2) + +static CURLcode http2_data_setup(struct Curl_cfilter *cf, + struct Curl_easy *data, + struct stream_ctx **pstream) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + + (void)cf; + if(!data || !data->req.p.http) { + failf(data, "initialization failure, transfer not http initialized"); + return CURLE_FAILED_INIT; + } + if(stream) { + *pstream = stream; + return CURLE_OK; + } + + stream = calloc(1, sizeof(*stream)); + if(!stream) + return CURLE_OUT_OF_MEMORY; + + stream->id = -1; + Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, + H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); + Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, + H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); + Curl_dynhds_init(&stream->resp_trailers, 0, DYN_H2_TRAILERS); + stream->req_hds_len = 0; + stream->resp_hds_len = 0; + stream->bodystarted = FALSE; + stream->status_code = -1; + stream->closed = FALSE; + stream->close_handled = FALSE; + stream->error = NGHTTP2_NO_ERROR; + stream->upload_left = 0; + + H2_STREAM_LCTX(data) = stream; + *pstream = stream; + return CURLE_OK; +} + +static void http2_data_done(struct Curl_cfilter *cf, + struct Curl_easy *data, bool premature) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); + + DEBUGASSERT(ctx); + (void)premature; + if(!stream) + return; + + if(ctx->h2) { + if(!stream->closed && stream->id > 0) { + /* RST_STREAM */ + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] premature DATA_DONE, RST stream", + stream->id)); + if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_STREAM_CLOSED)) + (void)nghttp2_session_send(ctx->h2); + } + + drained_transfer(cf, data); + + /* -1 means unassigned and 0 means cleared */ + if(nghttp2_session_get_stream_user_data(ctx->h2, stream->id)) { + int rv = nghttp2_session_set_stream_user_data(ctx->h2, + stream->id, 0); + if(rv) { + infof(data, "http/2: failed to clear user_data for stream %u", + stream->id); + DEBUGASSERT(0); + } + } + } + + Curl_bufq_free(&stream->sendbuf); + Curl_bufq_free(&stream->recvbuf); + Curl_dynhds_free(&stream->resp_trailers); + if(stream->push_headers) { + /* if they weren't used and then freed before */ + for(; stream->push_headers_used > 0; --stream->push_headers_used) { + free(stream->push_headers[stream->push_headers_used - 1]); + } + free(stream->push_headers); + stream->push_headers = NULL; + } + + free(stream); + H2_STREAM_LCTX(data) = NULL; +} + static int h2_client_new(struct Curl_cfilter *cf, nghttp2_session_callbacks *cbs) { @@ -228,35 +380,6 @@ static void multi_connchanged(struct Curl_multi *multi) multi->recheckstate = TRUE; } -static CURLcode http2_data_setup(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; - - (void)cf; - DEBUGASSERT(stream); - DEBUGASSERT(data->state.buffer); - - stream->stream_id = -1; - - Curl_bufq_initp(&stream->h2_sendbuf, &ctx->stream_bufcp, - H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - Curl_bufq_initp(&stream->h2_recvbuf, &ctx->stream_bufcp, - H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - Curl_dynhds_init(&stream->resp_trailers, 0, DYN_H2_TRAILERS); - stream->h2_send_hds_len = 0; - stream->h2_recv_hds_len = 0; - stream->bodystarted = FALSE; - stream->status_code = -1; - stream->closed = FALSE; - stream->close_handled = FALSE; - stream->error = NGHTTP2_NO_ERROR; - stream->upload_left = 0; - - return CURLE_OK; -} - /* * Initialize the cfilter context */ @@ -265,7 +388,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, bool via_h1_upgrade) { struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream; CURLcode result = CURLE_OUT_OF_MEMORY; int rc; nghttp2_session_callbacks *cbs = NULL; @@ -300,10 +423,6 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, } ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; - result = http2_data_setup(cf, data); - if(result) - goto out; - if(via_h1_upgrade) { /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted * in the H1 request and we upgrade from there. This stream @@ -313,7 +432,11 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, binlen = populate_binsettings(binsettings, data); - stream->stream_id = 1; + result = http2_data_setup(cf, data, &stream); + if(result) + goto out; + DEBUGASSERT(stream); + stream->id = 1; /* queue SETTINGS frame (again) */ rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, data->state.httpreq == HTTPREQ_HEAD, @@ -325,11 +448,11 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, goto out; } - rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->stream_id, + rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, data); if(rc) { infof(data, "http/2: failed to set user_data for stream %u", - stream->stream_id); + stream->id); DEBUGASSERT(0); } } @@ -337,9 +460,6 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; int ivlen; - /* H2 Settings need to be submitted. Stream is not open yet. */ - DEBUGASSERT(stream->stream_id == -1); - ivlen = populate_settings(iv, data); rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, ivlen); @@ -369,22 +489,6 @@ out: return result; } -/* - * http2_stream_free() free HTTP2 stream related data - */ -static void http2_stream_free(struct HTTP *stream) -{ - if(stream) { - Curl_bufq_free(&stream->h2_sendbuf); - Curl_bufq_free(&stream->h2_recvbuf); - for(; stream->push_headers_used > 0; --stream->push_headers_used) { - free(stream->push_headers[stream->push_headers_used - 1]); - } - free(stream->push_headers); - stream->push_headers = NULL; - } -} - /* * Returns nonzero if current HTTP/2 session should be closed. */ @@ -593,8 +697,8 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) if(!h || !GOOD_EASY_HANDLE(h->data)) return NULL; else { - struct HTTP *stream = h->data->req.p.http; - if(num < stream->push_headers_used) + struct stream_ctx *stream = H2_STREAM_CTX(h->data); + if(stream && num < stream->push_headers_used) return stream->push_headers[num]; } return NULL; @@ -605,6 +709,9 @@ char *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) */ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) { + struct stream_ctx *stream; + size_t len; + size_t i; /* Verify that we got a good easy handle in the push header struct, mostly to detect rubbish input fast(er). Also empty header name is just a rubbish too. We have to allow ":" at the beginning of @@ -614,50 +721,23 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] || !strcmp(header, ":") || strchr(header + 1, ':')) return NULL; - else { - struct HTTP *stream = h->data->req.p.http; - size_t len = strlen(header); - size_t i; - for(i = 0; ipush_headers_used; i++) { - if(!strncmp(header, stream->push_headers[i], len)) { - /* sub-match, make sure that it is followed by a colon */ - if(stream->push_headers[i][len] != ':') - continue; - return &stream->push_headers[i][len + 1]; - } + + stream = H2_STREAM_CTX(h->data); + if(!stream) + return NULL; + + len = strlen(header); + for(i = 0; ipush_headers_used; i++) { + if(!strncmp(header, stream->push_headers[i], len)) { + /* sub-match, make sure that it is followed by a colon */ + if(stream->push_headers[i][len] != ':') + continue; + return &stream->push_headers[i][len + 1]; } } return NULL; } -/* - * This specific transfer on this connection has been "drained". - */ -static void drained_transfer(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - if(data->state.drain) { - struct cf_h2_ctx *ctx = cf->ctx; - DEBUGASSERT(ctx->drain_total > 0); - ctx->drain_total--; - data->state.drain = 0; - } -} - -/* - * Mark this transfer to get "drained". - */ -static void drain_this(struct Curl_cfilter *cf, - struct Curl_easy *data) -{ - if(!data->state.drain) { - struct cf_h2_ctx *ctx = cf->ctx; - data->state.drain = 1; - ctx->drain_total++; - DEBUGASSERT(ctx->drain_total > 0); - } -} - static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, struct Curl_easy *data) { @@ -669,8 +749,10 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, (void)Curl_close(&second); } else { + struct stream_ctx *second_stream; + second->req.p.http = http; - http2_data_setup(cf, second); + http2_data_setup(cf, second, &second_stream); second->state.priority.weight = data->state.priority.weight; } } @@ -731,6 +813,16 @@ static int set_transfer_url(struct Curl_easy *data, return 0; } +static void discard_newhandle(struct Curl_cfilter *cf, + struct Curl_easy *newhandle) +{ + if(!newhandle->req.p.http) { + http2_data_done(cf, newhandle, TRUE); + newhandle->req.p.http = NULL; + } + (void)Curl_close(&newhandle); +} + static int push_promise(struct Curl_cfilter *cf, struct Curl_easy *data, const nghttp2_push_promise *frame) @@ -738,13 +830,14 @@ static int push_promise(struct Curl_cfilter *cf, struct cf_h2_ctx *ctx = cf->ctx; int rv; /* one of the CURL_PUSH_* defines */ - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] PUSH_PROMISE received", + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] PUSH_PROMISE received", frame->promised_stream_id)); if(data->multi->push_cb) { - struct HTTP *stream; - struct HTTP *newstream; + struct stream_ctx *stream; + struct stream_ctx *newstream; struct curl_pushheaders heads; CURLMcode rc; + CURLcode result; size_t i; /* clone the parent */ struct Curl_easy *newhandle = h2_duphandle(cf, data); @@ -759,21 +852,30 @@ static int push_promise(struct Curl_cfilter *cf, /* ask the application */ DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ask application")); - stream = data->req.p.http; + stream = H2_STREAM_CTX(data); if(!stream) { failf(data, "Internal NULL stream"); - (void)Curl_close(&newhandle); + discard_newhandle(cf, newhandle); rv = CURL_PUSH_DENY; goto fail; } rv = set_transfer_url(newhandle, &heads); if(rv) { - (void)Curl_close(&newhandle); + discard_newhandle(cf, newhandle); rv = CURL_PUSH_DENY; goto fail; } + result = http2_data_setup(cf, newhandle, &newstream); + if(result) { + failf(data, "error setting up stream: %d", result); + discard_newhandle(cf, newhandle); + rv = CURL_PUSH_DENY; + goto fail; + } + DEBUGASSERT(stream); + Curl_set_in_callback(data, true); rv = data->multi->push_cb(data, newhandle, stream->push_headers_used, &heads, @@ -790,14 +892,11 @@ static int push_promise(struct Curl_cfilter *cf, if(rv) { DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); /* denied, kill off the new handle again */ - http2_stream_free(newhandle->req.p.http); - newhandle->req.p.http = NULL; - (void)Curl_close(&newhandle); + discard_newhandle(cf, newhandle); goto fail; } - newstream = newhandle->req.p.http; - newstream->stream_id = frame->promised_stream_id; + newstream->id = frame->promised_stream_id; newhandle->req.maxdownload = -1; newhandle->req.size = -1; @@ -806,29 +905,21 @@ static int push_promise(struct Curl_cfilter *cf, rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); if(rc) { infof(data, "failed to add handle to multi"); - http2_stream_free(newhandle->req.p.http); - newhandle->req.p.http = NULL; - Curl_close(&newhandle); + discard_newhandle(cf, newhandle); rv = CURL_PUSH_DENY; goto fail; } rv = nghttp2_session_set_stream_user_data(ctx->h2, - newstream->stream_id, + newstream->id, newhandle); if(rv) { infof(data, "failed to set user_data for stream %u", - newstream->stream_id); + newstream->id); DEBUGASSERT(0); rv = CURL_PUSH_DENY; goto fail; } - Curl_bufq_initp(&newstream->h2_sendbuf, &ctx->stream_bufcp, - H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); - Curl_bufq_initp(&newstream->h2_recvbuf, &ctx->stream_bufcp, - H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); - newstream->h2_send_hds_len = 0; - Curl_dynhds_init(&newstream->resp_trailers, 0, DYN_H2_TRAILERS); } else { DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ignore it")); @@ -842,16 +933,16 @@ static CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, struct Curl_easy *data, const char *buf, size_t blen) { - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); ssize_t nwritten; CURLcode result; (void)cf; - nwritten = Curl_bufq_write(&stream->h2_recvbuf, + nwritten = Curl_bufq_write(&stream->recvbuf, (const unsigned char *)buf, blen, &result); if(nwritten < 0) return result; - stream->h2_recv_hds_len += (size_t)nwritten; + stream->resp_hds_len += (size_t)nwritten; /* TODO: make sure recvbuf is more flexible with overflow */ DEBUGASSERT((size_t)nwritten == blen); return CURLE_OK; @@ -863,7 +954,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, struct Curl_cfilter *cf = userp; struct cf_h2_ctx *ctx = cf->ctx; struct Curl_easy *data_s = NULL; - struct HTTP *stream = NULL; + struct stream_ctx *stream = NULL; struct Curl_easy *data = CF_DATA_CURRENT(cf); int rv; int32_t stream_id = frame->hd.stream_id; @@ -915,21 +1006,21 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, } data_s = nghttp2_session_get_stream_user_data(session, stream_id); if(!data_s) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] No Curl_easy associated", + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] No Curl_easy associated", stream_id)); return 0; } - stream = data_s->req.p.http; + stream = H2_STREAM_CTX(data_s); if(!stream) { - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] No proto pointer", stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] No proto pointer", stream_id)); return NGHTTP2_ERR_CALLBACK_FAILURE; } switch(frame->hd.type) { case NGHTTP2_DATA: /* If !body started on this stream, then receiving DATA is illegal. */ - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame DATA", stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] recv frame DATA", stream_id)); if(!stream->bodystarted) { rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, NGHTTP2_PROTOCOL_ERROR); @@ -941,14 +1032,14 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { /* Stream has ended. If there is pending data, ensure that read will occur to consume it. */ - if(!data->state.drain && !Curl_bufq_is_empty(&stream->h2_recvbuf)) { + if(!data->state.drain && !Curl_bufq_is_empty(&stream->recvbuf)) { drain_this(cf, data_s); Curl_expire(data, 0, EXPIRE_RUN_NOW); } } break; case NGHTTP2_HEADERS: - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame HEADERS", stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] recv frame HEADERS", stream_id)); if(stream->bodystarted) { /* Only valid HEADERS after body started is trailer HEADERS. We buffer them in on_header callback. */ @@ -971,15 +1062,15 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, if(result) return NGHTTP2_ERR_CALLBACK_FAILURE; - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu header bytes", - stream_id, Curl_bufq_len(&stream->h2_recvbuf))); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] %zu header bytes", + stream_id, Curl_bufq_len(&stream->recvbuf))); if(CF_DATA_CURRENT(cf) != data_s) { drain_this(cf, data_s); Curl_expire(data_s, 0, EXPIRE_RUN_NOW); } break; case NGHTTP2_PUSH_PROMISE: - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] recv PUSH_PROMISE", stream_id)); rv = push_promise(cf, data_s, &frame->push_promise); if(rv) { /* deny! */ int h2; @@ -996,25 +1087,25 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, } break; case NGHTTP2_RST_STREAM: - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] recv RST", stream_id)); stream->closed = TRUE; stream->reset = TRUE; drain_this(cf, data); Curl_expire(data, 0, EXPIRE_RUN_NOW); break; case NGHTTP2_WINDOW_UPDATE: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] recv WINDOW_UPDATE", stream_id)); if((data_s->req.keepon & KEEP_SEND_HOLD) && (data_s->req.keepon & KEEP_SEND)) { data_s->req.keepon &= ~KEEP_SEND_HOLD; drain_this(cf, data_s); Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] un-holding after win update", + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] un-holding after win update", stream_id)); } break; default: - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame %x", + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] recv frame %x", stream_id, frame->hd.type)); break; } @@ -1026,7 +1117,7 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, const uint8_t *mem, size_t len, void *userp) { struct Curl_cfilter *cf = userp; - struct HTTP *stream; + struct stream_ctx *stream; struct Curl_easy *data_s; ssize_t nwritten; CURLcode result; @@ -1041,18 +1132,18 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, /* Receiving a Stream ID not in the hash should not happen - unless we have aborted a transfer artificially and there were more data in the pipeline. Silently ignore. */ - DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%u] Data for unknown", + DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%d] Data for unknown", stream_id)); /* consumed explicitly as no one will read it */ nghttp2_session_consume(session, stream_id, len); return 0; } - stream = data_s->req.p.http; + stream = H2_STREAM_CTX(data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; - nwritten = Curl_bufq_write(&stream->h2_recvbuf, mem, len, &result); + nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result); if(nwritten < 0) { if(result != CURLE_AGAIN) return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1067,9 +1158,9 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, } DEBUGASSERT((size_t)nwritten == len); - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zd/%zu DATA recvd, " + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] %zd/%zu DATA recvd, " "(buffer now holds %zu)", - stream_id, nwritten, len, Curl_bufq_len(&stream->h2_recvbuf))); + stream_id, nwritten, len, Curl_bufq_len(&stream->recvbuf))); return 0; } @@ -1079,7 +1170,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, { struct Curl_cfilter *cf = userp; struct Curl_easy *data_s; - struct HTTP *stream; + struct stream_ctx *stream; int rv; (void)session; @@ -1090,8 +1181,8 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, if(!data_s) { return 0; } - stream = data_s->req.p.http; - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)", + stream = H2_STREAM_CTX(data_s); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] on_stream_close(), %s (err %d)", stream_id, nghttp2_http2_strerror(error_code), error_code)); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1113,7 +1204,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id, stream_id); DEBUGASSERT(0); } - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed now", stream_id)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] closed now", stream_id)); return 0; } @@ -1121,7 +1212,7 @@ static int on_begin_headers(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; - struct HTTP *stream; + struct stream_ctx *stream; struct Curl_easy *data_s = NULL; (void)cf; @@ -1136,7 +1227,7 @@ static int on_begin_headers(nghttp2_session *session, return 0; } - stream = data_s->req.p.http; + stream = H2_STREAM_CTX(data_s); if(!stream || !stream->bodystarted) { return 0; } @@ -1152,7 +1243,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, void *userp) { struct Curl_cfilter *cf = userp; - struct HTTP *stream; + struct stream_ctx *stream; struct Curl_easy *data_s; int32_t stream_id = frame->hd.stream_id; CURLcode result; @@ -1167,7 +1258,7 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = data_s->req.p.http; + stream = H2_STREAM_CTX(data_s); if(!stream) { failf(data_s, "Internal NULL stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1237,8 +1328,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(stream->bodystarted) { /* This is a trailer */ - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] trailer: %.*s: %.*s", - stream->stream_id, + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] trailer: %.*s: %.*s", + stream->id, (int)namelen, name, (int)valuelen, value)); result = Curl_dynhds_add(&stream->resp_trailers, @@ -1277,8 +1368,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] status: HTTP/2 %03d", - stream->stream_id, stream->status_code)); + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] status: HTTP/2 %03d", + stream->id, stream->status_code)); return 0; } @@ -1301,8 +1392,8 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame, if(CF_DATA_CURRENT(cf) != data_s) Curl_expire(data_s, 0, EXPIRE_RUN_NOW); - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] header: %.*s: %.*s", - stream->stream_id, + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] header: %.*s: %.*s", + stream->id, (int)namelen, name, (int)valuelen, value)); @@ -1318,7 +1409,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session, { struct Curl_cfilter *cf = userp; struct Curl_easy *data_s; - struct HTTP *stream = NULL; + struct stream_ctx *stream = NULL; CURLcode result; ssize_t nread; (void)source; @@ -1333,14 +1424,14 @@ static ssize_t req_body_read_callback(nghttp2_session *session, internal error more than anything else! */ return NGHTTP2_ERR_CALLBACK_FAILURE; - stream = data_s->req.p.http; + stream = H2_STREAM_CTX(data_s); if(!stream) return NGHTTP2_ERR_CALLBACK_FAILURE; } else return NGHTTP2_ERR_INVALID_ARGUMENT; - nread = Curl_bufq_read(&stream->h2_sendbuf, buf, length, &result); + nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result); if(nread < 0) { if(result != CURLE_AGAIN) return NGHTTP2_ERR_CALLBACK_FAILURE; @@ -1350,7 +1441,7 @@ static ssize_t req_body_read_callback(nghttp2_session *session, if(nread > 0 && data_s->state.infilesize != -1) stream->upload_left -= nread; - DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] req_body_read(len=%zu) left=%zd" + DEBUGF(LOG_CF(data_s, cf, "[h2sid=%d] req_body_read(len=%zu) left=%zd" " -> %zd, %d", stream_id, length, stream->upload_left, nread, result)); @@ -1376,53 +1467,6 @@ static int error_callback(nghttp2_session *session, } #endif -static void http2_data_done(struct Curl_cfilter *cf, - struct Curl_easy *data, bool premature) -{ - struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; - - /* there might be allocated resources done before this got the 'h2' pointer - setup */ - Curl_bufq_free(&stream->h2_sendbuf); - Curl_bufq_free(&stream->h2_recvbuf); - Curl_dynhds_free(&stream->resp_trailers); - if(stream->push_headers) { - /* if they weren't used and then freed before */ - for(; stream->push_headers_used > 0; --stream->push_headers_used) { - free(stream->push_headers[stream->push_headers_used - 1]); - } - free(stream->push_headers); - stream->push_headers = NULL; - } - - if(!ctx || !ctx->h2) - return; - - (void)premature; - if(!stream->closed && stream->stream_id) { - /* RST_STREAM */ - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] premature DATA_DONE, RST stream", - stream->stream_id)); - if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, - stream->stream_id, NGHTTP2_STREAM_CLOSED)) - (void)nghttp2_session_send(ctx->h2); - } - - drained_transfer(cf, data); - - /* -1 means unassigned and 0 means cleared */ - if(nghttp2_session_get_stream_user_data(ctx->h2, stream->stream_id)) { - int rv = nghttp2_session_set_stream_user_data(ctx->h2, - stream->stream_id, 0); - if(rv) { - infof(data, "http/2: failed to clear user_data for stream %u", - stream->stream_id); - DEBUGASSERT(0); - } - } -} - /* * Append headers to ask for an HTTP1.1 to HTTP2 upgrade. */ @@ -1467,12 +1511,12 @@ static CURLcode http2_data_done_send(struct Curl_cfilter *cf, { struct cf_h2_ctx *ctx = cf->ctx; CURLcode result = CURLE_OK; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); - if(!ctx || !ctx->h2) + if(!ctx || !ctx->h2 || !stream) goto out; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] data done", stream->stream_id)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] data done", stream->id)); if(stream->upload_left) { /* If the stream still thinks there's data left to upload. */ if(stream->upload_left == -1) @@ -1480,7 +1524,7 @@ static CURLcode http2_data_done_send(struct Curl_cfilter *cf, /* resume sending here to trigger the callback to get called again so that it can signal EOF to nghttp2 */ - (void)nghttp2_session_resume_data(ctx->h2, stream->stream_id); + (void)nghttp2_session_resume_data(ctx->h2, stream->id); drain_this(cf, data); } @@ -1490,15 +1534,16 @@ out: static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, struct Curl_easy *data, - struct HTTP *stream, CURLcode *err) + CURLcode *err) { + struct stream_ctx *stream = H2_STREAM_CTX(data); ssize_t rv = 0; drained_transfer(cf, data); if(stream->error == NGHTTP2_REFUSED_STREAM) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new " - "connection", stream->stream_id)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] REFUSED_STREAM, try again on a new " + "connection", stream->id)); connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ data->state.refused_stream = TRUE; *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ @@ -1506,13 +1551,13 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, } else if(stream->error != NGHTTP2_NO_ERROR) { failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", - stream->stream_id, nghttp2_http2_strerror(stream->error), + stream->id, nghttp2_http2_strerror(stream->error), stream->error); *err = CURLE_HTTP2_STREAM; return -1; } else if(stream->reset) { - failf(data, "HTTP/2 stream %u was reset", stream->stream_id); + failf(data, "HTTP/2 stream %u was reset", stream->id); *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; return -1; } @@ -1520,7 +1565,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, if(!stream->bodystarted) { failf(data, "HTTP/2 stream %u was closed cleanly, but before getting " " all response header fields, treated as error", - stream->stream_id); + stream->id); *err = CURLE_HTTP2_STREAM; return -1; } @@ -1587,9 +1632,8 @@ static void h2_pri_spec(struct Curl_easy *data, nghttp2_priority_spec *pri_spec) { struct Curl_data_priority *prio = &data->set.priority; - struct HTTP *depstream = (prio->parent? - prio->parent->req.p.http:NULL); - int32_t depstream_id = depstream? depstream->stream_id:0; + struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent); + int32_t depstream_id = depstream? depstream->id:0; nghttp2_priority_spec_init(pri_spec, depstream_id, sweight_wanted(data), data->set.priority.exclusive); @@ -1606,7 +1650,7 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); int rv = 0; if((sweight_wanted(data) != sweight_in_effect(data)) || @@ -1616,11 +1660,11 @@ static CURLcode h2_progress_egress(struct Curl_cfilter *cf, nghttp2_priority_spec pri_spec; h2_pri_spec(data, &pri_spec); - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Queuing PRIORITY", - stream->stream_id)); - DEBUGASSERT(stream->stream_id != -1); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] Queuing PRIORITY", + stream->id)); + DEBUGASSERT(stream->id != -1); rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, - stream->stream_id, &pri_spec); + stream->id, &pri_spec); if(rv) goto out; } @@ -1639,13 +1683,13 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); ssize_t nread = -1; *err = CURLE_AGAIN; drained_transfer(cf, data); - if(!Curl_bufq_is_empty(&stream->h2_recvbuf)) { - nread = Curl_bufq_read(&stream->h2_recvbuf, + if(!Curl_bufq_is_empty(&stream->recvbuf)) { + nread = Curl_bufq_read(&stream->recvbuf, (unsigned char *)buf, len, err); DEBUGF(LOG_CF(data, cf, "recvbuf read(len=%zu) -> %zd, %d", len, nread, *err)); @@ -1656,11 +1700,11 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, if(nread < 0) { if(stream->closed) { - nread = http2_handle_stream_close(cf, data, stream, err); + nread = http2_handle_stream_close(cf, data, err); } else if(stream->reset || (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx->goaway && ctx->last_stream_id < stream->stream_id)) { + (ctx->goaway && ctx->last_stream_id < stream->id)) { *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; nread = -1; } @@ -1680,7 +1724,7 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream; CURLcode result = CURLE_OK; ssize_t nread; @@ -1693,12 +1737,14 @@ static CURLcode h2_progress_ingress(struct Curl_cfilter *cf, } /* Receive data from the "lower" filters, e.g. network until - * it is time to stop or we have enough data for this stream */ - while(!ctx->conn_closed && /* not closed the connection */ - !stream->closed && /* nor the stream */ - Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */ - !Curl_bufq_is_full(&stream->h2_recvbuf) && /* enough? */ - 1 /* Curl_bufq_len(&stream->h2_recvbuf) < data->set.buffer_size */) { + * it is time to stop due to connection close or us not processing + * all network input */ + while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { + /* Also, when the stream exists, break the loop when it has become + * closed or its receive buffer is full */ + stream = H2_STREAM_CTX(data); + if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) + break; nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); DEBUGF(LOG_CF(data, cf, "read %zd bytes nw data -> %zd, %d", @@ -1730,18 +1776,18 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, char *buf, size_t len, CURLcode *err) { struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); ssize_t nread = -1; CURLcode result; struct cf_call_data save; CF_DATA_SAVE(save, cf, data); - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv: win %u/%u", - stream->stream_id, + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_recv: win %u/%u", + stream->id, nghttp2_session_get_local_window_size(ctx->h2), nghttp2_session_get_stream_local_window_size(ctx->h2, - stream->stream_id) + stream->id) )); nread = stream_recv(cf, data, buf, len, err); if(nread < 0 && *err != CURLE_AGAIN) @@ -1760,24 +1806,24 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, /* 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 */ - if(stream->h2_recv_hds_len >= data_consumed) { - stream->h2_recv_hds_len -= data_consumed; /* no DATA */ + if(stream->resp_hds_len >= data_consumed) { + stream->resp_hds_len -= data_consumed; /* no DATA */ } else { - if(stream->h2_recv_hds_len) { - data_consumed -= stream->h2_recv_hds_len; - stream->h2_recv_hds_len = 0; + if(stream->resp_hds_len) { + data_consumed -= stream->resp_hds_len; + stream->resp_hds_len = 0; } if(data_consumed) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] increase window by %zu", - stream->stream_id, data_consumed)); - nghttp2_session_consume(ctx->h2, stream->stream_id, data_consumed); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] increase window by %zu", + stream->id, data_consumed)); + nghttp2_session_consume(ctx->h2, stream->id, data_consumed); } } if(stream->closed) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] closed stream, set drain", - stream->stream_id)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] closed stream, set drain", + stream->id)); drain_this(cf, data); } } @@ -1788,8 +1834,8 @@ out: *err = result; nread = -1; } - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv(len=%zu) -> %zd %d", - stream->stream_id, len, nread, *err)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_recv(len=%zu) -> %zd %d", + stream->id, len, nread, *err)); CF_DATA_RESTORE(cf, save); return nread; } @@ -1804,7 +1850,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, */ struct cf_h2_ctx *ctx = cf->ctx; int rv; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); nghttp2_nv *nva = NULL; size_t nheader; nghttp2_data_provider data_prd; @@ -1817,35 +1863,35 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, CF_DATA_SAVE(save, cf, data); - if(stream->stream_id != -1) { - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send: win %u/%u", - stream->stream_id, + if(stream && stream->id != -1) { + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send: win %u/%u", + stream->id, nghttp2_session_get_remote_window_size(ctx->h2), nghttp2_session_get_stream_remote_window_size( - ctx->h2, stream->stream_id) + ctx->h2, stream->id) )); if(stream->close_handled) { - infof(data, "stream %u closed", stream->stream_id); + infof(data, "stream %u closed", stream->id); *err = CURLE_HTTP2_STREAM; nwritten = -1; goto out; } else if(stream->closed) { - nwritten = http2_handle_stream_close(cf, data, stream, err); + nwritten = http2_handle_stream_close(cf, data, err); goto out; } /* If stream_id != -1, we have dispatched request HEADERS, and now are going to send or sending request body in DATA frame */ - nwritten = Curl_bufq_write(&stream->h2_sendbuf, buf, len, err); + nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); if(nwritten < 0) { if(*err != CURLE_AGAIN) goto out; nwritten = 0; } - if(!Curl_bufq_is_empty(&stream->h2_sendbuf)) { - rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id); + if(!Curl_bufq_is_empty(&stream->sendbuf)) { + rv = nghttp2_session_resume_data(ctx->h2, stream->id); if(nghttp2_is_fatal(rv)) { *err = CURLE_SEND_ERROR; nwritten = -1; @@ -1876,35 +1922,44 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, if(!nwritten) { size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->stream_id); - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send: win %u/%zu", - stream->stream_id, + stream->id); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send: win %u/%zu", + stream->id, nghttp2_session_get_remote_window_size(ctx->h2), rwin)); if(rwin == 0) { /* We cannot upload more as the stream's remote window size * is 0. We need to receive WIN_UPDATEs before we can continue. */ data->req.keepon |= KEEP_SEND_HOLD; - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] holding send as remote flow " - "window is exhausted", stream->stream_id)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] holding send as remote flow " + "window is exhausted", stream->id)); } } /* handled writing BODY for open stream. */ goto out; } - DEBUGF(LOG_CF(data, cf, "cf_send, submit %s", data->state.url)); - if(!stream->h2_send_hds_len) { + *err = http2_data_setup(cf, data, &stream); + if(*err) { + nwritten = -1; + goto out; + } + + if(!stream->req_hds_len) { /* first invocation carries the HTTP/1.1 formatted request headers. * we remember that in case we EAGAIN this call, because the next * invocation may have added request body data into the buffer. */ - stream->h2_send_hds_len = len; + stream->req_hds_len = len; + DEBUGF(LOG_CF(data, cf, "cf_send, first submit (len=%zu, hds_len=%zu)", + len, stream->req_hds_len)); } /* Stream has not been opened yet. `buf` is expected to contain - * `stream->h2_send_hds_len` bytes of request headers. */ - DEBUGASSERT(stream->h2_send_hds_len <= len); - result = Curl_pseudo_headers(data, buf, stream->h2_send_hds_len, + * `stream->req_hds_len` bytes of request headers. */ + DEBUGF(LOG_CF(data, cf, "cf_send, submit %s (len=%zu, hds_len=%zu)", + data->state.url, len, stream->req_hds_len)); + DEBUGASSERT(stream->req_hds_len <= len); + result = Curl_pseudo_headers(data, buf, stream->req_hds_len, NULL, &hreq); if(result) { *err = result; @@ -1946,7 +2001,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, stream->upload_left = data->state.infilesize; else /* data sending without specifying the data amount up front */ - stream->upload_left = -1; /* unknown, but not zero */ + stream->upload_left = -1; /* unknown */ data_prd.read_callback = req_body_read_callback; data_prd.source.ptr = NULL; @@ -1954,6 +2009,7 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, &data_prd, data); break; default: + stream->upload_left = 0; /* no request body */ stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, NULL, data); } @@ -1968,12 +2024,12 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, goto out; } - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send(len=%zu) submit %s", + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) submit %s", stream_id, len, data->state.url)); infof(data, "Using Stream ID: %u (easy handle %p)", stream_id, (void *)data); - stream->stream_id = stream_id; - nwritten = stream->h2_send_hds_len; + stream->id = stream_id; + nwritten = stream->req_hds_len; result = h2_progress_ingress(cf, data); if(result) { @@ -1997,8 +2053,8 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, } out: - DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_send -> %zd, %d", - stream->stream_id, nwritten, *err)); + DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send -> %zd, %d", + stream->id, nwritten, *err)); CF_DATA_RESTORE(cf, save); return nwritten; } @@ -2009,7 +2065,7 @@ static int cf_h2_get_select_socks(struct Curl_cfilter *cf, { struct cf_h2_ctx *ctx = cf->ctx; struct SingleRequest *k = &data->req; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); int bitmap = GETSOCK_BLANK; struct cf_call_data save; @@ -2027,7 +2083,7 @@ static int cf_h2_get_select_socks(struct Curl_cfilter *cf, nghttp2_session_want_write(ctx->h2)) && (nghttp2_session_get_remote_window_size(ctx->h2) && nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->stream_id))) + stream->id))) bitmap |= GETSOCK_WRITESOCK(0); CF_DATA_RESTORE(cf, save); @@ -2109,18 +2165,18 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) { +#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE struct cf_h2_ctx *ctx = cf->ctx; + struct stream_ctx *stream = H2_STREAM_CTX(data); DEBUGASSERT(data); -#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE - if(ctx && ctx->h2) { - struct HTTP *stream = data->req.p.http; + if(ctx && ctx->h2 && stream) { uint32_t window = !pause * H2_STREAM_WINDOW_SIZE; CURLcode result; int rv = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, - stream->stream_id, + stream->id, window); if(rv) { failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", @@ -2134,16 +2190,16 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, return result; DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u", - window, stream->stream_id)); + window, stream->id)); #ifdef DEBUGBUILD { /* read out the stream local window again */ uint32_t window2 = nghttp2_session_get_stream_local_window_size(ctx->h2, - stream->stream_id); + stream->id); DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", - window2, stream->stream_id)); + window2, stream->id)); } #endif } @@ -2162,10 +2218,8 @@ static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf, CF_DATA_SAVE(save, cf, data); switch(event) { - case CF_CTRL_DATA_SETUP: { - result = http2_data_setup(cf, data); + case CF_CTRL_DATA_SETUP: break; - } case CF_CTRL_DATA_PAUSE: result = http2_data_pause(cf, data, (arg1 != 0)); break; @@ -2188,10 +2242,10 @@ static bool cf_h2_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data) { struct cf_h2_ctx *ctx = cf->ctx; - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) - || (stream && !Curl_bufq_is_empty(&stream->h2_recvbuf)))) + || (stream && !Curl_bufq_is_empty(&stream->recvbuf)))) return TRUE; return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; } @@ -2483,7 +2537,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, CURLE_HTTP2_STREAM error! */ bool Curl_h2_http_1_1_error(struct Curl_easy *data) { - struct HTTP *stream = data->req.p.http; + struct stream_ctx *stream = H2_STREAM_CTX(data); return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); } diff --git a/lib/vquic/curl_msh3.c b/lib/vquic/curl_msh3.c index ce1e634732..d6aaa47bba 100644 --- a/lib/vquic/curl_msh3.c +++ b/lib/vquic/curl_msh3.c @@ -148,9 +148,9 @@ struct stream_ctx { }; #define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->impl_ctx \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->impl_ctx +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx #define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ H3_STREAM_CTX(d)->id : -2) diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c index 5a0b154051..26bc80c9c4 100644 --- a/lib/vquic/curl_ngtcp2.c +++ b/lib/vquic/curl_ngtcp2.c @@ -186,9 +186,9 @@ struct stream_ctx { }; #define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->impl_ctx \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->impl_ctx +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx #define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ H3_STREAM_CTX(d)->id : -2) @@ -198,6 +198,11 @@ static CURLcode h3_data_setup(struct Curl_cfilter *cf, struct cf_ngtcp2_ctx *ctx = cf->ctx; struct stream_ctx *stream = H3_STREAM_CTX(data); + if(!data || !data->req.p.http) { + failf(data, "initialization failure, transfer not http initialized"); + return CURLE_FAILED_INIT; + } + if(stream) return CURLE_OK; @@ -1517,7 +1522,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, size_t len) { struct cf_ngtcp2_ctx *ctx = cf->ctx; - struct stream_ctx *stream = H3_STREAM_CTX(data); + struct stream_ctx *stream = NULL; size_t nheader; CURLcode result = CURLE_OK; nghttp3_nv *nva = NULL; @@ -1527,6 +1532,11 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, nghttp3_data_reader reader; nghttp3_data_reader *preader = NULL; + result = h3_data_setup(cf, data); + if(result) + goto out; + stream = H3_STREAM_CTX(data); + rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream->id, NULL); if(rc) { failf(data, "can get bidi streams"); @@ -1579,7 +1589,7 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf, stream->id, data->state.url)); out: - if(!result && rc) { + if(stream && !result && rc) { switch(rc) { case NGHTTP3_ERR_CONN_CLOSING: DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send, " @@ -1612,13 +1622,13 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data, DEBUGASSERT(ctx->h3conn); *err = CURLE_OK; - if(stream->closed) { + if(stream && stream->closed) { *err = CURLE_HTTP3; sent = -1; goto out; } - if(stream->id < 0) { + if(!stream || stream->id < 0) { CURLcode result = h3_stream_open(cf, data, buf, len); if(result) { DEBUGF(LOG_CF(data, cf, "failed to open stream -> %d", result)); @@ -2070,10 +2080,8 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf, (void)arg1; (void)arg2; switch(event) { - case CF_CTRL_DATA_SETUP: { - result = h3_data_setup(cf, data); + case CF_CTRL_DATA_SETUP: break; - } case CF_CTRL_DATA_DONE: { h3_data_done(cf, data); break; @@ -2235,10 +2243,6 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf, if(result) return result; - result = h3_data_setup(cf, data); - if(result) - return result; - Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd, &sockaddr, NULL, NULL, NULL, NULL); ctx->q.local_addrlen = sizeof(ctx->q.local_addr); diff --git a/lib/vquic/curl_quiche.c b/lib/vquic/curl_quiche.c index d2209adeb3..69a65f01bc 100644 --- a/lib/vquic/curl_quiche.c +++ b/lib/vquic/curl_quiche.c @@ -197,9 +197,9 @@ struct stream_ctx { }; #define H3_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ - ((struct HTTP *)(d)->req.p.http)->impl_ctx \ + ((struct HTTP *)(d)->req.p.http)->h3_ctx \ : NULL)) -#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->impl_ctx +#define H3_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h3_ctx #define H3_STREAM_ID(d) (H3_STREAM_CTX(d)? \ H3_STREAM_CTX(d)->id : -2)