From 67a42fc97c64c83e02f6f0d68e5a4a22c71138d3 Mon Sep 17 00:00:00 2001 From: dartraiden Date: Thu, 25 Jul 2024 00:50:30 +0300 Subject: libcurl: update to 8.9.0 --- libs/libcurl/src/http2.c | 160 ++++++++++++++++++++++++++++++----------------- 1 file changed, 102 insertions(+), 58 deletions(-) (limited to 'libs/libcurl/src/http2.c') diff --git a/libs/libcurl/src/http2.c b/libs/libcurl/src/http2.c index 261c9fbb8a..108678a2b8 100644 --- a/libs/libcurl/src/http2.c +++ b/libs/libcurl/src/http2.c @@ -83,18 +83,17 @@ /* spare chunks we keep for a full window */ #define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) -/* We need to accommodate the max number of streams with their window - * sizes on the overall connection. Streams might become PAUSED which - * will block their received QUOTA in the connection window. And if we - * run out of space, the server is blocked from sending us any data. - * See #10988 for an issue with this. */ +/* We need to accommodate the max number of streams with their window sizes on + * the overall connection. Streams might become PAUSED which will block their + * received QUOTA in the connection window. If we run out of space, the server + * is blocked from sending us any data. See #10988 for an issue with this. */ #define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE) #define H2_SETTINGS_IV_LEN 3 #define H2_BINSETTINGS_LEN 80 -static int populate_settings(nghttp2_settings_entry *iv, - struct Curl_easy *data) +static size_t populate_settings(nghttp2_settings_entry *iv, + struct Curl_easy *data) { iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; iv[0].value = Curl_multi_max_concurrent_streams(data->multi); @@ -112,7 +111,7 @@ static ssize_t populate_binsettings(uint8_t *binsettings, struct Curl_easy *data) { nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; - int ivlen; + size_t ivlen; ivlen = populate_settings(iv, data); /* this returns number of bytes it wrote or a negative number on error. */ @@ -133,10 +132,12 @@ struct cf_h2_ctx { struct Curl_hash streams; /* hash of `data->id` to `h2_stream_ctx` */ size_t drain_total; /* sum of all stream's UrlState drain */ uint32_t max_concurrent_streams; - int32_t goaway_error; - int32_t last_stream_id; + uint32_t goaway_error; /* goaway error code from server */ + int32_t remote_max_sid; /* max id processed by server */ + int32_t local_max_sid; /* max id processed by us */ BIT(conn_closed); - BIT(goaway); + BIT(rcvd_goaway); + BIT(sent_goaway); BIT(enable_push); BIT(nw_out_blocked); }; @@ -289,10 +290,6 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf, (void)cf; DEBUGASSERT(data); - if(!data->req.p.http) { - failf(data, "initialization failure, transfer not http initialized"); - return CURLE_FAILED_INIT; - } stream = H2_STREAM_CTX(ctx, data); if(stream) { *pstream = stream; @@ -437,7 +434,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); Curl_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER); Curl_hash_offt_init(&ctx->streams, 63, h2_stream_hash_free); - ctx->last_stream_id = 2147483647; + ctx->remote_max_sid = 2147483647; rc = nghttp2_session_callbacks_new(&cbs); if(rc) { @@ -486,7 +483,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, DEBUGASSERT(stream); stream->id = 1; /* queue SETTINGS frame (again) */ - rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, + rc = nghttp2_session_upgrade2(ctx->h2, binsettings, (size_t)binlen, data->state.httpreq == HTTPREQ_HEAD, NULL); if(rc) { @@ -507,7 +504,7 @@ static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, } else { nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; - int ivlen; + size_t ivlen; ivlen = populate_settings(iv, data); rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, @@ -612,8 +609,8 @@ static bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, return FALSE; if(*input_pending) { - /* This happens before we've sent off a request and the connection is - not in use by any other transfer, there shouldn't be any data here, + /* This happens before we have sent off a request and the connection is + not in use by any other transfer, there should not be any data here, only "protocol frames" */ CURLcode result; ssize_t nread = -1; @@ -794,18 +791,9 @@ static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, { struct Curl_easy *second = curl_easy_duphandle(data); if(second) { - /* setup the request struct */ - struct HTTP *http = calloc(1, sizeof(struct HTTP)); - if(!http) { - (void)Curl_close(&second); - } - else { - struct h2_stream_ctx *second_stream; - - second->req.p.http = http; - http2_data_setup(cf, second, &second_stream); - second->state.priority.weight = data->state.priority.weight; - } + struct h2_stream_ctx *second_stream; + http2_data_setup(cf, second, &second_stream); + second->state.priority.weight = data->state.priority.weight; } return second; } @@ -867,9 +855,7 @@ fail: static void discard_newhandle(struct Curl_cfilter *cf, struct Curl_easy *newhandle) { - if(newhandle->req.p.http) { - http2_data_done(cf, newhandle); - } + http2_data_done(cf, newhandle); (void)Curl_close(&newhandle); } @@ -967,6 +953,10 @@ static int push_promise(struct Curl_cfilter *cf, rv = CURL_PUSH_DENY; goto fail; } + + /* success, remember max stream id processed */ + if(newstream->id > ctx->local_max_sid) + ctx->local_max_sid = newstream->id; } else { CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it"); @@ -1007,7 +997,7 @@ static void h2_xfer_write_resp(struct Curl_cfilter *cf, "RST-ing stream", stream->id, stream->xfer_result, blen); nghttp2_submit_rst_stream(ctx->h2, 0, stream->id, - NGHTTP2_ERR_CALLBACK_FAILURE); + (uint32_t)NGHTTP2_ERR_CALLBACK_FAILURE); } } @@ -1048,7 +1038,7 @@ static CURLcode on_stream_frame(struct Curl_cfilter *cf, break; case NGHTTP2_HEADERS: if(stream->bodystarted) { - /* Only valid HEADERS after body started is trailer HEADERS. We + /* Only valid HEADERS after body started is trailer HEADERS. We buffer them in on_header callback. */ break; } @@ -1252,12 +1242,12 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, break; } case NGHTTP2_GOAWAY: - ctx->goaway = TRUE; + ctx->rcvd_goaway = TRUE; ctx->goaway_error = frame->goaway.error_code; - ctx->last_stream_id = frame->goaway.last_stream_id; + ctx->remote_max_sid = frame->goaway.last_stream_id; if(data) { - infof(data, "received GOAWAY, error=%d, last_stream=%u", - ctx->goaway_error, ctx->last_stream_id); + infof(data, "received GOAWAY, error=%u, last_stream=%u", + ctx->goaway_error, ctx->remote_max_sid); Curl_multi_connchanged(data->multi); } break; @@ -1654,7 +1644,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req, return CURLE_FAILED_INIT; } - result = Curl_base64url_encode((const char *)binsettings, binlen, + result = Curl_base64url_encode((const char *)binsettings, (size_t)binlen, &base64, &blen); if(result) { Curl_dyn_free(req); @@ -1710,7 +1700,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, 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"); /* don't use this anymore */ + 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; @@ -1817,7 +1807,7 @@ static void h2_pri_spec(struct cf_h2_ctx *ctx, } /* - * Check if there's been an update in the priority / + * Check if there is been an update in the priority / * dependency settings and if so it submits a PRIORITY frame with the updated * info. * Flush any out data pending in the network buffer. @@ -1878,7 +1868,7 @@ static ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, } else if(stream->reset || (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || - (ctx->goaway && ctx->last_stream_id < stream->id)) { + (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; @@ -2015,7 +2005,7 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, out: result = h2_progress_egress(cf, data); if(result == CURLE_AGAIN) { - /* pending data to send, need to be called again. Ideally, we'd + /* pending data to send, need to be called again. Ideally, we would * monitor the socket for POLLOUT, but we might not be in SENDING * transfer state any longer and are unable to make this happen. */ @@ -2292,8 +2282,8 @@ static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, /* Unable to send all data, due to connection blocked or H2 window * exhaustion. Data is left in our stream buffer, or nghttp2's internal * frame buffer or our network out buffer. */ - size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, - stream->id); + size_t rwin = (size_t)nghttp2_session_get_stream_remote_window_size( + ctx->h2, stream->id); /* At the start of a stream, we are called with request headers * and, possibly, parts of the body. Later, only body data. * If we cannot send pure body data, we EAGAIN. If there had been @@ -2358,6 +2348,7 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, struct easy_pollset *ps) { struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; curl_socket_t sock; bool want_recv, want_send; @@ -2368,7 +2359,6 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, Curl_pollset_check(data, ps, sock, &want_recv, &want_send); if(want_recv || want_send) { struct h2_stream_ctx *stream = H2_STREAM_CTX(ctx, data); - struct cf_call_data save; bool c_exhaust, s_exhaust; CF_DATA_SAVE(save, cf, data); @@ -2383,6 +2373,14 @@ static void cf_h2_adjust_pollset(struct Curl_cfilter *cf, Curl_pollset_set(data, ps, sock, want_recv, want_send); CF_DATA_RESTORE(cf, save); } + else if(ctx->sent_goaway && !cf->shutdown) { + /* shutdown in progress */ + CF_DATA_SAVE(save, cf, data); + want_send = nghttp2_session_want_write(ctx->h2); + want_recv = nghttp2_session_want_read(ctx->h2); + Curl_pollset_set(data, ps, sock, want_recv, want_send); + CF_DATA_RESTORE(cf, save); + } } static CURLcode cf_h2_connect(struct Curl_cfilter *cf, @@ -2446,6 +2444,7 @@ static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) CF_DATA_SAVE(save, cf, data); cf_h2_ctx_clear(ctx); CF_DATA_RESTORE(cf, save); + cf->connected = FALSE; } if(cf->next) cf->next->cft->do_close(cf->next, data); @@ -2462,6 +2461,50 @@ static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) } } +static CURLcode cf_h2_shutdown(struct Curl_cfilter *cf, + struct Curl_easy *data, bool *done) +{ + struct cf_h2_ctx *ctx = cf->ctx; + struct cf_call_data save; + CURLcode result; + int rv; + + if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) { + *done = TRUE; + return CURLE_OK; + } + + CF_DATA_SAVE(save, cf, data); + + if(!ctx->sent_goaway) { + rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE, + ctx->local_max_sid, 0, + (const uint8_t *)"shutown", sizeof("shutown")); + if(rv) { + failf(data, "nghttp2_submit_goaway() failed: %s(%d)", + nghttp2_strerror(rv), rv); + result = CURLE_SEND_ERROR; + goto out; + } + ctx->sent_goaway = TRUE; + } + /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */ + result = CURLE_OK; + if(nghttp2_session_want_write(ctx->h2)) + result = h2_progress_egress(cf, data); + if(!result && nghttp2_session_want_read(ctx->h2)) + result = h2_progress_ingress(cf, data, 0); + + *done = (ctx->conn_closed || + (!result && !nghttp2_session_want_write(ctx->h2) && + !nghttp2_session_want_read(ctx->h2))); + +out: + CF_DATA_RESTORE(cf, save); + cf->shutdown = (result || *done); + return result; +} + static CURLcode http2_data_pause(struct Curl_cfilter *cf, struct Curl_easy *data, bool pause) @@ -2474,10 +2517,10 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, if(ctx && ctx->h2 && stream) { uint32_t window = pause? 0 : stream->local_window_size; - int rv = nghttp2_session_set_local_window_size(ctx->h2, - NGHTTP2_FLAG_NONE, - stream->id, - window); + int rv = (int)nghttp2_session_set_local_window_size(ctx->h2, + NGHTTP2_FLAG_NONE, + stream->id, + (int32_t)window); if(rv) { failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", nghttp2_strerror(rv), rv); @@ -2505,7 +2548,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf, #ifdef DEBUGBUILD { /* read out the stream local window again */ - uint32_t window2 = + uint32_t window2 = (uint32_t) nghttp2_session_get_stream_local_window_size(ctx->h2, stream->id); DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", @@ -2632,6 +2675,7 @@ struct Curl_cftype Curl_cft_nghttp2 = { cf_h2_destroy, cf_h2_connect, cf_h2_close, + cf_h2_shutdown, Curl_cf_def_get_host, cf_h2_adjust_pollset, cf_h2_data_pending, @@ -2729,7 +2773,7 @@ bool Curl_http2_may_switch(struct Curl_easy *data, data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { #ifndef CURL_DISABLE_PROXY if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { - /* We don't support HTTP/2 proxies yet. Also it's debatable + /* We do not support HTTP/2 proxies yet. Also it is debatable whether or not this setting should apply to HTTP/2 proxies. */ infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); return FALSE; @@ -2753,7 +2797,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data, if(result) return result; - conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->httpversion = 20; /* we know we are on HTTP/2 now */ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ conn->bundle->multiuse = BUNDLE_MULTIPLEX; Curl_multi_connchanged(data->multi); @@ -2777,7 +2821,7 @@ CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) return result; cf_h2 = cf->next; - cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */ + cf->conn->httpversion = 20; /* we know we are on HTTP/2 now */ cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; Curl_multi_connchanged(data->multi); @@ -2830,7 +2874,7 @@ CURLcode Curl_http2_upgrade(struct Curl_easy *data, " after upgrade: len=%zu", nread); } - conn->httpversion = 20; /* we know we're on HTTP/2 now */ + conn->httpversion = 20; /* we know we are on HTTP/2 now */ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ conn->bundle->multiuse = BUNDLE_MULTIPLEX; Curl_multi_connchanged(data->multi); -- cgit v1.2.3