113498266Sopenharmony_ci/*************************************************************************** 213498266Sopenharmony_ci * _ _ ____ _ 313498266Sopenharmony_ci * Project ___| | | | _ \| | 413498266Sopenharmony_ci * / __| | | | |_) | | 513498266Sopenharmony_ci * | (__| |_| | _ <| |___ 613498266Sopenharmony_ci * \___|\___/|_| \_\_____| 713498266Sopenharmony_ci * 813498266Sopenharmony_ci * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al. 913498266Sopenharmony_ci * 1013498266Sopenharmony_ci * This software is licensed as described in the file COPYING, which 1113498266Sopenharmony_ci * you should have received as part of this distribution. The terms 1213498266Sopenharmony_ci * are also available at https://curl.se/docs/copyright.html. 1313498266Sopenharmony_ci * 1413498266Sopenharmony_ci * You may opt to use, copy, modify, merge, publish, distribute and/or sell 1513498266Sopenharmony_ci * copies of the Software, and permit persons to whom the Software is 1613498266Sopenharmony_ci * furnished to do so, under the terms of the COPYING file. 1713498266Sopenharmony_ci * 1813498266Sopenharmony_ci * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 1913498266Sopenharmony_ci * KIND, either express or implied. 2013498266Sopenharmony_ci * 2113498266Sopenharmony_ci * SPDX-License-Identifier: curl 2213498266Sopenharmony_ci * 2313498266Sopenharmony_ci ***************************************************************************/ 2413498266Sopenharmony_ci 2513498266Sopenharmony_ci#include "curl_setup.h" 2613498266Sopenharmony_ci 2713498266Sopenharmony_ci#ifdef USE_NGHTTP2 2813498266Sopenharmony_ci#include <stdint.h> 2913498266Sopenharmony_ci#include <nghttp2/nghttp2.h> 3013498266Sopenharmony_ci#include "urldata.h" 3113498266Sopenharmony_ci#include "bufq.h" 3213498266Sopenharmony_ci#include "http1.h" 3313498266Sopenharmony_ci#include "http2.h" 3413498266Sopenharmony_ci#include "http.h" 3513498266Sopenharmony_ci#include "sendf.h" 3613498266Sopenharmony_ci#include "select.h" 3713498266Sopenharmony_ci#include "curl_base64.h" 3813498266Sopenharmony_ci#include "strcase.h" 3913498266Sopenharmony_ci#include "multiif.h" 4013498266Sopenharmony_ci#include "url.h" 4113498266Sopenharmony_ci#include "urlapi-int.h" 4213498266Sopenharmony_ci#include "cfilters.h" 4313498266Sopenharmony_ci#include "connect.h" 4413498266Sopenharmony_ci#include "rand.h" 4513498266Sopenharmony_ci#include "strtoofft.h" 4613498266Sopenharmony_ci#include "strdup.h" 4713498266Sopenharmony_ci#include "transfer.h" 4813498266Sopenharmony_ci#include "dynbuf.h" 4913498266Sopenharmony_ci#include "headers.h" 5013498266Sopenharmony_ci/* The last 3 #include files should be in this order */ 5113498266Sopenharmony_ci#include "curl_printf.h" 5213498266Sopenharmony_ci#include "curl_memory.h" 5313498266Sopenharmony_ci#include "memdebug.h" 5413498266Sopenharmony_ci 5513498266Sopenharmony_ci#if (NGHTTP2_VERSION_NUM < 0x010c00) 5613498266Sopenharmony_ci#error too old nghttp2 version, upgrade! 5713498266Sopenharmony_ci#endif 5813498266Sopenharmony_ci 5913498266Sopenharmony_ci#ifdef CURL_DISABLE_VERBOSE_STRINGS 6013498266Sopenharmony_ci#define nghttp2_session_callbacks_set_error_callback(x,y) 6113498266Sopenharmony_ci#endif 6213498266Sopenharmony_ci 6313498266Sopenharmony_ci#if (NGHTTP2_VERSION_NUM >= 0x010c00) 6413498266Sopenharmony_ci#define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1 6513498266Sopenharmony_ci#endif 6613498266Sopenharmony_ci 6713498266Sopenharmony_ci 6813498266Sopenharmony_ci/* buffer dimensioning: 6913498266Sopenharmony_ci * use 16K as chunk size, as that fits H2 DATA frames well */ 7013498266Sopenharmony_ci#define H2_CHUNK_SIZE (16 * 1024) 7113498266Sopenharmony_ci/* this is how much we want "in flight" for a stream */ 7213498266Sopenharmony_ci#define H2_STREAM_WINDOW_SIZE (10 * 1024 * 1024) 7313498266Sopenharmony_ci/* on receiving from TLS, we prep for holding a full stream window */ 7413498266Sopenharmony_ci#define H2_NW_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) 7513498266Sopenharmony_ci/* on send into TLS, we just want to accumulate small frames */ 7613498266Sopenharmony_ci#define H2_NW_SEND_CHUNKS 1 7713498266Sopenharmony_ci/* stream recv/send chunks are a result of window / chunk sizes */ 7813498266Sopenharmony_ci#define H2_STREAM_RECV_CHUNKS (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) 7913498266Sopenharmony_ci/* keep smaller stream upload buffer (default h2 window size) to have 8013498266Sopenharmony_ci * our progress bars and "upload done" reporting closer to reality */ 8113498266Sopenharmony_ci#define H2_STREAM_SEND_CHUNKS ((64 * 1024) / H2_CHUNK_SIZE) 8213498266Sopenharmony_ci/* spare chunks we keep for a full window */ 8313498266Sopenharmony_ci#define H2_STREAM_POOL_SPARES (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) 8413498266Sopenharmony_ci 8513498266Sopenharmony_ci/* We need to accommodate the max number of streams with their window 8613498266Sopenharmony_ci * sizes on the overall connection. Streams might become PAUSED which 8713498266Sopenharmony_ci * will block their received QUOTA in the connection window. And if we 8813498266Sopenharmony_ci * run out of space, the server is blocked from sending us any data. 8913498266Sopenharmony_ci * See #10988 for an issue with this. */ 9013498266Sopenharmony_ci#define HTTP2_HUGE_WINDOW_SIZE (100 * H2_STREAM_WINDOW_SIZE) 9113498266Sopenharmony_ci 9213498266Sopenharmony_ci#define H2_SETTINGS_IV_LEN 3 9313498266Sopenharmony_ci#define H2_BINSETTINGS_LEN 80 9413498266Sopenharmony_ci 9513498266Sopenharmony_cistatic int populate_settings(nghttp2_settings_entry *iv, 9613498266Sopenharmony_ci struct Curl_easy *data) 9713498266Sopenharmony_ci{ 9813498266Sopenharmony_ci iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; 9913498266Sopenharmony_ci iv[0].value = Curl_multi_max_concurrent_streams(data->multi); 10013498266Sopenharmony_ci 10113498266Sopenharmony_ci iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; 10213498266Sopenharmony_ci iv[1].value = H2_STREAM_WINDOW_SIZE; 10313498266Sopenharmony_ci 10413498266Sopenharmony_ci iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH; 10513498266Sopenharmony_ci iv[2].value = data->multi->push_cb != NULL; 10613498266Sopenharmony_ci 10713498266Sopenharmony_ci return 3; 10813498266Sopenharmony_ci} 10913498266Sopenharmony_ci 11013498266Sopenharmony_cistatic ssize_t populate_binsettings(uint8_t *binsettings, 11113498266Sopenharmony_ci struct Curl_easy *data) 11213498266Sopenharmony_ci{ 11313498266Sopenharmony_ci nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; 11413498266Sopenharmony_ci int ivlen; 11513498266Sopenharmony_ci 11613498266Sopenharmony_ci ivlen = populate_settings(iv, data); 11713498266Sopenharmony_ci /* this returns number of bytes it wrote or a negative number on error. */ 11813498266Sopenharmony_ci return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN, 11913498266Sopenharmony_ci iv, ivlen); 12013498266Sopenharmony_ci} 12113498266Sopenharmony_ci 12213498266Sopenharmony_cistruct cf_h2_ctx { 12313498266Sopenharmony_ci nghttp2_session *h2; 12413498266Sopenharmony_ci uint32_t max_concurrent_streams; 12513498266Sopenharmony_ci /* The easy handle used in the current filter call, cleared at return */ 12613498266Sopenharmony_ci struct cf_call_data call_data; 12713498266Sopenharmony_ci 12813498266Sopenharmony_ci struct bufq inbufq; /* network input */ 12913498266Sopenharmony_ci struct bufq outbufq; /* network output */ 13013498266Sopenharmony_ci struct bufc_pool stream_bufcp; /* spares for stream buffers */ 13113498266Sopenharmony_ci 13213498266Sopenharmony_ci size_t drain_total; /* sum of all stream's UrlState drain */ 13313498266Sopenharmony_ci int32_t goaway_error; 13413498266Sopenharmony_ci int32_t last_stream_id; 13513498266Sopenharmony_ci BIT(conn_closed); 13613498266Sopenharmony_ci BIT(goaway); 13713498266Sopenharmony_ci BIT(enable_push); 13813498266Sopenharmony_ci BIT(nw_out_blocked); 13913498266Sopenharmony_ci}; 14013498266Sopenharmony_ci 14113498266Sopenharmony_ci/* How to access `call_data` from a cf_h2 filter */ 14213498266Sopenharmony_ci#undef CF_CTX_CALL_DATA 14313498266Sopenharmony_ci#define CF_CTX_CALL_DATA(cf) \ 14413498266Sopenharmony_ci ((struct cf_h2_ctx *)(cf)->ctx)->call_data 14513498266Sopenharmony_ci 14613498266Sopenharmony_cistatic void cf_h2_ctx_clear(struct cf_h2_ctx *ctx) 14713498266Sopenharmony_ci{ 14813498266Sopenharmony_ci struct cf_call_data save = ctx->call_data; 14913498266Sopenharmony_ci 15013498266Sopenharmony_ci if(ctx->h2) { 15113498266Sopenharmony_ci nghttp2_session_del(ctx->h2); 15213498266Sopenharmony_ci } 15313498266Sopenharmony_ci Curl_bufq_free(&ctx->inbufq); 15413498266Sopenharmony_ci Curl_bufq_free(&ctx->outbufq); 15513498266Sopenharmony_ci Curl_bufcp_free(&ctx->stream_bufcp); 15613498266Sopenharmony_ci memset(ctx, 0, sizeof(*ctx)); 15713498266Sopenharmony_ci ctx->call_data = save; 15813498266Sopenharmony_ci} 15913498266Sopenharmony_ci 16013498266Sopenharmony_cistatic void cf_h2_ctx_free(struct cf_h2_ctx *ctx) 16113498266Sopenharmony_ci{ 16213498266Sopenharmony_ci if(ctx) { 16313498266Sopenharmony_ci cf_h2_ctx_clear(ctx); 16413498266Sopenharmony_ci free(ctx); 16513498266Sopenharmony_ci } 16613498266Sopenharmony_ci} 16713498266Sopenharmony_ci 16813498266Sopenharmony_cistatic CURLcode h2_progress_egress(struct Curl_cfilter *cf, 16913498266Sopenharmony_ci struct Curl_easy *data); 17013498266Sopenharmony_ci 17113498266Sopenharmony_ci/** 17213498266Sopenharmony_ci * All about the H3 internals of a stream 17313498266Sopenharmony_ci */ 17413498266Sopenharmony_cistruct stream_ctx { 17513498266Sopenharmony_ci /*********** for HTTP/2 we store stream-local data here *************/ 17613498266Sopenharmony_ci int32_t id; /* HTTP/2 protocol identifier for stream */ 17713498266Sopenharmony_ci struct bufq recvbuf; /* response buffer */ 17813498266Sopenharmony_ci struct bufq sendbuf; /* request buffer */ 17913498266Sopenharmony_ci struct h1_req_parser h1; /* parsing the request */ 18013498266Sopenharmony_ci struct dynhds resp_trailers; /* response trailer fields */ 18113498266Sopenharmony_ci size_t resp_hds_len; /* amount of response header bytes in recvbuf */ 18213498266Sopenharmony_ci size_t upload_blocked_len; 18313498266Sopenharmony_ci curl_off_t upload_left; /* number of request bytes left to upload */ 18413498266Sopenharmony_ci 18513498266Sopenharmony_ci char **push_headers; /* allocated array */ 18613498266Sopenharmony_ci size_t push_headers_used; /* number of entries filled in */ 18713498266Sopenharmony_ci size_t push_headers_alloc; /* number of entries allocated */ 18813498266Sopenharmony_ci 18913498266Sopenharmony_ci int status_code; /* HTTP response status code */ 19013498266Sopenharmony_ci uint32_t error; /* stream error code */ 19113498266Sopenharmony_ci uint32_t local_window_size; /* the local recv window size */ 19213498266Sopenharmony_ci bool resp_hds_complete; /* we have a complete, final response */ 19313498266Sopenharmony_ci bool closed; /* TRUE on stream close */ 19413498266Sopenharmony_ci bool reset; /* TRUE on stream reset */ 19513498266Sopenharmony_ci bool close_handled; /* TRUE if stream closure is handled by libcurl */ 19613498266Sopenharmony_ci bool bodystarted; 19713498266Sopenharmony_ci bool send_closed; /* transfer is done sending, we might have still 19813498266Sopenharmony_ci buffered data in stream->sendbuf to upload. */ 19913498266Sopenharmony_ci}; 20013498266Sopenharmony_ci 20113498266Sopenharmony_ci#define H2_STREAM_CTX(d) ((struct stream_ctx *)(((d) && (d)->req.p.http)? \ 20213498266Sopenharmony_ci ((struct HTTP *)(d)->req.p.http)->h2_ctx \ 20313498266Sopenharmony_ci : NULL)) 20413498266Sopenharmony_ci#define H2_STREAM_LCTX(d) ((struct HTTP *)(d)->req.p.http)->h2_ctx 20513498266Sopenharmony_ci#define H2_STREAM_ID(d) (H2_STREAM_CTX(d)? \ 20613498266Sopenharmony_ci H2_STREAM_CTX(d)->id : -2) 20713498266Sopenharmony_ci 20813498266Sopenharmony_ci/* 20913498266Sopenharmony_ci * Mark this transfer to get "drained". 21013498266Sopenharmony_ci */ 21113498266Sopenharmony_cistatic void drain_stream(struct Curl_cfilter *cf, 21213498266Sopenharmony_ci struct Curl_easy *data, 21313498266Sopenharmony_ci struct stream_ctx *stream) 21413498266Sopenharmony_ci{ 21513498266Sopenharmony_ci unsigned char bits; 21613498266Sopenharmony_ci 21713498266Sopenharmony_ci (void)cf; 21813498266Sopenharmony_ci bits = CURL_CSELECT_IN; 21913498266Sopenharmony_ci if(!stream->send_closed && 22013498266Sopenharmony_ci (stream->upload_left || stream->upload_blocked_len)) 22113498266Sopenharmony_ci bits |= CURL_CSELECT_OUT; 22213498266Sopenharmony_ci if(data->state.select_bits != bits) { 22313498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] DRAIN select_bits=%x", 22413498266Sopenharmony_ci stream->id, bits); 22513498266Sopenharmony_ci data->state.select_bits = bits; 22613498266Sopenharmony_ci Curl_expire(data, 0, EXPIRE_RUN_NOW); 22713498266Sopenharmony_ci } 22813498266Sopenharmony_ci} 22913498266Sopenharmony_ci 23013498266Sopenharmony_cistatic CURLcode http2_data_setup(struct Curl_cfilter *cf, 23113498266Sopenharmony_ci struct Curl_easy *data, 23213498266Sopenharmony_ci struct stream_ctx **pstream) 23313498266Sopenharmony_ci{ 23413498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 23513498266Sopenharmony_ci struct stream_ctx *stream; 23613498266Sopenharmony_ci 23713498266Sopenharmony_ci (void)cf; 23813498266Sopenharmony_ci DEBUGASSERT(data); 23913498266Sopenharmony_ci if(!data->req.p.http) { 24013498266Sopenharmony_ci failf(data, "initialization failure, transfer not http initialized"); 24113498266Sopenharmony_ci return CURLE_FAILED_INIT; 24213498266Sopenharmony_ci } 24313498266Sopenharmony_ci stream = H2_STREAM_CTX(data); 24413498266Sopenharmony_ci if(stream) { 24513498266Sopenharmony_ci *pstream = stream; 24613498266Sopenharmony_ci return CURLE_OK; 24713498266Sopenharmony_ci } 24813498266Sopenharmony_ci 24913498266Sopenharmony_ci stream = calloc(1, sizeof(*stream)); 25013498266Sopenharmony_ci if(!stream) 25113498266Sopenharmony_ci return CURLE_OUT_OF_MEMORY; 25213498266Sopenharmony_ci 25313498266Sopenharmony_ci stream->id = -1; 25413498266Sopenharmony_ci Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp, 25513498266Sopenharmony_ci H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE); 25613498266Sopenharmony_ci Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp, 25713498266Sopenharmony_ci H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT); 25813498266Sopenharmony_ci Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN); 25913498266Sopenharmony_ci Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST); 26013498266Sopenharmony_ci stream->resp_hds_len = 0; 26113498266Sopenharmony_ci stream->bodystarted = FALSE; 26213498266Sopenharmony_ci stream->status_code = -1; 26313498266Sopenharmony_ci stream->closed = FALSE; 26413498266Sopenharmony_ci stream->close_handled = FALSE; 26513498266Sopenharmony_ci stream->error = NGHTTP2_NO_ERROR; 26613498266Sopenharmony_ci stream->local_window_size = H2_STREAM_WINDOW_SIZE; 26713498266Sopenharmony_ci stream->upload_left = 0; 26813498266Sopenharmony_ci 26913498266Sopenharmony_ci H2_STREAM_LCTX(data) = stream; 27013498266Sopenharmony_ci *pstream = stream; 27113498266Sopenharmony_ci return CURLE_OK; 27213498266Sopenharmony_ci} 27313498266Sopenharmony_ci 27413498266Sopenharmony_cistatic void free_push_headers(struct stream_ctx *stream) 27513498266Sopenharmony_ci{ 27613498266Sopenharmony_ci size_t i; 27713498266Sopenharmony_ci for(i = 0; i<stream->push_headers_used; i++) 27813498266Sopenharmony_ci free(stream->push_headers[i]); 27913498266Sopenharmony_ci Curl_safefree(stream->push_headers); 28013498266Sopenharmony_ci stream->push_headers_used = 0; 28113498266Sopenharmony_ci} 28213498266Sopenharmony_ci 28313498266Sopenharmony_cistatic void http2_data_done(struct Curl_cfilter *cf, 28413498266Sopenharmony_ci struct Curl_easy *data, bool premature) 28513498266Sopenharmony_ci{ 28613498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 28713498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 28813498266Sopenharmony_ci 28913498266Sopenharmony_ci DEBUGASSERT(ctx); 29013498266Sopenharmony_ci (void)premature; 29113498266Sopenharmony_ci if(!stream) 29213498266Sopenharmony_ci return; 29313498266Sopenharmony_ci 29413498266Sopenharmony_ci if(ctx->h2) { 29513498266Sopenharmony_ci bool flush_egress = FALSE; 29613498266Sopenharmony_ci /* returns error if stream not known, which is fine here */ 29713498266Sopenharmony_ci (void)nghttp2_session_set_stream_user_data(ctx->h2, stream->id, NULL); 29813498266Sopenharmony_ci 29913498266Sopenharmony_ci if(!stream->closed && stream->id > 0) { 30013498266Sopenharmony_ci /* RST_STREAM */ 30113498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] premature DATA_DONE, RST stream", 30213498266Sopenharmony_ci stream->id); 30313498266Sopenharmony_ci stream->closed = TRUE; 30413498266Sopenharmony_ci stream->reset = TRUE; 30513498266Sopenharmony_ci stream->send_closed = TRUE; 30613498266Sopenharmony_ci nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, 30713498266Sopenharmony_ci stream->id, NGHTTP2_STREAM_CLOSED); 30813498266Sopenharmony_ci flush_egress = TRUE; 30913498266Sopenharmony_ci } 31013498266Sopenharmony_ci if(!Curl_bufq_is_empty(&stream->recvbuf)) { 31113498266Sopenharmony_ci /* Anything in the recvbuf is still being counted 31213498266Sopenharmony_ci * in stream and connection window flow control. Need 31313498266Sopenharmony_ci * to free that space or the connection window might get 31413498266Sopenharmony_ci * exhausted eventually. */ 31513498266Sopenharmony_ci nghttp2_session_consume(ctx->h2, stream->id, 31613498266Sopenharmony_ci Curl_bufq_len(&stream->recvbuf)); 31713498266Sopenharmony_ci /* give WINDOW_UPATE a chance to be sent, but ignore any error */ 31813498266Sopenharmony_ci flush_egress = TRUE; 31913498266Sopenharmony_ci } 32013498266Sopenharmony_ci 32113498266Sopenharmony_ci if(flush_egress) 32213498266Sopenharmony_ci nghttp2_session_send(ctx->h2); 32313498266Sopenharmony_ci } 32413498266Sopenharmony_ci 32513498266Sopenharmony_ci Curl_bufq_free(&stream->sendbuf); 32613498266Sopenharmony_ci Curl_bufq_free(&stream->recvbuf); 32713498266Sopenharmony_ci Curl_h1_req_parse_free(&stream->h1); 32813498266Sopenharmony_ci Curl_dynhds_free(&stream->resp_trailers); 32913498266Sopenharmony_ci free_push_headers(stream); 33013498266Sopenharmony_ci free(stream); 33113498266Sopenharmony_ci H2_STREAM_LCTX(data) = NULL; 33213498266Sopenharmony_ci} 33313498266Sopenharmony_ci 33413498266Sopenharmony_cistatic int h2_client_new(struct Curl_cfilter *cf, 33513498266Sopenharmony_ci nghttp2_session_callbacks *cbs) 33613498266Sopenharmony_ci{ 33713498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 33813498266Sopenharmony_ci nghttp2_option *o; 33913498266Sopenharmony_ci 34013498266Sopenharmony_ci int rc = nghttp2_option_new(&o); 34113498266Sopenharmony_ci if(rc) 34213498266Sopenharmony_ci return rc; 34313498266Sopenharmony_ci /* We handle window updates ourself to enforce buffer limits */ 34413498266Sopenharmony_ci nghttp2_option_set_no_auto_window_update(o, 1); 34513498266Sopenharmony_ci#if NGHTTP2_VERSION_NUM >= 0x013200 34613498266Sopenharmony_ci /* with 1.50.0 */ 34713498266Sopenharmony_ci /* turn off RFC 9113 leading and trailing white spaces validation against 34813498266Sopenharmony_ci HTTP field value. */ 34913498266Sopenharmony_ci nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1); 35013498266Sopenharmony_ci#endif 35113498266Sopenharmony_ci rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o); 35213498266Sopenharmony_ci nghttp2_option_del(o); 35313498266Sopenharmony_ci return rc; 35413498266Sopenharmony_ci} 35513498266Sopenharmony_ci 35613498266Sopenharmony_cistatic ssize_t nw_in_reader(void *reader_ctx, 35713498266Sopenharmony_ci unsigned char *buf, size_t buflen, 35813498266Sopenharmony_ci CURLcode *err) 35913498266Sopenharmony_ci{ 36013498266Sopenharmony_ci struct Curl_cfilter *cf = reader_ctx; 36113498266Sopenharmony_ci struct Curl_easy *data = CF_DATA_CURRENT(cf); 36213498266Sopenharmony_ci 36313498266Sopenharmony_ci return Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err); 36413498266Sopenharmony_ci} 36513498266Sopenharmony_ci 36613498266Sopenharmony_cistatic ssize_t nw_out_writer(void *writer_ctx, 36713498266Sopenharmony_ci const unsigned char *buf, size_t buflen, 36813498266Sopenharmony_ci CURLcode *err) 36913498266Sopenharmony_ci{ 37013498266Sopenharmony_ci struct Curl_cfilter *cf = writer_ctx; 37113498266Sopenharmony_ci struct Curl_easy *data = CF_DATA_CURRENT(cf); 37213498266Sopenharmony_ci 37313498266Sopenharmony_ci if(data) { 37413498266Sopenharmony_ci ssize_t nwritten = Curl_conn_cf_send(cf->next, data, 37513498266Sopenharmony_ci (const char *)buf, buflen, err); 37613498266Sopenharmony_ci if(nwritten > 0) 37713498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] egress: wrote %zd bytes", nwritten); 37813498266Sopenharmony_ci return nwritten; 37913498266Sopenharmony_ci } 38013498266Sopenharmony_ci return 0; 38113498266Sopenharmony_ci} 38213498266Sopenharmony_ci 38313498266Sopenharmony_cistatic ssize_t send_callback(nghttp2_session *h2, 38413498266Sopenharmony_ci const uint8_t *mem, size_t length, int flags, 38513498266Sopenharmony_ci void *userp); 38613498266Sopenharmony_cistatic int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, 38713498266Sopenharmony_ci void *userp); 38813498266Sopenharmony_ci#ifndef CURL_DISABLE_VERBOSE_STRINGS 38913498266Sopenharmony_cistatic int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, 39013498266Sopenharmony_ci void *userp); 39113498266Sopenharmony_ci#endif 39213498266Sopenharmony_cistatic int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, 39313498266Sopenharmony_ci int32_t stream_id, 39413498266Sopenharmony_ci const uint8_t *mem, size_t len, void *userp); 39513498266Sopenharmony_cistatic int on_stream_close(nghttp2_session *session, int32_t stream_id, 39613498266Sopenharmony_ci uint32_t error_code, void *userp); 39713498266Sopenharmony_cistatic int on_begin_headers(nghttp2_session *session, 39813498266Sopenharmony_ci const nghttp2_frame *frame, void *userp); 39913498266Sopenharmony_cistatic int on_header(nghttp2_session *session, const nghttp2_frame *frame, 40013498266Sopenharmony_ci const uint8_t *name, size_t namelen, 40113498266Sopenharmony_ci const uint8_t *value, size_t valuelen, 40213498266Sopenharmony_ci uint8_t flags, 40313498266Sopenharmony_ci void *userp); 40413498266Sopenharmony_cistatic int error_callback(nghttp2_session *session, const char *msg, 40513498266Sopenharmony_ci size_t len, void *userp); 40613498266Sopenharmony_ci 40713498266Sopenharmony_ci/* 40813498266Sopenharmony_ci * Initialize the cfilter context 40913498266Sopenharmony_ci */ 41013498266Sopenharmony_cistatic CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf, 41113498266Sopenharmony_ci struct Curl_easy *data, 41213498266Sopenharmony_ci bool via_h1_upgrade) 41313498266Sopenharmony_ci{ 41413498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 41513498266Sopenharmony_ci struct stream_ctx *stream; 41613498266Sopenharmony_ci CURLcode result = CURLE_OUT_OF_MEMORY; 41713498266Sopenharmony_ci int rc; 41813498266Sopenharmony_ci nghttp2_session_callbacks *cbs = NULL; 41913498266Sopenharmony_ci 42013498266Sopenharmony_ci DEBUGASSERT(!ctx->h2); 42113498266Sopenharmony_ci Curl_bufcp_init(&ctx->stream_bufcp, H2_CHUNK_SIZE, H2_STREAM_POOL_SPARES); 42213498266Sopenharmony_ci Curl_bufq_initp(&ctx->inbufq, &ctx->stream_bufcp, H2_NW_RECV_CHUNKS, 0); 42313498266Sopenharmony_ci Curl_bufq_initp(&ctx->outbufq, &ctx->stream_bufcp, H2_NW_SEND_CHUNKS, 0); 42413498266Sopenharmony_ci ctx->last_stream_id = 2147483647; 42513498266Sopenharmony_ci 42613498266Sopenharmony_ci rc = nghttp2_session_callbacks_new(&cbs); 42713498266Sopenharmony_ci if(rc) { 42813498266Sopenharmony_ci failf(data, "Couldn't initialize nghttp2 callbacks"); 42913498266Sopenharmony_ci goto out; 43013498266Sopenharmony_ci } 43113498266Sopenharmony_ci 43213498266Sopenharmony_ci nghttp2_session_callbacks_set_send_callback(cbs, send_callback); 43313498266Sopenharmony_ci nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv); 43413498266Sopenharmony_ci#ifndef CURL_DISABLE_VERBOSE_STRINGS 43513498266Sopenharmony_ci nghttp2_session_callbacks_set_on_frame_send_callback(cbs, on_frame_send); 43613498266Sopenharmony_ci#endif 43713498266Sopenharmony_ci nghttp2_session_callbacks_set_on_data_chunk_recv_callback( 43813498266Sopenharmony_ci cbs, on_data_chunk_recv); 43913498266Sopenharmony_ci nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close); 44013498266Sopenharmony_ci nghttp2_session_callbacks_set_on_begin_headers_callback( 44113498266Sopenharmony_ci cbs, on_begin_headers); 44213498266Sopenharmony_ci nghttp2_session_callbacks_set_on_header_callback(cbs, on_header); 44313498266Sopenharmony_ci nghttp2_session_callbacks_set_error_callback(cbs, error_callback); 44413498266Sopenharmony_ci 44513498266Sopenharmony_ci /* The nghttp2 session is not yet setup, do it */ 44613498266Sopenharmony_ci rc = h2_client_new(cf, cbs); 44713498266Sopenharmony_ci if(rc) { 44813498266Sopenharmony_ci failf(data, "Couldn't initialize nghttp2"); 44913498266Sopenharmony_ci goto out; 45013498266Sopenharmony_ci } 45113498266Sopenharmony_ci ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS; 45213498266Sopenharmony_ci 45313498266Sopenharmony_ci if(via_h1_upgrade) { 45413498266Sopenharmony_ci /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted 45513498266Sopenharmony_ci * in the H1 request and we upgrade from there. This stream 45613498266Sopenharmony_ci * is opened implicitly as #1. */ 45713498266Sopenharmony_ci uint8_t binsettings[H2_BINSETTINGS_LEN]; 45813498266Sopenharmony_ci ssize_t binlen; /* length of the binsettings data */ 45913498266Sopenharmony_ci 46013498266Sopenharmony_ci binlen = populate_binsettings(binsettings, data); 46113498266Sopenharmony_ci if(binlen <= 0) { 46213498266Sopenharmony_ci failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); 46313498266Sopenharmony_ci result = CURLE_FAILED_INIT; 46413498266Sopenharmony_ci goto out; 46513498266Sopenharmony_ci } 46613498266Sopenharmony_ci 46713498266Sopenharmony_ci result = http2_data_setup(cf, data, &stream); 46813498266Sopenharmony_ci if(result) 46913498266Sopenharmony_ci goto out; 47013498266Sopenharmony_ci DEBUGASSERT(stream); 47113498266Sopenharmony_ci stream->id = 1; 47213498266Sopenharmony_ci /* queue SETTINGS frame (again) */ 47313498266Sopenharmony_ci rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen, 47413498266Sopenharmony_ci data->state.httpreq == HTTPREQ_HEAD, 47513498266Sopenharmony_ci NULL); 47613498266Sopenharmony_ci if(rc) { 47713498266Sopenharmony_ci failf(data, "nghttp2_session_upgrade2() failed: %s(%d)", 47813498266Sopenharmony_ci nghttp2_strerror(rc), rc); 47913498266Sopenharmony_ci result = CURLE_HTTP2; 48013498266Sopenharmony_ci goto out; 48113498266Sopenharmony_ci } 48213498266Sopenharmony_ci 48313498266Sopenharmony_ci rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->id, 48413498266Sopenharmony_ci data); 48513498266Sopenharmony_ci if(rc) { 48613498266Sopenharmony_ci infof(data, "http/2: failed to set user_data for stream %u", 48713498266Sopenharmony_ci stream->id); 48813498266Sopenharmony_ci DEBUGASSERT(0); 48913498266Sopenharmony_ci } 49013498266Sopenharmony_ci CURL_TRC_CF(data, cf, "created session via Upgrade"); 49113498266Sopenharmony_ci } 49213498266Sopenharmony_ci else { 49313498266Sopenharmony_ci nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN]; 49413498266Sopenharmony_ci int ivlen; 49513498266Sopenharmony_ci 49613498266Sopenharmony_ci ivlen = populate_settings(iv, data); 49713498266Sopenharmony_ci rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, 49813498266Sopenharmony_ci iv, ivlen); 49913498266Sopenharmony_ci if(rc) { 50013498266Sopenharmony_ci failf(data, "nghttp2_submit_settings() failed: %s(%d)", 50113498266Sopenharmony_ci nghttp2_strerror(rc), rc); 50213498266Sopenharmony_ci result = CURLE_HTTP2; 50313498266Sopenharmony_ci goto out; 50413498266Sopenharmony_ci } 50513498266Sopenharmony_ci } 50613498266Sopenharmony_ci 50713498266Sopenharmony_ci rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0, 50813498266Sopenharmony_ci HTTP2_HUGE_WINDOW_SIZE); 50913498266Sopenharmony_ci if(rc) { 51013498266Sopenharmony_ci failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", 51113498266Sopenharmony_ci nghttp2_strerror(rc), rc); 51213498266Sopenharmony_ci result = CURLE_HTTP2; 51313498266Sopenharmony_ci goto out; 51413498266Sopenharmony_ci } 51513498266Sopenharmony_ci 51613498266Sopenharmony_ci /* all set, traffic will be send on connect */ 51713498266Sopenharmony_ci result = CURLE_OK; 51813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] created h2 session%s", 51913498266Sopenharmony_ci via_h1_upgrade? " (via h1 upgrade)" : ""); 52013498266Sopenharmony_ci 52113498266Sopenharmony_ciout: 52213498266Sopenharmony_ci if(cbs) 52313498266Sopenharmony_ci nghttp2_session_callbacks_del(cbs); 52413498266Sopenharmony_ci return result; 52513498266Sopenharmony_ci} 52613498266Sopenharmony_ci 52713498266Sopenharmony_ci/* 52813498266Sopenharmony_ci * Returns nonzero if current HTTP/2 session should be closed. 52913498266Sopenharmony_ci */ 53013498266Sopenharmony_cistatic int should_close_session(struct cf_h2_ctx *ctx) 53113498266Sopenharmony_ci{ 53213498266Sopenharmony_ci return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) && 53313498266Sopenharmony_ci !nghttp2_session_want_write(ctx->h2); 53413498266Sopenharmony_ci} 53513498266Sopenharmony_ci 53613498266Sopenharmony_ci/* 53713498266Sopenharmony_ci * Processes pending input left in network input buffer. 53813498266Sopenharmony_ci * This function returns 0 if it succeeds, or -1 and error code will 53913498266Sopenharmony_ci * be assigned to *err. 54013498266Sopenharmony_ci */ 54113498266Sopenharmony_cistatic int h2_process_pending_input(struct Curl_cfilter *cf, 54213498266Sopenharmony_ci struct Curl_easy *data, 54313498266Sopenharmony_ci CURLcode *err) 54413498266Sopenharmony_ci{ 54513498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 54613498266Sopenharmony_ci const unsigned char *buf; 54713498266Sopenharmony_ci size_t blen; 54813498266Sopenharmony_ci ssize_t rv; 54913498266Sopenharmony_ci 55013498266Sopenharmony_ci while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) { 55113498266Sopenharmony_ci 55213498266Sopenharmony_ci rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen); 55313498266Sopenharmony_ci if(rv < 0) { 55413498266Sopenharmony_ci failf(data, 55513498266Sopenharmony_ci "process_pending_input: nghttp2_session_mem_recv() returned " 55613498266Sopenharmony_ci "%zd:%s", rv, nghttp2_strerror((int)rv)); 55713498266Sopenharmony_ci *err = CURLE_RECV_ERROR; 55813498266Sopenharmony_ci return -1; 55913498266Sopenharmony_ci } 56013498266Sopenharmony_ci Curl_bufq_skip(&ctx->inbufq, (size_t)rv); 56113498266Sopenharmony_ci if(Curl_bufq_is_empty(&ctx->inbufq)) { 56213498266Sopenharmony_ci break; 56313498266Sopenharmony_ci } 56413498266Sopenharmony_ci else { 56513498266Sopenharmony_ci CURL_TRC_CF(data, cf, "process_pending_input: %zu bytes left " 56613498266Sopenharmony_ci "in connection buffer", Curl_bufq_len(&ctx->inbufq)); 56713498266Sopenharmony_ci } 56813498266Sopenharmony_ci } 56913498266Sopenharmony_ci 57013498266Sopenharmony_ci if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { 57113498266Sopenharmony_ci /* No more requests are allowed in the current session, so 57213498266Sopenharmony_ci the connection may not be reused. This is set when a 57313498266Sopenharmony_ci GOAWAY frame has been received or when the limit of stream 57413498266Sopenharmony_ci identifiers has been reached. */ 57513498266Sopenharmony_ci connclose(cf->conn, "http/2: No new requests allowed"); 57613498266Sopenharmony_ci } 57713498266Sopenharmony_ci 57813498266Sopenharmony_ci return 0; 57913498266Sopenharmony_ci} 58013498266Sopenharmony_ci 58113498266Sopenharmony_ci/* 58213498266Sopenharmony_ci * The server may send us data at any point (e.g. PING frames). Therefore, 58313498266Sopenharmony_ci * we cannot assume that an HTTP/2 socket is dead just because it is readable. 58413498266Sopenharmony_ci * 58513498266Sopenharmony_ci * Check the lower filters first and, if successful, peek at the socket 58613498266Sopenharmony_ci * and distinguish between closed and data. 58713498266Sopenharmony_ci */ 58813498266Sopenharmony_cistatic bool http2_connisalive(struct Curl_cfilter *cf, struct Curl_easy *data, 58913498266Sopenharmony_ci bool *input_pending) 59013498266Sopenharmony_ci{ 59113498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 59213498266Sopenharmony_ci bool alive = TRUE; 59313498266Sopenharmony_ci 59413498266Sopenharmony_ci *input_pending = FALSE; 59513498266Sopenharmony_ci if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending)) 59613498266Sopenharmony_ci return FALSE; 59713498266Sopenharmony_ci 59813498266Sopenharmony_ci if(*input_pending) { 59913498266Sopenharmony_ci /* This happens before we've sent off a request and the connection is 60013498266Sopenharmony_ci not in use by any other transfer, there shouldn't be any data here, 60113498266Sopenharmony_ci only "protocol frames" */ 60213498266Sopenharmony_ci CURLcode result; 60313498266Sopenharmony_ci ssize_t nread = -1; 60413498266Sopenharmony_ci 60513498266Sopenharmony_ci *input_pending = FALSE; 60613498266Sopenharmony_ci nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); 60713498266Sopenharmony_ci if(nread != -1) { 60813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "%zd bytes stray data read before trying " 60913498266Sopenharmony_ci "h2 connection", nread); 61013498266Sopenharmony_ci if(h2_process_pending_input(cf, data, &result) < 0) 61113498266Sopenharmony_ci /* immediate error, considered dead */ 61213498266Sopenharmony_ci alive = FALSE; 61313498266Sopenharmony_ci else { 61413498266Sopenharmony_ci alive = !should_close_session(ctx); 61513498266Sopenharmony_ci } 61613498266Sopenharmony_ci } 61713498266Sopenharmony_ci else if(result != CURLE_AGAIN) { 61813498266Sopenharmony_ci /* the read failed so let's say this is dead anyway */ 61913498266Sopenharmony_ci alive = FALSE; 62013498266Sopenharmony_ci } 62113498266Sopenharmony_ci } 62213498266Sopenharmony_ci 62313498266Sopenharmony_ci return alive; 62413498266Sopenharmony_ci} 62513498266Sopenharmony_ci 62613498266Sopenharmony_cistatic CURLcode http2_send_ping(struct Curl_cfilter *cf, 62713498266Sopenharmony_ci struct Curl_easy *data) 62813498266Sopenharmony_ci{ 62913498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 63013498266Sopenharmony_ci int rc; 63113498266Sopenharmony_ci 63213498266Sopenharmony_ci rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL); 63313498266Sopenharmony_ci if(rc) { 63413498266Sopenharmony_ci failf(data, "nghttp2_submit_ping() failed: %s(%d)", 63513498266Sopenharmony_ci nghttp2_strerror(rc), rc); 63613498266Sopenharmony_ci return CURLE_HTTP2; 63713498266Sopenharmony_ci } 63813498266Sopenharmony_ci 63913498266Sopenharmony_ci rc = nghttp2_session_send(ctx->h2); 64013498266Sopenharmony_ci if(rc) { 64113498266Sopenharmony_ci failf(data, "nghttp2_session_send() failed: %s(%d)", 64213498266Sopenharmony_ci nghttp2_strerror(rc), rc); 64313498266Sopenharmony_ci return CURLE_SEND_ERROR; 64413498266Sopenharmony_ci } 64513498266Sopenharmony_ci return CURLE_OK; 64613498266Sopenharmony_ci} 64713498266Sopenharmony_ci 64813498266Sopenharmony_ci/* 64913498266Sopenharmony_ci * Store nghttp2 version info in this buffer. 65013498266Sopenharmony_ci */ 65113498266Sopenharmony_civoid Curl_http2_ver(char *p, size_t len) 65213498266Sopenharmony_ci{ 65313498266Sopenharmony_ci nghttp2_info *h2 = nghttp2_version(0); 65413498266Sopenharmony_ci (void)msnprintf(p, len, "nghttp2/%s", h2->version_str); 65513498266Sopenharmony_ci} 65613498266Sopenharmony_ci 65713498266Sopenharmony_cistatic CURLcode nw_out_flush(struct Curl_cfilter *cf, 65813498266Sopenharmony_ci struct Curl_easy *data) 65913498266Sopenharmony_ci{ 66013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 66113498266Sopenharmony_ci ssize_t nwritten; 66213498266Sopenharmony_ci CURLcode result; 66313498266Sopenharmony_ci 66413498266Sopenharmony_ci (void)data; 66513498266Sopenharmony_ci if(Curl_bufq_is_empty(&ctx->outbufq)) 66613498266Sopenharmony_ci return CURLE_OK; 66713498266Sopenharmony_ci 66813498266Sopenharmony_ci nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result); 66913498266Sopenharmony_ci if(nwritten < 0) { 67013498266Sopenharmony_ci if(result == CURLE_AGAIN) { 67113498266Sopenharmony_ci CURL_TRC_CF(data, cf, "flush nw send buffer(%zu) -> EAGAIN", 67213498266Sopenharmony_ci Curl_bufq_len(&ctx->outbufq)); 67313498266Sopenharmony_ci ctx->nw_out_blocked = 1; 67413498266Sopenharmony_ci } 67513498266Sopenharmony_ci return result; 67613498266Sopenharmony_ci } 67713498266Sopenharmony_ci return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN; 67813498266Sopenharmony_ci} 67913498266Sopenharmony_ci 68013498266Sopenharmony_ci/* 68113498266Sopenharmony_ci * The implementation of nghttp2_send_callback type. Here we write |data| with 68213498266Sopenharmony_ci * size |length| to the network and return the number of bytes actually 68313498266Sopenharmony_ci * written. See the documentation of nghttp2_send_callback for the details. 68413498266Sopenharmony_ci */ 68513498266Sopenharmony_cistatic ssize_t send_callback(nghttp2_session *h2, 68613498266Sopenharmony_ci const uint8_t *buf, size_t blen, int flags, 68713498266Sopenharmony_ci void *userp) 68813498266Sopenharmony_ci{ 68913498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 69013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 69113498266Sopenharmony_ci struct Curl_easy *data = CF_DATA_CURRENT(cf); 69213498266Sopenharmony_ci ssize_t nwritten; 69313498266Sopenharmony_ci CURLcode result = CURLE_OK; 69413498266Sopenharmony_ci 69513498266Sopenharmony_ci (void)h2; 69613498266Sopenharmony_ci (void)flags; 69713498266Sopenharmony_ci DEBUGASSERT(data); 69813498266Sopenharmony_ci 69913498266Sopenharmony_ci nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen, 70013498266Sopenharmony_ci nw_out_writer, cf, &result); 70113498266Sopenharmony_ci if(nwritten < 0) { 70213498266Sopenharmony_ci if(result == CURLE_AGAIN) { 70313498266Sopenharmony_ci ctx->nw_out_blocked = 1; 70413498266Sopenharmony_ci return NGHTTP2_ERR_WOULDBLOCK; 70513498266Sopenharmony_ci } 70613498266Sopenharmony_ci failf(data, "Failed sending HTTP2 data"); 70713498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 70813498266Sopenharmony_ci } 70913498266Sopenharmony_ci 71013498266Sopenharmony_ci if(!nwritten) { 71113498266Sopenharmony_ci ctx->nw_out_blocked = 1; 71213498266Sopenharmony_ci return NGHTTP2_ERR_WOULDBLOCK; 71313498266Sopenharmony_ci } 71413498266Sopenharmony_ci return nwritten; 71513498266Sopenharmony_ci} 71613498266Sopenharmony_ci 71713498266Sopenharmony_ci 71813498266Sopenharmony_ci/* We pass a pointer to this struct in the push callback, but the contents of 71913498266Sopenharmony_ci the struct are hidden from the user. */ 72013498266Sopenharmony_cistruct curl_pushheaders { 72113498266Sopenharmony_ci struct Curl_easy *data; 72213498266Sopenharmony_ci const nghttp2_push_promise *frame; 72313498266Sopenharmony_ci}; 72413498266Sopenharmony_ci 72513498266Sopenharmony_ci/* 72613498266Sopenharmony_ci * push header access function. Only to be used from within the push callback 72713498266Sopenharmony_ci */ 72813498266Sopenharmony_cichar *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) 72913498266Sopenharmony_ci{ 73013498266Sopenharmony_ci /* Verify that we got a good easy handle in the push header struct, mostly to 73113498266Sopenharmony_ci detect rubbish input fast(er). */ 73213498266Sopenharmony_ci if(!h || !GOOD_EASY_HANDLE(h->data)) 73313498266Sopenharmony_ci return NULL; 73413498266Sopenharmony_ci else { 73513498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(h->data); 73613498266Sopenharmony_ci if(stream && num < stream->push_headers_used) 73713498266Sopenharmony_ci return stream->push_headers[num]; 73813498266Sopenharmony_ci } 73913498266Sopenharmony_ci return NULL; 74013498266Sopenharmony_ci} 74113498266Sopenharmony_ci 74213498266Sopenharmony_ci/* 74313498266Sopenharmony_ci * push header access function. Only to be used from within the push callback 74413498266Sopenharmony_ci */ 74513498266Sopenharmony_cichar *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) 74613498266Sopenharmony_ci{ 74713498266Sopenharmony_ci struct stream_ctx *stream; 74813498266Sopenharmony_ci size_t len; 74913498266Sopenharmony_ci size_t i; 75013498266Sopenharmony_ci /* Verify that we got a good easy handle in the push header struct, 75113498266Sopenharmony_ci mostly to detect rubbish input fast(er). Also empty header name 75213498266Sopenharmony_ci is just a rubbish too. We have to allow ":" at the beginning of 75313498266Sopenharmony_ci the header, but header == ":" must be rejected. If we have ':' in 75413498266Sopenharmony_ci the middle of header, it could be matched in middle of the value, 75513498266Sopenharmony_ci this is because we do prefix match.*/ 75613498266Sopenharmony_ci if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] || 75713498266Sopenharmony_ci !strcmp(header, ":") || strchr(header + 1, ':')) 75813498266Sopenharmony_ci return NULL; 75913498266Sopenharmony_ci 76013498266Sopenharmony_ci stream = H2_STREAM_CTX(h->data); 76113498266Sopenharmony_ci if(!stream) 76213498266Sopenharmony_ci return NULL; 76313498266Sopenharmony_ci 76413498266Sopenharmony_ci len = strlen(header); 76513498266Sopenharmony_ci for(i = 0; i<stream->push_headers_used; i++) { 76613498266Sopenharmony_ci if(!strncmp(header, stream->push_headers[i], len)) { 76713498266Sopenharmony_ci /* sub-match, make sure that it is followed by a colon */ 76813498266Sopenharmony_ci if(stream->push_headers[i][len] != ':') 76913498266Sopenharmony_ci continue; 77013498266Sopenharmony_ci return &stream->push_headers[i][len + 1]; 77113498266Sopenharmony_ci } 77213498266Sopenharmony_ci } 77313498266Sopenharmony_ci return NULL; 77413498266Sopenharmony_ci} 77513498266Sopenharmony_ci 77613498266Sopenharmony_cistatic struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf, 77713498266Sopenharmony_ci struct Curl_easy *data) 77813498266Sopenharmony_ci{ 77913498266Sopenharmony_ci struct Curl_easy *second = curl_easy_duphandle(data); 78013498266Sopenharmony_ci if(second) { 78113498266Sopenharmony_ci /* setup the request struct */ 78213498266Sopenharmony_ci struct HTTP *http = calloc(1, sizeof(struct HTTP)); 78313498266Sopenharmony_ci if(!http) { 78413498266Sopenharmony_ci (void)Curl_close(&second); 78513498266Sopenharmony_ci } 78613498266Sopenharmony_ci else { 78713498266Sopenharmony_ci struct stream_ctx *second_stream; 78813498266Sopenharmony_ci 78913498266Sopenharmony_ci second->req.p.http = http; 79013498266Sopenharmony_ci http2_data_setup(cf, second, &second_stream); 79113498266Sopenharmony_ci second->state.priority.weight = data->state.priority.weight; 79213498266Sopenharmony_ci } 79313498266Sopenharmony_ci } 79413498266Sopenharmony_ci return second; 79513498266Sopenharmony_ci} 79613498266Sopenharmony_ci 79713498266Sopenharmony_cistatic int set_transfer_url(struct Curl_easy *data, 79813498266Sopenharmony_ci struct curl_pushheaders *hp) 79913498266Sopenharmony_ci{ 80013498266Sopenharmony_ci const char *v; 80113498266Sopenharmony_ci CURLUcode uc; 80213498266Sopenharmony_ci char *url = NULL; 80313498266Sopenharmony_ci int rc = 0; 80413498266Sopenharmony_ci CURLU *u = curl_url(); 80513498266Sopenharmony_ci 80613498266Sopenharmony_ci if(!u) 80713498266Sopenharmony_ci return 5; 80813498266Sopenharmony_ci 80913498266Sopenharmony_ci v = curl_pushheader_byname(hp, HTTP_PSEUDO_SCHEME); 81013498266Sopenharmony_ci if(v) { 81113498266Sopenharmony_ci uc = curl_url_set(u, CURLUPART_SCHEME, v, 0); 81213498266Sopenharmony_ci if(uc) { 81313498266Sopenharmony_ci rc = 1; 81413498266Sopenharmony_ci goto fail; 81513498266Sopenharmony_ci } 81613498266Sopenharmony_ci } 81713498266Sopenharmony_ci 81813498266Sopenharmony_ci v = curl_pushheader_byname(hp, HTTP_PSEUDO_AUTHORITY); 81913498266Sopenharmony_ci if(v) { 82013498266Sopenharmony_ci uc = Curl_url_set_authority(u, v, CURLU_DISALLOW_USER); 82113498266Sopenharmony_ci if(uc) { 82213498266Sopenharmony_ci rc = 2; 82313498266Sopenharmony_ci goto fail; 82413498266Sopenharmony_ci } 82513498266Sopenharmony_ci } 82613498266Sopenharmony_ci 82713498266Sopenharmony_ci v = curl_pushheader_byname(hp, HTTP_PSEUDO_PATH); 82813498266Sopenharmony_ci if(v) { 82913498266Sopenharmony_ci uc = curl_url_set(u, CURLUPART_PATH, v, 0); 83013498266Sopenharmony_ci if(uc) { 83113498266Sopenharmony_ci rc = 3; 83213498266Sopenharmony_ci goto fail; 83313498266Sopenharmony_ci } 83413498266Sopenharmony_ci } 83513498266Sopenharmony_ci 83613498266Sopenharmony_ci uc = curl_url_get(u, CURLUPART_URL, &url, 0); 83713498266Sopenharmony_ci if(uc) 83813498266Sopenharmony_ci rc = 4; 83913498266Sopenharmony_cifail: 84013498266Sopenharmony_ci curl_url_cleanup(u); 84113498266Sopenharmony_ci if(rc) 84213498266Sopenharmony_ci return rc; 84313498266Sopenharmony_ci 84413498266Sopenharmony_ci if(data->state.url_alloc) 84513498266Sopenharmony_ci free(data->state.url); 84613498266Sopenharmony_ci data->state.url_alloc = TRUE; 84713498266Sopenharmony_ci data->state.url = url; 84813498266Sopenharmony_ci return 0; 84913498266Sopenharmony_ci} 85013498266Sopenharmony_ci 85113498266Sopenharmony_cistatic void discard_newhandle(struct Curl_cfilter *cf, 85213498266Sopenharmony_ci struct Curl_easy *newhandle) 85313498266Sopenharmony_ci{ 85413498266Sopenharmony_ci if(!newhandle->req.p.http) { 85513498266Sopenharmony_ci http2_data_done(cf, newhandle, TRUE); 85613498266Sopenharmony_ci newhandle->req.p.http = NULL; 85713498266Sopenharmony_ci } 85813498266Sopenharmony_ci (void)Curl_close(&newhandle); 85913498266Sopenharmony_ci} 86013498266Sopenharmony_ci 86113498266Sopenharmony_cistatic int push_promise(struct Curl_cfilter *cf, 86213498266Sopenharmony_ci struct Curl_easy *data, 86313498266Sopenharmony_ci const nghttp2_push_promise *frame) 86413498266Sopenharmony_ci{ 86513498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 86613498266Sopenharmony_ci int rv; /* one of the CURL_PUSH_* defines */ 86713498266Sopenharmony_ci 86813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] PUSH_PROMISE received", 86913498266Sopenharmony_ci frame->promised_stream_id); 87013498266Sopenharmony_ci if(data->multi->push_cb) { 87113498266Sopenharmony_ci struct stream_ctx *stream; 87213498266Sopenharmony_ci struct stream_ctx *newstream; 87313498266Sopenharmony_ci struct curl_pushheaders heads; 87413498266Sopenharmony_ci CURLMcode rc; 87513498266Sopenharmony_ci CURLcode result; 87613498266Sopenharmony_ci /* clone the parent */ 87713498266Sopenharmony_ci struct Curl_easy *newhandle = h2_duphandle(cf, data); 87813498266Sopenharmony_ci if(!newhandle) { 87913498266Sopenharmony_ci infof(data, "failed to duplicate handle"); 88013498266Sopenharmony_ci rv = CURL_PUSH_DENY; /* FAIL HARD */ 88113498266Sopenharmony_ci goto fail; 88213498266Sopenharmony_ci } 88313498266Sopenharmony_ci 88413498266Sopenharmony_ci heads.data = data; 88513498266Sopenharmony_ci heads.frame = frame; 88613498266Sopenharmony_ci /* ask the application */ 88713498266Sopenharmony_ci CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ask application"); 88813498266Sopenharmony_ci 88913498266Sopenharmony_ci stream = H2_STREAM_CTX(data); 89013498266Sopenharmony_ci if(!stream) { 89113498266Sopenharmony_ci failf(data, "Internal NULL stream"); 89213498266Sopenharmony_ci discard_newhandle(cf, newhandle); 89313498266Sopenharmony_ci rv = CURL_PUSH_DENY; 89413498266Sopenharmony_ci goto fail; 89513498266Sopenharmony_ci } 89613498266Sopenharmony_ci 89713498266Sopenharmony_ci rv = set_transfer_url(newhandle, &heads); 89813498266Sopenharmony_ci if(rv) { 89913498266Sopenharmony_ci discard_newhandle(cf, newhandle); 90013498266Sopenharmony_ci rv = CURL_PUSH_DENY; 90113498266Sopenharmony_ci goto fail; 90213498266Sopenharmony_ci } 90313498266Sopenharmony_ci 90413498266Sopenharmony_ci result = http2_data_setup(cf, newhandle, &newstream); 90513498266Sopenharmony_ci if(result) { 90613498266Sopenharmony_ci failf(data, "error setting up stream: %d", result); 90713498266Sopenharmony_ci discard_newhandle(cf, newhandle); 90813498266Sopenharmony_ci rv = CURL_PUSH_DENY; 90913498266Sopenharmony_ci goto fail; 91013498266Sopenharmony_ci } 91113498266Sopenharmony_ci DEBUGASSERT(stream); 91213498266Sopenharmony_ci 91313498266Sopenharmony_ci Curl_set_in_callback(data, true); 91413498266Sopenharmony_ci rv = data->multi->push_cb(data, newhandle, 91513498266Sopenharmony_ci stream->push_headers_used, &heads, 91613498266Sopenharmony_ci data->multi->push_userp); 91713498266Sopenharmony_ci Curl_set_in_callback(data, false); 91813498266Sopenharmony_ci 91913498266Sopenharmony_ci /* free the headers again */ 92013498266Sopenharmony_ci free_push_headers(stream); 92113498266Sopenharmony_ci 92213498266Sopenharmony_ci if(rv) { 92313498266Sopenharmony_ci DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); 92413498266Sopenharmony_ci /* denied, kill off the new handle again */ 92513498266Sopenharmony_ci discard_newhandle(cf, newhandle); 92613498266Sopenharmony_ci goto fail; 92713498266Sopenharmony_ci } 92813498266Sopenharmony_ci 92913498266Sopenharmony_ci newstream->id = frame->promised_stream_id; 93013498266Sopenharmony_ci newhandle->req.maxdownload = -1; 93113498266Sopenharmony_ci newhandle->req.size = -1; 93213498266Sopenharmony_ci 93313498266Sopenharmony_ci /* approved, add to the multi handle and immediately switch to PERFORM 93413498266Sopenharmony_ci state with the given connection !*/ 93513498266Sopenharmony_ci rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn); 93613498266Sopenharmony_ci if(rc) { 93713498266Sopenharmony_ci infof(data, "failed to add handle to multi"); 93813498266Sopenharmony_ci discard_newhandle(cf, newhandle); 93913498266Sopenharmony_ci rv = CURL_PUSH_DENY; 94013498266Sopenharmony_ci goto fail; 94113498266Sopenharmony_ci } 94213498266Sopenharmony_ci 94313498266Sopenharmony_ci rv = nghttp2_session_set_stream_user_data(ctx->h2, 94413498266Sopenharmony_ci newstream->id, 94513498266Sopenharmony_ci newhandle); 94613498266Sopenharmony_ci if(rv) { 94713498266Sopenharmony_ci infof(data, "failed to set user_data for stream %u", 94813498266Sopenharmony_ci newstream->id); 94913498266Sopenharmony_ci DEBUGASSERT(0); 95013498266Sopenharmony_ci rv = CURL_PUSH_DENY; 95113498266Sopenharmony_ci goto fail; 95213498266Sopenharmony_ci } 95313498266Sopenharmony_ci } 95413498266Sopenharmony_ci else { 95513498266Sopenharmony_ci CURL_TRC_CF(data, cf, "Got PUSH_PROMISE, ignore it"); 95613498266Sopenharmony_ci rv = CURL_PUSH_DENY; 95713498266Sopenharmony_ci } 95813498266Sopenharmony_cifail: 95913498266Sopenharmony_ci return rv; 96013498266Sopenharmony_ci} 96113498266Sopenharmony_ci 96213498266Sopenharmony_cistatic CURLcode recvbuf_write_hds(struct Curl_cfilter *cf, 96313498266Sopenharmony_ci struct Curl_easy *data, 96413498266Sopenharmony_ci const char *buf, size_t blen) 96513498266Sopenharmony_ci{ 96613498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 96713498266Sopenharmony_ci ssize_t nwritten; 96813498266Sopenharmony_ci CURLcode result; 96913498266Sopenharmony_ci 97013498266Sopenharmony_ci (void)cf; 97113498266Sopenharmony_ci nwritten = Curl_bufq_write(&stream->recvbuf, 97213498266Sopenharmony_ci (const unsigned char *)buf, blen, &result); 97313498266Sopenharmony_ci if(nwritten < 0) 97413498266Sopenharmony_ci return result; 97513498266Sopenharmony_ci stream->resp_hds_len += (size_t)nwritten; 97613498266Sopenharmony_ci DEBUGASSERT((size_t)nwritten == blen); 97713498266Sopenharmony_ci return CURLE_OK; 97813498266Sopenharmony_ci} 97913498266Sopenharmony_ci 98013498266Sopenharmony_cistatic CURLcode on_stream_frame(struct Curl_cfilter *cf, 98113498266Sopenharmony_ci struct Curl_easy *data, 98213498266Sopenharmony_ci const nghttp2_frame *frame) 98313498266Sopenharmony_ci{ 98413498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 98513498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 98613498266Sopenharmony_ci int32_t stream_id = frame->hd.stream_id; 98713498266Sopenharmony_ci CURLcode result; 98813498266Sopenharmony_ci size_t rbuflen; 98913498266Sopenharmony_ci int rv; 99013498266Sopenharmony_ci 99113498266Sopenharmony_ci if(!stream) { 99213498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] No stream_ctx set", stream_id); 99313498266Sopenharmony_ci return CURLE_FAILED_INIT; 99413498266Sopenharmony_ci } 99513498266Sopenharmony_ci 99613498266Sopenharmony_ci switch(frame->hd.type) { 99713498266Sopenharmony_ci case NGHTTP2_DATA: 99813498266Sopenharmony_ci rbuflen = Curl_bufq_len(&stream->recvbuf); 99913498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] DATA, buffered=%zu, window=%d/%d", 100013498266Sopenharmony_ci stream_id, rbuflen, 100113498266Sopenharmony_ci nghttp2_session_get_stream_effective_recv_data_length( 100213498266Sopenharmony_ci ctx->h2, stream->id), 100313498266Sopenharmony_ci nghttp2_session_get_stream_effective_local_window_size( 100413498266Sopenharmony_ci ctx->h2, stream->id)); 100513498266Sopenharmony_ci /* If !body started on this stream, then receiving DATA is illegal. */ 100613498266Sopenharmony_ci if(!stream->bodystarted) { 100713498266Sopenharmony_ci rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, 100813498266Sopenharmony_ci stream_id, NGHTTP2_PROTOCOL_ERROR); 100913498266Sopenharmony_ci 101013498266Sopenharmony_ci if(nghttp2_is_fatal(rv)) { 101113498266Sopenharmony_ci return CURLE_RECV_ERROR; 101213498266Sopenharmony_ci } 101313498266Sopenharmony_ci } 101413498266Sopenharmony_ci if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { 101513498266Sopenharmony_ci drain_stream(cf, data, stream); 101613498266Sopenharmony_ci } 101713498266Sopenharmony_ci else if(rbuflen > stream->local_window_size) { 101813498266Sopenharmony_ci int32_t wsize = nghttp2_session_get_stream_local_window_size( 101913498266Sopenharmony_ci ctx->h2, stream->id); 102013498266Sopenharmony_ci if(wsize > 0 && (uint32_t)wsize != stream->local_window_size) { 102113498266Sopenharmony_ci /* H2 flow control is not absolute, as the server might not have the 102213498266Sopenharmony_ci * same view, yet. When we receive more than we want, we enforce 102313498266Sopenharmony_ci * the local window size again to make nghttp2 send WINDOW_UPATEs 102413498266Sopenharmony_ci * accordingly. */ 102513498266Sopenharmony_ci nghttp2_session_set_local_window_size(ctx->h2, 102613498266Sopenharmony_ci NGHTTP2_FLAG_NONE, 102713498266Sopenharmony_ci stream->id, 102813498266Sopenharmony_ci stream->local_window_size); 102913498266Sopenharmony_ci } 103013498266Sopenharmony_ci } 103113498266Sopenharmony_ci break; 103213498266Sopenharmony_ci case NGHTTP2_HEADERS: 103313498266Sopenharmony_ci if(stream->bodystarted) { 103413498266Sopenharmony_ci /* Only valid HEADERS after body started is trailer HEADERS. We 103513498266Sopenharmony_ci buffer them in on_header callback. */ 103613498266Sopenharmony_ci break; 103713498266Sopenharmony_ci } 103813498266Sopenharmony_ci 103913498266Sopenharmony_ci /* nghttp2 guarantees that :status is received, and we store it to 104013498266Sopenharmony_ci stream->status_code. Fuzzing has proven this can still be reached 104113498266Sopenharmony_ci without status code having been set. */ 104213498266Sopenharmony_ci if(stream->status_code == -1) 104313498266Sopenharmony_ci return CURLE_RECV_ERROR; 104413498266Sopenharmony_ci 104513498266Sopenharmony_ci /* Only final status code signals the end of header */ 104613498266Sopenharmony_ci if(stream->status_code / 100 != 1) { 104713498266Sopenharmony_ci stream->bodystarted = TRUE; 104813498266Sopenharmony_ci stream->status_code = -1; 104913498266Sopenharmony_ci } 105013498266Sopenharmony_ci 105113498266Sopenharmony_ci result = recvbuf_write_hds(cf, data, STRCONST("\r\n")); 105213498266Sopenharmony_ci if(result) 105313498266Sopenharmony_ci return result; 105413498266Sopenharmony_ci 105513498266Sopenharmony_ci if(stream->status_code / 100 != 1) { 105613498266Sopenharmony_ci stream->resp_hds_complete = TRUE; 105713498266Sopenharmony_ci } 105813498266Sopenharmony_ci drain_stream(cf, data, stream); 105913498266Sopenharmony_ci break; 106013498266Sopenharmony_ci case NGHTTP2_PUSH_PROMISE: 106113498266Sopenharmony_ci rv = push_promise(cf, data, &frame->push_promise); 106213498266Sopenharmony_ci if(rv) { /* deny! */ 106313498266Sopenharmony_ci DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT)); 106413498266Sopenharmony_ci rv = nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE, 106513498266Sopenharmony_ci frame->push_promise.promised_stream_id, 106613498266Sopenharmony_ci NGHTTP2_CANCEL); 106713498266Sopenharmony_ci if(nghttp2_is_fatal(rv)) 106813498266Sopenharmony_ci return CURLE_SEND_ERROR; 106913498266Sopenharmony_ci else if(rv == CURL_PUSH_ERROROUT) { 107013498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] fail in PUSH_PROMISE received", 107113498266Sopenharmony_ci stream_id); 107213498266Sopenharmony_ci return CURLE_RECV_ERROR; 107313498266Sopenharmony_ci } 107413498266Sopenharmony_ci } 107513498266Sopenharmony_ci break; 107613498266Sopenharmony_ci case NGHTTP2_RST_STREAM: 107713498266Sopenharmony_ci stream->closed = TRUE; 107813498266Sopenharmony_ci if(frame->rst_stream.error_code) { 107913498266Sopenharmony_ci stream->reset = TRUE; 108013498266Sopenharmony_ci } 108113498266Sopenharmony_ci stream->send_closed = TRUE; 108213498266Sopenharmony_ci drain_stream(cf, data, stream); 108313498266Sopenharmony_ci break; 108413498266Sopenharmony_ci case NGHTTP2_WINDOW_UPDATE: 108513498266Sopenharmony_ci if(CURL_WANT_SEND(data)) { 108613498266Sopenharmony_ci drain_stream(cf, data, stream); 108713498266Sopenharmony_ci } 108813498266Sopenharmony_ci break; 108913498266Sopenharmony_ci default: 109013498266Sopenharmony_ci break; 109113498266Sopenharmony_ci } 109213498266Sopenharmony_ci return CURLE_OK; 109313498266Sopenharmony_ci} 109413498266Sopenharmony_ci 109513498266Sopenharmony_ci#ifndef CURL_DISABLE_VERBOSE_STRINGS 109613498266Sopenharmony_cistatic int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen) 109713498266Sopenharmony_ci{ 109813498266Sopenharmony_ci switch(frame->hd.type) { 109913498266Sopenharmony_ci case NGHTTP2_DATA: { 110013498266Sopenharmony_ci return msnprintf(buffer, blen, 110113498266Sopenharmony_ci "FRAME[DATA, len=%d, eos=%d, padlen=%d]", 110213498266Sopenharmony_ci (int)frame->hd.length, 110313498266Sopenharmony_ci !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM), 110413498266Sopenharmony_ci (int)frame->data.padlen); 110513498266Sopenharmony_ci } 110613498266Sopenharmony_ci case NGHTTP2_HEADERS: { 110713498266Sopenharmony_ci return msnprintf(buffer, blen, 110813498266Sopenharmony_ci "FRAME[HEADERS, len=%d, hend=%d, eos=%d]", 110913498266Sopenharmony_ci (int)frame->hd.length, 111013498266Sopenharmony_ci !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS), 111113498266Sopenharmony_ci !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)); 111213498266Sopenharmony_ci } 111313498266Sopenharmony_ci case NGHTTP2_PRIORITY: { 111413498266Sopenharmony_ci return msnprintf(buffer, blen, 111513498266Sopenharmony_ci "FRAME[PRIORITY, len=%d, flags=%d]", 111613498266Sopenharmony_ci (int)frame->hd.length, frame->hd.flags); 111713498266Sopenharmony_ci } 111813498266Sopenharmony_ci case NGHTTP2_RST_STREAM: { 111913498266Sopenharmony_ci return msnprintf(buffer, blen, 112013498266Sopenharmony_ci "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]", 112113498266Sopenharmony_ci (int)frame->hd.length, frame->hd.flags, 112213498266Sopenharmony_ci frame->rst_stream.error_code); 112313498266Sopenharmony_ci } 112413498266Sopenharmony_ci case NGHTTP2_SETTINGS: { 112513498266Sopenharmony_ci if(frame->hd.flags & NGHTTP2_FLAG_ACK) { 112613498266Sopenharmony_ci return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]"); 112713498266Sopenharmony_ci } 112813498266Sopenharmony_ci return msnprintf(buffer, blen, 112913498266Sopenharmony_ci "FRAME[SETTINGS, len=%d]", (int)frame->hd.length); 113013498266Sopenharmony_ci } 113113498266Sopenharmony_ci case NGHTTP2_PUSH_PROMISE: { 113213498266Sopenharmony_ci return msnprintf(buffer, blen, 113313498266Sopenharmony_ci "FRAME[PUSH_PROMISE, len=%d, hend=%d]", 113413498266Sopenharmony_ci (int)frame->hd.length, 113513498266Sopenharmony_ci !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS)); 113613498266Sopenharmony_ci } 113713498266Sopenharmony_ci case NGHTTP2_PING: { 113813498266Sopenharmony_ci return msnprintf(buffer, blen, 113913498266Sopenharmony_ci "FRAME[PING, len=%d, ack=%d]", 114013498266Sopenharmony_ci (int)frame->hd.length, 114113498266Sopenharmony_ci frame->hd.flags&NGHTTP2_FLAG_ACK); 114213498266Sopenharmony_ci } 114313498266Sopenharmony_ci case NGHTTP2_GOAWAY: { 114413498266Sopenharmony_ci char scratch[128]; 114513498266Sopenharmony_ci size_t s_len = sizeof(scratch)/sizeof(scratch[0]); 114613498266Sopenharmony_ci size_t len = (frame->goaway.opaque_data_len < s_len)? 114713498266Sopenharmony_ci frame->goaway.opaque_data_len : s_len-1; 114813498266Sopenharmony_ci if(len) 114913498266Sopenharmony_ci memcpy(scratch, frame->goaway.opaque_data, len); 115013498266Sopenharmony_ci scratch[len] = '\0'; 115113498266Sopenharmony_ci return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', " 115213498266Sopenharmony_ci "last_stream=%d]", frame->goaway.error_code, 115313498266Sopenharmony_ci scratch, frame->goaway.last_stream_id); 115413498266Sopenharmony_ci } 115513498266Sopenharmony_ci case NGHTTP2_WINDOW_UPDATE: { 115613498266Sopenharmony_ci return msnprintf(buffer, blen, 115713498266Sopenharmony_ci "FRAME[WINDOW_UPDATE, incr=%d]", 115813498266Sopenharmony_ci frame->window_update.window_size_increment); 115913498266Sopenharmony_ci } 116013498266Sopenharmony_ci default: 116113498266Sopenharmony_ci return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]", 116213498266Sopenharmony_ci frame->hd.type, (int)frame->hd.length, 116313498266Sopenharmony_ci frame->hd.flags); 116413498266Sopenharmony_ci } 116513498266Sopenharmony_ci} 116613498266Sopenharmony_ci 116713498266Sopenharmony_cistatic int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame, 116813498266Sopenharmony_ci void *userp) 116913498266Sopenharmony_ci{ 117013498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 117113498266Sopenharmony_ci struct Curl_easy *data = CF_DATA_CURRENT(cf); 117213498266Sopenharmony_ci 117313498266Sopenharmony_ci (void)session; 117413498266Sopenharmony_ci DEBUGASSERT(data); 117513498266Sopenharmony_ci if(data && Curl_trc_cf_is_verbose(cf, data)) { 117613498266Sopenharmony_ci char buffer[256]; 117713498266Sopenharmony_ci int len; 117813498266Sopenharmony_ci len = fr_print(frame, buffer, sizeof(buffer)-1); 117913498266Sopenharmony_ci buffer[len] = 0; 118013498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer); 118113498266Sopenharmony_ci } 118213498266Sopenharmony_ci return 0; 118313498266Sopenharmony_ci} 118413498266Sopenharmony_ci#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ 118513498266Sopenharmony_ci 118613498266Sopenharmony_cistatic int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame, 118713498266Sopenharmony_ci void *userp) 118813498266Sopenharmony_ci{ 118913498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 119013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 119113498266Sopenharmony_ci struct Curl_easy *data = CF_DATA_CURRENT(cf), *data_s; 119213498266Sopenharmony_ci int32_t stream_id = frame->hd.stream_id; 119313498266Sopenharmony_ci 119413498266Sopenharmony_ci DEBUGASSERT(data); 119513498266Sopenharmony_ci#ifndef CURL_DISABLE_VERBOSE_STRINGS 119613498266Sopenharmony_ci if(Curl_trc_cf_is_verbose(cf, data)) { 119713498266Sopenharmony_ci char buffer[256]; 119813498266Sopenharmony_ci int len; 119913498266Sopenharmony_ci len = fr_print(frame, buffer, sizeof(buffer)-1); 120013498266Sopenharmony_ci buffer[len] = 0; 120113498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer); 120213498266Sopenharmony_ci } 120313498266Sopenharmony_ci#endif /* !CURL_DISABLE_VERBOSE_STRINGS */ 120413498266Sopenharmony_ci 120513498266Sopenharmony_ci if(!stream_id) { 120613498266Sopenharmony_ci /* stream ID zero is for connection-oriented stuff */ 120713498266Sopenharmony_ci DEBUGASSERT(data); 120813498266Sopenharmony_ci switch(frame->hd.type) { 120913498266Sopenharmony_ci case NGHTTP2_SETTINGS: { 121013498266Sopenharmony_ci if(!(frame->hd.flags & NGHTTP2_FLAG_ACK)) { 121113498266Sopenharmony_ci uint32_t max_conn = ctx->max_concurrent_streams; 121213498266Sopenharmony_ci ctx->max_concurrent_streams = nghttp2_session_get_remote_settings( 121313498266Sopenharmony_ci session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS); 121413498266Sopenharmony_ci ctx->enable_push = nghttp2_session_get_remote_settings( 121513498266Sopenharmony_ci session, NGHTTP2_SETTINGS_ENABLE_PUSH) != 0; 121613498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] MAX_CONCURRENT_STREAMS: %d", 121713498266Sopenharmony_ci ctx->max_concurrent_streams); 121813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] ENABLE_PUSH: %s", 121913498266Sopenharmony_ci ctx->enable_push ? "TRUE" : "false"); 122013498266Sopenharmony_ci if(data && max_conn != ctx->max_concurrent_streams) { 122113498266Sopenharmony_ci /* only signal change if the value actually changed */ 122213498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] notify MAX_CONCURRENT_STREAMS: %u", 122313498266Sopenharmony_ci ctx->max_concurrent_streams); 122413498266Sopenharmony_ci Curl_multi_connchanged(data->multi); 122513498266Sopenharmony_ci } 122613498266Sopenharmony_ci /* Since the initial stream window is 64K, a request might be on HOLD, 122713498266Sopenharmony_ci * due to exhaustion. The (initial) SETTINGS may announce a much larger 122813498266Sopenharmony_ci * window and *assume* that we treat this like a WINDOW_UPDATE. Some 122913498266Sopenharmony_ci * servers send an explicit WINDOW_UPDATE, but not all seem to do that. 123013498266Sopenharmony_ci * To be safe, we UNHOLD a stream in order not to stall. */ 123113498266Sopenharmony_ci if(CURL_WANT_SEND(data)) { 123213498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 123313498266Sopenharmony_ci if(stream) 123413498266Sopenharmony_ci drain_stream(cf, data, stream); 123513498266Sopenharmony_ci } 123613498266Sopenharmony_ci } 123713498266Sopenharmony_ci break; 123813498266Sopenharmony_ci } 123913498266Sopenharmony_ci case NGHTTP2_GOAWAY: 124013498266Sopenharmony_ci ctx->goaway = TRUE; 124113498266Sopenharmony_ci ctx->goaway_error = frame->goaway.error_code; 124213498266Sopenharmony_ci ctx->last_stream_id = frame->goaway.last_stream_id; 124313498266Sopenharmony_ci if(data) { 124413498266Sopenharmony_ci infof(data, "received GOAWAY, error=%d, last_stream=%u", 124513498266Sopenharmony_ci ctx->goaway_error, ctx->last_stream_id); 124613498266Sopenharmony_ci Curl_multi_connchanged(data->multi); 124713498266Sopenharmony_ci } 124813498266Sopenharmony_ci break; 124913498266Sopenharmony_ci default: 125013498266Sopenharmony_ci break; 125113498266Sopenharmony_ci } 125213498266Sopenharmony_ci return 0; 125313498266Sopenharmony_ci } 125413498266Sopenharmony_ci 125513498266Sopenharmony_ci data_s = nghttp2_session_get_stream_user_data(session, stream_id); 125613498266Sopenharmony_ci if(!data_s) { 125713498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] No Curl_easy associated", stream_id); 125813498266Sopenharmony_ci return 0; 125913498266Sopenharmony_ci } 126013498266Sopenharmony_ci 126113498266Sopenharmony_ci return on_stream_frame(cf, data_s, frame)? NGHTTP2_ERR_CALLBACK_FAILURE : 0; 126213498266Sopenharmony_ci} 126313498266Sopenharmony_ci 126413498266Sopenharmony_cistatic int on_data_chunk_recv(nghttp2_session *session, uint8_t flags, 126513498266Sopenharmony_ci int32_t stream_id, 126613498266Sopenharmony_ci const uint8_t *mem, size_t len, void *userp) 126713498266Sopenharmony_ci{ 126813498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 126913498266Sopenharmony_ci struct stream_ctx *stream; 127013498266Sopenharmony_ci struct Curl_easy *data_s; 127113498266Sopenharmony_ci ssize_t nwritten; 127213498266Sopenharmony_ci CURLcode result; 127313498266Sopenharmony_ci (void)flags; 127413498266Sopenharmony_ci 127513498266Sopenharmony_ci DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ 127613498266Sopenharmony_ci DEBUGASSERT(CF_DATA_CURRENT(cf)); 127713498266Sopenharmony_ci 127813498266Sopenharmony_ci /* get the stream from the hash based on Stream ID */ 127913498266Sopenharmony_ci data_s = nghttp2_session_get_stream_user_data(session, stream_id); 128013498266Sopenharmony_ci if(!data_s) { 128113498266Sopenharmony_ci /* Receiving a Stream ID not in the hash should not happen - unless 128213498266Sopenharmony_ci we have aborted a transfer artificially and there were more data 128313498266Sopenharmony_ci in the pipeline. Silently ignore. */ 128413498266Sopenharmony_ci CURL_TRC_CF(CF_DATA_CURRENT(cf), cf, "[%d] Data for unknown", 128513498266Sopenharmony_ci stream_id); 128613498266Sopenharmony_ci /* consumed explicitly as no one will read it */ 128713498266Sopenharmony_ci nghttp2_session_consume(session, stream_id, len); 128813498266Sopenharmony_ci return 0; 128913498266Sopenharmony_ci } 129013498266Sopenharmony_ci 129113498266Sopenharmony_ci stream = H2_STREAM_CTX(data_s); 129213498266Sopenharmony_ci if(!stream) 129313498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 129413498266Sopenharmony_ci 129513498266Sopenharmony_ci nwritten = Curl_bufq_write(&stream->recvbuf, mem, len, &result); 129613498266Sopenharmony_ci if(nwritten < 0) { 129713498266Sopenharmony_ci if(result != CURLE_AGAIN) 129813498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 129913498266Sopenharmony_ci 130013498266Sopenharmony_ci nwritten = 0; 130113498266Sopenharmony_ci } 130213498266Sopenharmony_ci 130313498266Sopenharmony_ci /* if we receive data for another handle, wake that up */ 130413498266Sopenharmony_ci drain_stream(cf, data_s, stream); 130513498266Sopenharmony_ci 130613498266Sopenharmony_ci DEBUGASSERT((size_t)nwritten == len); 130713498266Sopenharmony_ci return 0; 130813498266Sopenharmony_ci} 130913498266Sopenharmony_ci 131013498266Sopenharmony_cistatic int on_stream_close(nghttp2_session *session, int32_t stream_id, 131113498266Sopenharmony_ci uint32_t error_code, void *userp) 131213498266Sopenharmony_ci{ 131313498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 131413498266Sopenharmony_ci struct Curl_easy *data_s, *call_data = CF_DATA_CURRENT(cf); 131513498266Sopenharmony_ci struct stream_ctx *stream; 131613498266Sopenharmony_ci int rv; 131713498266Sopenharmony_ci (void)session; 131813498266Sopenharmony_ci 131913498266Sopenharmony_ci DEBUGASSERT(call_data); 132013498266Sopenharmony_ci /* get the stream from the hash based on Stream ID, stream ID zero is for 132113498266Sopenharmony_ci connection-oriented stuff */ 132213498266Sopenharmony_ci data_s = stream_id? 132313498266Sopenharmony_ci nghttp2_session_get_stream_user_data(session, stream_id) : NULL; 132413498266Sopenharmony_ci if(!data_s) { 132513498266Sopenharmony_ci CURL_TRC_CF(call_data, cf, 132613498266Sopenharmony_ci "[%d] on_stream_close, no easy set on stream", stream_id); 132713498266Sopenharmony_ci return 0; 132813498266Sopenharmony_ci } 132913498266Sopenharmony_ci if(!GOOD_EASY_HANDLE(data_s)) { 133013498266Sopenharmony_ci /* nghttp2 still has an easy registered for the stream which has 133113498266Sopenharmony_ci * been freed be libcurl. This points to a code path that does not 133213498266Sopenharmony_ci * trigger DONE or DETACH events as it must. */ 133313498266Sopenharmony_ci CURL_TRC_CF(call_data, cf, 133413498266Sopenharmony_ci "[%d] on_stream_close, not a GOOD easy on stream", stream_id); 133513498266Sopenharmony_ci (void)nghttp2_session_set_stream_user_data(session, stream_id, 0); 133613498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 133713498266Sopenharmony_ci } 133813498266Sopenharmony_ci stream = H2_STREAM_CTX(data_s); 133913498266Sopenharmony_ci if(!stream) { 134013498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, 134113498266Sopenharmony_ci "[%d] on_stream_close, GOOD easy but no stream", stream_id); 134213498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 134313498266Sopenharmony_ci } 134413498266Sopenharmony_ci 134513498266Sopenharmony_ci stream->closed = TRUE; 134613498266Sopenharmony_ci stream->error = error_code; 134713498266Sopenharmony_ci if(stream->error) { 134813498266Sopenharmony_ci stream->reset = TRUE; 134913498266Sopenharmony_ci stream->send_closed = TRUE; 135013498266Sopenharmony_ci } 135113498266Sopenharmony_ci 135213498266Sopenharmony_ci if(stream->error) 135313498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, "[%d] RESET: %s (err %d)", 135413498266Sopenharmony_ci stream_id, nghttp2_http2_strerror(error_code), error_code); 135513498266Sopenharmony_ci else 135613498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, "[%d] CLOSED", stream_id); 135713498266Sopenharmony_ci drain_stream(cf, data_s, stream); 135813498266Sopenharmony_ci 135913498266Sopenharmony_ci /* remove `data_s` from the nghttp2 stream */ 136013498266Sopenharmony_ci rv = nghttp2_session_set_stream_user_data(session, stream_id, 0); 136113498266Sopenharmony_ci if(rv) { 136213498266Sopenharmony_ci infof(data_s, "http/2: failed to clear user_data for stream %u", 136313498266Sopenharmony_ci stream_id); 136413498266Sopenharmony_ci DEBUGASSERT(0); 136513498266Sopenharmony_ci } 136613498266Sopenharmony_ci return 0; 136713498266Sopenharmony_ci} 136813498266Sopenharmony_ci 136913498266Sopenharmony_cistatic int on_begin_headers(nghttp2_session *session, 137013498266Sopenharmony_ci const nghttp2_frame *frame, void *userp) 137113498266Sopenharmony_ci{ 137213498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 137313498266Sopenharmony_ci struct stream_ctx *stream; 137413498266Sopenharmony_ci struct Curl_easy *data_s = NULL; 137513498266Sopenharmony_ci 137613498266Sopenharmony_ci (void)cf; 137713498266Sopenharmony_ci data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); 137813498266Sopenharmony_ci if(!data_s) { 137913498266Sopenharmony_ci return 0; 138013498266Sopenharmony_ci } 138113498266Sopenharmony_ci 138213498266Sopenharmony_ci if(frame->hd.type != NGHTTP2_HEADERS) { 138313498266Sopenharmony_ci return 0; 138413498266Sopenharmony_ci } 138513498266Sopenharmony_ci 138613498266Sopenharmony_ci stream = H2_STREAM_CTX(data_s); 138713498266Sopenharmony_ci if(!stream || !stream->bodystarted) { 138813498266Sopenharmony_ci return 0; 138913498266Sopenharmony_ci } 139013498266Sopenharmony_ci 139113498266Sopenharmony_ci return 0; 139213498266Sopenharmony_ci} 139313498266Sopenharmony_ci 139413498266Sopenharmony_ci/* frame->hd.type is either NGHTTP2_HEADERS or NGHTTP2_PUSH_PROMISE */ 139513498266Sopenharmony_cistatic int on_header(nghttp2_session *session, const nghttp2_frame *frame, 139613498266Sopenharmony_ci const uint8_t *name, size_t namelen, 139713498266Sopenharmony_ci const uint8_t *value, size_t valuelen, 139813498266Sopenharmony_ci uint8_t flags, 139913498266Sopenharmony_ci void *userp) 140013498266Sopenharmony_ci{ 140113498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 140213498266Sopenharmony_ci struct stream_ctx *stream; 140313498266Sopenharmony_ci struct Curl_easy *data_s; 140413498266Sopenharmony_ci int32_t stream_id = frame->hd.stream_id; 140513498266Sopenharmony_ci CURLcode result; 140613498266Sopenharmony_ci (void)flags; 140713498266Sopenharmony_ci 140813498266Sopenharmony_ci DEBUGASSERT(stream_id); /* should never be a zero stream ID here */ 140913498266Sopenharmony_ci 141013498266Sopenharmony_ci /* get the stream from the hash based on Stream ID */ 141113498266Sopenharmony_ci data_s = nghttp2_session_get_stream_user_data(session, stream_id); 141213498266Sopenharmony_ci if(!data_s) 141313498266Sopenharmony_ci /* Receiving a Stream ID not in the hash should not happen, this is an 141413498266Sopenharmony_ci internal error more than anything else! */ 141513498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 141613498266Sopenharmony_ci 141713498266Sopenharmony_ci stream = H2_STREAM_CTX(data_s); 141813498266Sopenharmony_ci if(!stream) { 141913498266Sopenharmony_ci failf(data_s, "Internal NULL stream"); 142013498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 142113498266Sopenharmony_ci } 142213498266Sopenharmony_ci 142313498266Sopenharmony_ci /* Store received PUSH_PROMISE headers to be used when the subsequent 142413498266Sopenharmony_ci PUSH_PROMISE callback comes */ 142513498266Sopenharmony_ci if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { 142613498266Sopenharmony_ci char *h; 142713498266Sopenharmony_ci 142813498266Sopenharmony_ci if(!strcmp(HTTP_PSEUDO_AUTHORITY, (const char *)name)) { 142913498266Sopenharmony_ci /* pseudo headers are lower case */ 143013498266Sopenharmony_ci int rc = 0; 143113498266Sopenharmony_ci char *check = aprintf("%s:%d", cf->conn->host.name, 143213498266Sopenharmony_ci cf->conn->remote_port); 143313498266Sopenharmony_ci if(!check) 143413498266Sopenharmony_ci /* no memory */ 143513498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 143613498266Sopenharmony_ci if(!strcasecompare(check, (const char *)value) && 143713498266Sopenharmony_ci ((cf->conn->remote_port != cf->conn->given->defport) || 143813498266Sopenharmony_ci !strcasecompare(cf->conn->host.name, (const char *)value))) { 143913498266Sopenharmony_ci /* This is push is not for the same authority that was asked for in 144013498266Sopenharmony_ci * the URL. RFC 7540 section 8.2 says: "A client MUST treat a 144113498266Sopenharmony_ci * PUSH_PROMISE for which the server is not authoritative as a stream 144213498266Sopenharmony_ci * error of type PROTOCOL_ERROR." 144313498266Sopenharmony_ci */ 144413498266Sopenharmony_ci (void)nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, 144513498266Sopenharmony_ci stream_id, NGHTTP2_PROTOCOL_ERROR); 144613498266Sopenharmony_ci rc = NGHTTP2_ERR_CALLBACK_FAILURE; 144713498266Sopenharmony_ci } 144813498266Sopenharmony_ci free(check); 144913498266Sopenharmony_ci if(rc) 145013498266Sopenharmony_ci return rc; 145113498266Sopenharmony_ci } 145213498266Sopenharmony_ci 145313498266Sopenharmony_ci if(!stream->push_headers) { 145413498266Sopenharmony_ci stream->push_headers_alloc = 10; 145513498266Sopenharmony_ci stream->push_headers = malloc(stream->push_headers_alloc * 145613498266Sopenharmony_ci sizeof(char *)); 145713498266Sopenharmony_ci if(!stream->push_headers) 145813498266Sopenharmony_ci return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 145913498266Sopenharmony_ci stream->push_headers_used = 0; 146013498266Sopenharmony_ci } 146113498266Sopenharmony_ci else if(stream->push_headers_used == 146213498266Sopenharmony_ci stream->push_headers_alloc) { 146313498266Sopenharmony_ci char **headp; 146413498266Sopenharmony_ci if(stream->push_headers_alloc > 1000) { 146513498266Sopenharmony_ci /* this is beyond crazy many headers, bail out */ 146613498266Sopenharmony_ci failf(data_s, "Too many PUSH_PROMISE headers"); 146713498266Sopenharmony_ci free_push_headers(stream); 146813498266Sopenharmony_ci return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 146913498266Sopenharmony_ci } 147013498266Sopenharmony_ci stream->push_headers_alloc *= 2; 147113498266Sopenharmony_ci headp = realloc(stream->push_headers, 147213498266Sopenharmony_ci stream->push_headers_alloc * sizeof(char *)); 147313498266Sopenharmony_ci if(!headp) { 147413498266Sopenharmony_ci free_push_headers(stream); 147513498266Sopenharmony_ci return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; 147613498266Sopenharmony_ci } 147713498266Sopenharmony_ci stream->push_headers = headp; 147813498266Sopenharmony_ci } 147913498266Sopenharmony_ci h = aprintf("%s:%s", name, value); 148013498266Sopenharmony_ci if(h) 148113498266Sopenharmony_ci stream->push_headers[stream->push_headers_used++] = h; 148213498266Sopenharmony_ci return 0; 148313498266Sopenharmony_ci } 148413498266Sopenharmony_ci 148513498266Sopenharmony_ci if(stream->bodystarted) { 148613498266Sopenharmony_ci /* This is a trailer */ 148713498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, "[%d] trailer: %.*s: %.*s", 148813498266Sopenharmony_ci stream->id, (int)namelen, name, (int)valuelen, value); 148913498266Sopenharmony_ci result = Curl_dynhds_add(&stream->resp_trailers, 149013498266Sopenharmony_ci (const char *)name, namelen, 149113498266Sopenharmony_ci (const char *)value, valuelen); 149213498266Sopenharmony_ci if(result) 149313498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 149413498266Sopenharmony_ci 149513498266Sopenharmony_ci return 0; 149613498266Sopenharmony_ci } 149713498266Sopenharmony_ci 149813498266Sopenharmony_ci if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 && 149913498266Sopenharmony_ci memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) { 150013498266Sopenharmony_ci /* nghttp2 guarantees :status is received first and only once. */ 150113498266Sopenharmony_ci char buffer[32]; 150213498266Sopenharmony_ci result = Curl_http_decode_status(&stream->status_code, 150313498266Sopenharmony_ci (const char *)value, valuelen); 150413498266Sopenharmony_ci if(result) 150513498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 150613498266Sopenharmony_ci msnprintf(buffer, sizeof(buffer), HTTP_PSEUDO_STATUS ":%u\r", 150713498266Sopenharmony_ci stream->status_code); 150813498266Sopenharmony_ci result = Curl_headers_push(data_s, buffer, CURLH_PSEUDO); 150913498266Sopenharmony_ci if(result) 151013498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 151113498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, STRCONST("HTTP/2 ")); 151213498266Sopenharmony_ci if(result) 151313498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 151413498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); 151513498266Sopenharmony_ci if(result) 151613498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 151713498266Sopenharmony_ci /* the space character after the status code is mandatory */ 151813498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, STRCONST(" \r\n")); 151913498266Sopenharmony_ci if(result) 152013498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 152113498266Sopenharmony_ci /* if we receive data for another handle, wake that up */ 152213498266Sopenharmony_ci if(CF_DATA_CURRENT(cf) != data_s) 152313498266Sopenharmony_ci Curl_expire(data_s, 0, EXPIRE_RUN_NOW); 152413498266Sopenharmony_ci 152513498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, "[%d] status: HTTP/2 %03d", 152613498266Sopenharmony_ci stream->id, stream->status_code); 152713498266Sopenharmony_ci return 0; 152813498266Sopenharmony_ci } 152913498266Sopenharmony_ci 153013498266Sopenharmony_ci /* nghttp2 guarantees that namelen > 0, and :status was already 153113498266Sopenharmony_ci received, and this is not pseudo-header field . */ 153213498266Sopenharmony_ci /* convert to an HTTP1-style header */ 153313498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, (const char *)name, namelen); 153413498266Sopenharmony_ci if(result) 153513498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 153613498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, STRCONST(": ")); 153713498266Sopenharmony_ci if(result) 153813498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 153913498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, (const char *)value, valuelen); 154013498266Sopenharmony_ci if(result) 154113498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 154213498266Sopenharmony_ci result = recvbuf_write_hds(cf, data_s, STRCONST("\r\n")); 154313498266Sopenharmony_ci if(result) 154413498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 154513498266Sopenharmony_ci /* if we receive data for another handle, wake that up */ 154613498266Sopenharmony_ci if(CF_DATA_CURRENT(cf) != data_s) 154713498266Sopenharmony_ci Curl_expire(data_s, 0, EXPIRE_RUN_NOW); 154813498266Sopenharmony_ci 154913498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, "[%d] header: %.*s: %.*s", 155013498266Sopenharmony_ci stream->id, (int)namelen, name, (int)valuelen, value); 155113498266Sopenharmony_ci 155213498266Sopenharmony_ci return 0; /* 0 is successful */ 155313498266Sopenharmony_ci} 155413498266Sopenharmony_ci 155513498266Sopenharmony_cistatic ssize_t req_body_read_callback(nghttp2_session *session, 155613498266Sopenharmony_ci int32_t stream_id, 155713498266Sopenharmony_ci uint8_t *buf, size_t length, 155813498266Sopenharmony_ci uint32_t *data_flags, 155913498266Sopenharmony_ci nghttp2_data_source *source, 156013498266Sopenharmony_ci void *userp) 156113498266Sopenharmony_ci{ 156213498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 156313498266Sopenharmony_ci struct Curl_easy *data_s; 156413498266Sopenharmony_ci struct stream_ctx *stream = NULL; 156513498266Sopenharmony_ci CURLcode result; 156613498266Sopenharmony_ci ssize_t nread; 156713498266Sopenharmony_ci (void)source; 156813498266Sopenharmony_ci 156913498266Sopenharmony_ci (void)cf; 157013498266Sopenharmony_ci if(stream_id) { 157113498266Sopenharmony_ci /* get the stream from the hash based on Stream ID, stream ID zero is for 157213498266Sopenharmony_ci connection-oriented stuff */ 157313498266Sopenharmony_ci data_s = nghttp2_session_get_stream_user_data(session, stream_id); 157413498266Sopenharmony_ci if(!data_s) 157513498266Sopenharmony_ci /* Receiving a Stream ID not in the hash should not happen, this is an 157613498266Sopenharmony_ci internal error more than anything else! */ 157713498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 157813498266Sopenharmony_ci 157913498266Sopenharmony_ci stream = H2_STREAM_CTX(data_s); 158013498266Sopenharmony_ci if(!stream) 158113498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 158213498266Sopenharmony_ci } 158313498266Sopenharmony_ci else 158413498266Sopenharmony_ci return NGHTTP2_ERR_INVALID_ARGUMENT; 158513498266Sopenharmony_ci 158613498266Sopenharmony_ci nread = Curl_bufq_read(&stream->sendbuf, buf, length, &result); 158713498266Sopenharmony_ci if(nread < 0) { 158813498266Sopenharmony_ci if(result != CURLE_AGAIN) 158913498266Sopenharmony_ci return NGHTTP2_ERR_CALLBACK_FAILURE; 159013498266Sopenharmony_ci nread = 0; 159113498266Sopenharmony_ci } 159213498266Sopenharmony_ci 159313498266Sopenharmony_ci if(nread > 0 && stream->upload_left != -1) 159413498266Sopenharmony_ci stream->upload_left -= nread; 159513498266Sopenharmony_ci 159613498266Sopenharmony_ci CURL_TRC_CF(data_s, cf, "[%d] req_body_read(len=%zu) left=%" 159713498266Sopenharmony_ci CURL_FORMAT_CURL_OFF_T " -> %zd, %d", 159813498266Sopenharmony_ci stream_id, length, stream->upload_left, nread, result); 159913498266Sopenharmony_ci 160013498266Sopenharmony_ci if(stream->upload_left == 0) 160113498266Sopenharmony_ci *data_flags = NGHTTP2_DATA_FLAG_EOF; 160213498266Sopenharmony_ci else if(nread == 0) 160313498266Sopenharmony_ci return NGHTTP2_ERR_DEFERRED; 160413498266Sopenharmony_ci 160513498266Sopenharmony_ci return nread; 160613498266Sopenharmony_ci} 160713498266Sopenharmony_ci 160813498266Sopenharmony_ci#if !defined(CURL_DISABLE_VERBOSE_STRINGS) 160913498266Sopenharmony_cistatic int error_callback(nghttp2_session *session, 161013498266Sopenharmony_ci const char *msg, 161113498266Sopenharmony_ci size_t len, 161213498266Sopenharmony_ci void *userp) 161313498266Sopenharmony_ci{ 161413498266Sopenharmony_ci struct Curl_cfilter *cf = userp; 161513498266Sopenharmony_ci struct Curl_easy *data = CF_DATA_CURRENT(cf); 161613498266Sopenharmony_ci (void)session; 161713498266Sopenharmony_ci failf(data, "%.*s", (int)len, msg); 161813498266Sopenharmony_ci return 0; 161913498266Sopenharmony_ci} 162013498266Sopenharmony_ci#endif 162113498266Sopenharmony_ci 162213498266Sopenharmony_ci/* 162313498266Sopenharmony_ci * Append headers to ask for an HTTP1.1 to HTTP2 upgrade. 162413498266Sopenharmony_ci */ 162513498266Sopenharmony_ciCURLcode Curl_http2_request_upgrade(struct dynbuf *req, 162613498266Sopenharmony_ci struct Curl_easy *data) 162713498266Sopenharmony_ci{ 162813498266Sopenharmony_ci CURLcode result; 162913498266Sopenharmony_ci char *base64; 163013498266Sopenharmony_ci size_t blen; 163113498266Sopenharmony_ci struct SingleRequest *k = &data->req; 163213498266Sopenharmony_ci uint8_t binsettings[H2_BINSETTINGS_LEN]; 163313498266Sopenharmony_ci ssize_t binlen; /* length of the binsettings data */ 163413498266Sopenharmony_ci 163513498266Sopenharmony_ci binlen = populate_binsettings(binsettings, data); 163613498266Sopenharmony_ci if(binlen <= 0) { 163713498266Sopenharmony_ci failf(data, "nghttp2 unexpectedly failed on pack_settings_payload"); 163813498266Sopenharmony_ci Curl_dyn_free(req); 163913498266Sopenharmony_ci return CURLE_FAILED_INIT; 164013498266Sopenharmony_ci } 164113498266Sopenharmony_ci 164213498266Sopenharmony_ci result = Curl_base64url_encode((const char *)binsettings, binlen, 164313498266Sopenharmony_ci &base64, &blen); 164413498266Sopenharmony_ci if(result) { 164513498266Sopenharmony_ci Curl_dyn_free(req); 164613498266Sopenharmony_ci return result; 164713498266Sopenharmony_ci } 164813498266Sopenharmony_ci 164913498266Sopenharmony_ci result = Curl_dyn_addf(req, 165013498266Sopenharmony_ci "Connection: Upgrade, HTTP2-Settings\r\n" 165113498266Sopenharmony_ci "Upgrade: %s\r\n" 165213498266Sopenharmony_ci "HTTP2-Settings: %s\r\n", 165313498266Sopenharmony_ci NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64); 165413498266Sopenharmony_ci free(base64); 165513498266Sopenharmony_ci 165613498266Sopenharmony_ci k->upgr101 = UPGR101_H2; 165713498266Sopenharmony_ci 165813498266Sopenharmony_ci return result; 165913498266Sopenharmony_ci} 166013498266Sopenharmony_ci 166113498266Sopenharmony_cistatic CURLcode http2_data_done_send(struct Curl_cfilter *cf, 166213498266Sopenharmony_ci struct Curl_easy *data) 166313498266Sopenharmony_ci{ 166413498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 166513498266Sopenharmony_ci CURLcode result = CURLE_OK; 166613498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 166713498266Sopenharmony_ci 166813498266Sopenharmony_ci if(!ctx || !ctx->h2 || !stream) 166913498266Sopenharmony_ci goto out; 167013498266Sopenharmony_ci 167113498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] data done send", stream->id); 167213498266Sopenharmony_ci if(!stream->send_closed) { 167313498266Sopenharmony_ci stream->send_closed = TRUE; 167413498266Sopenharmony_ci if(stream->upload_left) { 167513498266Sopenharmony_ci /* we now know that everything that is buffered is all there is. */ 167613498266Sopenharmony_ci stream->upload_left = Curl_bufq_len(&stream->sendbuf); 167713498266Sopenharmony_ci /* resume sending here to trigger the callback to get called again so 167813498266Sopenharmony_ci that it can signal EOF to nghttp2 */ 167913498266Sopenharmony_ci (void)nghttp2_session_resume_data(ctx->h2, stream->id); 168013498266Sopenharmony_ci drain_stream(cf, data, stream); 168113498266Sopenharmony_ci } 168213498266Sopenharmony_ci } 168313498266Sopenharmony_ci 168413498266Sopenharmony_ciout: 168513498266Sopenharmony_ci return result; 168613498266Sopenharmony_ci} 168713498266Sopenharmony_ci 168813498266Sopenharmony_cistatic ssize_t http2_handle_stream_close(struct Curl_cfilter *cf, 168913498266Sopenharmony_ci struct Curl_easy *data, 169013498266Sopenharmony_ci struct stream_ctx *stream, 169113498266Sopenharmony_ci CURLcode *err) 169213498266Sopenharmony_ci{ 169313498266Sopenharmony_ci ssize_t rv = 0; 169413498266Sopenharmony_ci 169513498266Sopenharmony_ci if(stream->error == NGHTTP2_REFUSED_STREAM) { 169613498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new " 169713498266Sopenharmony_ci "connection", stream->id); 169813498266Sopenharmony_ci connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */ 169913498266Sopenharmony_ci data->state.refused_stream = TRUE; 170013498266Sopenharmony_ci *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */ 170113498266Sopenharmony_ci return -1; 170213498266Sopenharmony_ci } 170313498266Sopenharmony_ci else if(stream->error != NGHTTP2_NO_ERROR) { 170413498266Sopenharmony_ci failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)", 170513498266Sopenharmony_ci stream->id, nghttp2_http2_strerror(stream->error), 170613498266Sopenharmony_ci stream->error); 170713498266Sopenharmony_ci *err = CURLE_HTTP2_STREAM; 170813498266Sopenharmony_ci return -1; 170913498266Sopenharmony_ci } 171013498266Sopenharmony_ci else if(stream->reset) { 171113498266Sopenharmony_ci failf(data, "HTTP/2 stream %u was reset", stream->id); 171213498266Sopenharmony_ci *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; 171313498266Sopenharmony_ci return -1; 171413498266Sopenharmony_ci } 171513498266Sopenharmony_ci 171613498266Sopenharmony_ci if(!stream->bodystarted) { 171713498266Sopenharmony_ci failf(data, "HTTP/2 stream %u was closed cleanly, but before getting " 171813498266Sopenharmony_ci " all response header fields, treated as error", 171913498266Sopenharmony_ci stream->id); 172013498266Sopenharmony_ci *err = CURLE_HTTP2_STREAM; 172113498266Sopenharmony_ci return -1; 172213498266Sopenharmony_ci } 172313498266Sopenharmony_ci 172413498266Sopenharmony_ci if(Curl_dynhds_count(&stream->resp_trailers)) { 172513498266Sopenharmony_ci struct dynhds_entry *e; 172613498266Sopenharmony_ci struct dynbuf dbuf; 172713498266Sopenharmony_ci size_t i; 172813498266Sopenharmony_ci 172913498266Sopenharmony_ci *err = CURLE_OK; 173013498266Sopenharmony_ci Curl_dyn_init(&dbuf, DYN_TRAILERS); 173113498266Sopenharmony_ci for(i = 0; i < Curl_dynhds_count(&stream->resp_trailers); ++i) { 173213498266Sopenharmony_ci e = Curl_dynhds_getn(&stream->resp_trailers, i); 173313498266Sopenharmony_ci if(!e) 173413498266Sopenharmony_ci break; 173513498266Sopenharmony_ci Curl_dyn_reset(&dbuf); 173613498266Sopenharmony_ci *err = Curl_dyn_addf(&dbuf, "%.*s: %.*s\x0d\x0a", 173713498266Sopenharmony_ci (int)e->namelen, e->name, 173813498266Sopenharmony_ci (int)e->valuelen, e->value); 173913498266Sopenharmony_ci if(*err) 174013498266Sopenharmony_ci break; 174113498266Sopenharmony_ci Curl_debug(data, CURLINFO_HEADER_IN, Curl_dyn_ptr(&dbuf), 174213498266Sopenharmony_ci Curl_dyn_len(&dbuf)); 174313498266Sopenharmony_ci *err = Curl_client_write(data, CLIENTWRITE_HEADER|CLIENTWRITE_TRAILER, 174413498266Sopenharmony_ci Curl_dyn_ptr(&dbuf), Curl_dyn_len(&dbuf)); 174513498266Sopenharmony_ci if(*err) 174613498266Sopenharmony_ci break; 174713498266Sopenharmony_ci } 174813498266Sopenharmony_ci Curl_dyn_free(&dbuf); 174913498266Sopenharmony_ci if(*err) 175013498266Sopenharmony_ci goto out; 175113498266Sopenharmony_ci } 175213498266Sopenharmony_ci 175313498266Sopenharmony_ci stream->close_handled = TRUE; 175413498266Sopenharmony_ci *err = CURLE_OK; 175513498266Sopenharmony_ci rv = 0; 175613498266Sopenharmony_ci 175713498266Sopenharmony_ciout: 175813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "handle_stream_close -> %zd, %d", rv, *err); 175913498266Sopenharmony_ci return rv; 176013498266Sopenharmony_ci} 176113498266Sopenharmony_ci 176213498266Sopenharmony_cistatic int sweight_wanted(const struct Curl_easy *data) 176313498266Sopenharmony_ci{ 176413498266Sopenharmony_ci /* 0 weight is not set by user and we take the nghttp2 default one */ 176513498266Sopenharmony_ci return data->set.priority.weight? 176613498266Sopenharmony_ci data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT; 176713498266Sopenharmony_ci} 176813498266Sopenharmony_ci 176913498266Sopenharmony_cistatic int sweight_in_effect(const struct Curl_easy *data) 177013498266Sopenharmony_ci{ 177113498266Sopenharmony_ci /* 0 weight is not set by user and we take the nghttp2 default one */ 177213498266Sopenharmony_ci return data->state.priority.weight? 177313498266Sopenharmony_ci data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT; 177413498266Sopenharmony_ci} 177513498266Sopenharmony_ci 177613498266Sopenharmony_ci/* 177713498266Sopenharmony_ci * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight 177813498266Sopenharmony_ci * and dependency to the peer. It also stores the updated values in the state 177913498266Sopenharmony_ci * struct. 178013498266Sopenharmony_ci */ 178113498266Sopenharmony_ci 178213498266Sopenharmony_cistatic void h2_pri_spec(struct Curl_easy *data, 178313498266Sopenharmony_ci nghttp2_priority_spec *pri_spec) 178413498266Sopenharmony_ci{ 178513498266Sopenharmony_ci struct Curl_data_priority *prio = &data->set.priority; 178613498266Sopenharmony_ci struct stream_ctx *depstream = H2_STREAM_CTX(prio->parent); 178713498266Sopenharmony_ci int32_t depstream_id = depstream? depstream->id:0; 178813498266Sopenharmony_ci nghttp2_priority_spec_init(pri_spec, depstream_id, 178913498266Sopenharmony_ci sweight_wanted(data), 179013498266Sopenharmony_ci data->set.priority.exclusive); 179113498266Sopenharmony_ci data->state.priority = *prio; 179213498266Sopenharmony_ci} 179313498266Sopenharmony_ci 179413498266Sopenharmony_ci/* 179513498266Sopenharmony_ci * Check if there's been an update in the priority / 179613498266Sopenharmony_ci * dependency settings and if so it submits a PRIORITY frame with the updated 179713498266Sopenharmony_ci * info. 179813498266Sopenharmony_ci * Flush any out data pending in the network buffer. 179913498266Sopenharmony_ci */ 180013498266Sopenharmony_cistatic CURLcode h2_progress_egress(struct Curl_cfilter *cf, 180113498266Sopenharmony_ci struct Curl_easy *data) 180213498266Sopenharmony_ci{ 180313498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 180413498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 180513498266Sopenharmony_ci int rv = 0; 180613498266Sopenharmony_ci 180713498266Sopenharmony_ci if(stream && stream->id > 0 && 180813498266Sopenharmony_ci ((sweight_wanted(data) != sweight_in_effect(data)) || 180913498266Sopenharmony_ci (data->set.priority.exclusive != data->state.priority.exclusive) || 181013498266Sopenharmony_ci (data->set.priority.parent != data->state.priority.parent)) ) { 181113498266Sopenharmony_ci /* send new weight and/or dependency */ 181213498266Sopenharmony_ci nghttp2_priority_spec pri_spec; 181313498266Sopenharmony_ci 181413498266Sopenharmony_ci h2_pri_spec(data, &pri_spec); 181513498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] Queuing PRIORITY", stream->id); 181613498266Sopenharmony_ci DEBUGASSERT(stream->id != -1); 181713498266Sopenharmony_ci rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE, 181813498266Sopenharmony_ci stream->id, &pri_spec); 181913498266Sopenharmony_ci if(rv) 182013498266Sopenharmony_ci goto out; 182113498266Sopenharmony_ci } 182213498266Sopenharmony_ci 182313498266Sopenharmony_ci ctx->nw_out_blocked = 0; 182413498266Sopenharmony_ci while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2)) 182513498266Sopenharmony_ci rv = nghttp2_session_send(ctx->h2); 182613498266Sopenharmony_ci 182713498266Sopenharmony_ciout: 182813498266Sopenharmony_ci if(nghttp2_is_fatal(rv)) { 182913498266Sopenharmony_ci CURL_TRC_CF(data, cf, "nghttp2_session_send error (%s)%d", 183013498266Sopenharmony_ci nghttp2_strerror(rv), rv); 183113498266Sopenharmony_ci return CURLE_SEND_ERROR; 183213498266Sopenharmony_ci } 183313498266Sopenharmony_ci return nw_out_flush(cf, data); 183413498266Sopenharmony_ci} 183513498266Sopenharmony_ci 183613498266Sopenharmony_cistatic ssize_t stream_recv(struct Curl_cfilter *cf, struct Curl_easy *data, 183713498266Sopenharmony_ci struct stream_ctx *stream, 183813498266Sopenharmony_ci char *buf, size_t len, CURLcode *err) 183913498266Sopenharmony_ci{ 184013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 184113498266Sopenharmony_ci ssize_t nread = -1; 184213498266Sopenharmony_ci 184313498266Sopenharmony_ci *err = CURLE_AGAIN; 184413498266Sopenharmony_ci if(!Curl_bufq_is_empty(&stream->recvbuf)) { 184513498266Sopenharmony_ci nread = Curl_bufq_read(&stream->recvbuf, 184613498266Sopenharmony_ci (unsigned char *)buf, len, err); 184713498266Sopenharmony_ci if(nread < 0) 184813498266Sopenharmony_ci goto out; 184913498266Sopenharmony_ci DEBUGASSERT(nread > 0); 185013498266Sopenharmony_ci } 185113498266Sopenharmony_ci 185213498266Sopenharmony_ci if(nread < 0) { 185313498266Sopenharmony_ci if(stream->closed) { 185413498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] returning CLOSE", stream->id); 185513498266Sopenharmony_ci nread = http2_handle_stream_close(cf, data, stream, err); 185613498266Sopenharmony_ci } 185713498266Sopenharmony_ci else if(stream->reset || 185813498266Sopenharmony_ci (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) || 185913498266Sopenharmony_ci (ctx->goaway && ctx->last_stream_id < stream->id)) { 186013498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] returning ERR", stream->id); 186113498266Sopenharmony_ci *err = stream->bodystarted? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR; 186213498266Sopenharmony_ci nread = -1; 186313498266Sopenharmony_ci } 186413498266Sopenharmony_ci } 186513498266Sopenharmony_ci else if(nread == 0) { 186613498266Sopenharmony_ci *err = CURLE_AGAIN; 186713498266Sopenharmony_ci nread = -1; 186813498266Sopenharmony_ci } 186913498266Sopenharmony_ci 187013498266Sopenharmony_ciout: 187113498266Sopenharmony_ci if(nread < 0 && *err != CURLE_AGAIN) 187213498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] stream_recv(len=%zu) -> %zd, %d", 187313498266Sopenharmony_ci stream->id, len, nread, *err); 187413498266Sopenharmony_ci return nread; 187513498266Sopenharmony_ci} 187613498266Sopenharmony_ci 187713498266Sopenharmony_cistatic CURLcode h2_progress_ingress(struct Curl_cfilter *cf, 187813498266Sopenharmony_ci struct Curl_easy *data) 187913498266Sopenharmony_ci{ 188013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 188113498266Sopenharmony_ci struct stream_ctx *stream; 188213498266Sopenharmony_ci CURLcode result = CURLE_OK; 188313498266Sopenharmony_ci ssize_t nread; 188413498266Sopenharmony_ci 188513498266Sopenharmony_ci /* Process network input buffer fist */ 188613498266Sopenharmony_ci if(!Curl_bufq_is_empty(&ctx->inbufq)) { 188713498266Sopenharmony_ci CURL_TRC_CF(data, cf, "Process %zu bytes in connection buffer", 188813498266Sopenharmony_ci Curl_bufq_len(&ctx->inbufq)); 188913498266Sopenharmony_ci if(h2_process_pending_input(cf, data, &result) < 0) 189013498266Sopenharmony_ci return result; 189113498266Sopenharmony_ci } 189213498266Sopenharmony_ci 189313498266Sopenharmony_ci /* Receive data from the "lower" filters, e.g. network until 189413498266Sopenharmony_ci * it is time to stop due to connection close or us not processing 189513498266Sopenharmony_ci * all network input */ 189613498266Sopenharmony_ci while(!ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { 189713498266Sopenharmony_ci stream = H2_STREAM_CTX(data); 189813498266Sopenharmony_ci if(stream && (stream->closed || Curl_bufq_is_full(&stream->recvbuf))) { 189913498266Sopenharmony_ci /* We would like to abort here and stop processing, so that 190013498266Sopenharmony_ci * the transfer loop can handle the data/close here. However, 190113498266Sopenharmony_ci * this may leave data in underlying buffers that will not 190213498266Sopenharmony_ci * be consumed. */ 190313498266Sopenharmony_ci if(!cf->next || !cf->next->cft->has_data_pending(cf->next, data)) 190413498266Sopenharmony_ci break; 190513498266Sopenharmony_ci } 190613498266Sopenharmony_ci 190713498266Sopenharmony_ci nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result); 190813498266Sopenharmony_ci if(nread < 0) { 190913498266Sopenharmony_ci if(result != CURLE_AGAIN) { 191013498266Sopenharmony_ci failf(data, "Failed receiving HTTP2 data: %d(%s)", result, 191113498266Sopenharmony_ci curl_easy_strerror(result)); 191213498266Sopenharmony_ci return result; 191313498266Sopenharmony_ci } 191413498266Sopenharmony_ci break; 191513498266Sopenharmony_ci } 191613498266Sopenharmony_ci else if(nread == 0) { 191713498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] ingress: connection closed"); 191813498266Sopenharmony_ci ctx->conn_closed = TRUE; 191913498266Sopenharmony_ci break; 192013498266Sopenharmony_ci } 192113498266Sopenharmony_ci else { 192213498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[0] ingress: read %zd bytes", 192313498266Sopenharmony_ci nread); 192413498266Sopenharmony_ci } 192513498266Sopenharmony_ci 192613498266Sopenharmony_ci if(h2_process_pending_input(cf, data, &result)) 192713498266Sopenharmony_ci return result; 192813498266Sopenharmony_ci } 192913498266Sopenharmony_ci 193013498266Sopenharmony_ci if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) { 193113498266Sopenharmony_ci connclose(cf->conn, "GOAWAY received"); 193213498266Sopenharmony_ci } 193313498266Sopenharmony_ci 193413498266Sopenharmony_ci return CURLE_OK; 193513498266Sopenharmony_ci} 193613498266Sopenharmony_ci 193713498266Sopenharmony_cistatic ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data, 193813498266Sopenharmony_ci char *buf, size_t len, CURLcode *err) 193913498266Sopenharmony_ci{ 194013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 194113498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 194213498266Sopenharmony_ci ssize_t nread = -1; 194313498266Sopenharmony_ci CURLcode result; 194413498266Sopenharmony_ci struct cf_call_data save; 194513498266Sopenharmony_ci 194613498266Sopenharmony_ci if(!stream) { 194713498266Sopenharmony_ci /* Abnormal call sequence: either this transfer has never opened a stream 194813498266Sopenharmony_ci * (unlikely) or the transfer has been done, cleaned up its resources, but 194913498266Sopenharmony_ci * a read() is called anyway. It is not clear what the calling sequence 195013498266Sopenharmony_ci * is for such a case. */ 195113498266Sopenharmony_ci failf(data, "[%zd-%zd], http/2 recv on a transfer never opened " 195213498266Sopenharmony_ci "or already cleared", (ssize_t)data->id, 195313498266Sopenharmony_ci (ssize_t)cf->conn->connection_id); 195413498266Sopenharmony_ci *err = CURLE_HTTP2; 195513498266Sopenharmony_ci return -1; 195613498266Sopenharmony_ci } 195713498266Sopenharmony_ci 195813498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 195913498266Sopenharmony_ci 196013498266Sopenharmony_ci nread = stream_recv(cf, data, stream, buf, len, err); 196113498266Sopenharmony_ci if(nread < 0 && *err != CURLE_AGAIN) 196213498266Sopenharmony_ci goto out; 196313498266Sopenharmony_ci 196413498266Sopenharmony_ci if(nread < 0) { 196513498266Sopenharmony_ci *err = h2_progress_ingress(cf, data); 196613498266Sopenharmony_ci if(*err) 196713498266Sopenharmony_ci goto out; 196813498266Sopenharmony_ci 196913498266Sopenharmony_ci nread = stream_recv(cf, data, stream, buf, len, err); 197013498266Sopenharmony_ci } 197113498266Sopenharmony_ci 197213498266Sopenharmony_ci if(nread > 0) { 197313498266Sopenharmony_ci size_t data_consumed = (size_t)nread; 197413498266Sopenharmony_ci /* Now that we transferred this to the upper layer, we report 197513498266Sopenharmony_ci * the actual amount of DATA consumed to the H2 session, so 197613498266Sopenharmony_ci * that it adjusts stream flow control */ 197713498266Sopenharmony_ci if(stream->resp_hds_len >= data_consumed) { 197813498266Sopenharmony_ci stream->resp_hds_len -= data_consumed; /* no DATA */ 197913498266Sopenharmony_ci } 198013498266Sopenharmony_ci else { 198113498266Sopenharmony_ci if(stream->resp_hds_len) { 198213498266Sopenharmony_ci data_consumed -= stream->resp_hds_len; 198313498266Sopenharmony_ci stream->resp_hds_len = 0; 198413498266Sopenharmony_ci } 198513498266Sopenharmony_ci if(data_consumed) { 198613498266Sopenharmony_ci nghttp2_session_consume(ctx->h2, stream->id, data_consumed); 198713498266Sopenharmony_ci } 198813498266Sopenharmony_ci } 198913498266Sopenharmony_ci 199013498266Sopenharmony_ci if(stream->closed) { 199113498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] DRAIN closed stream", stream->id); 199213498266Sopenharmony_ci drain_stream(cf, data, stream); 199313498266Sopenharmony_ci } 199413498266Sopenharmony_ci } 199513498266Sopenharmony_ci 199613498266Sopenharmony_ciout: 199713498266Sopenharmony_ci result = h2_progress_egress(cf, data); 199813498266Sopenharmony_ci if(result == CURLE_AGAIN) { 199913498266Sopenharmony_ci /* pending data to send, need to be called again. Ideally, we'd 200013498266Sopenharmony_ci * monitor the socket for POLLOUT, but we might not be in SENDING 200113498266Sopenharmony_ci * transfer state any longer and are unable to make this happen. 200213498266Sopenharmony_ci */ 200313498266Sopenharmony_ci drain_stream(cf, data, stream); 200413498266Sopenharmony_ci } 200513498266Sopenharmony_ci else if(result) { 200613498266Sopenharmony_ci *err = result; 200713498266Sopenharmony_ci nread = -1; 200813498266Sopenharmony_ci } 200913498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d, " 201013498266Sopenharmony_ci "buffered=%zu, window=%d/%d, connection %d/%d", 201113498266Sopenharmony_ci stream->id, len, nread, *err, 201213498266Sopenharmony_ci Curl_bufq_len(&stream->recvbuf), 201313498266Sopenharmony_ci nghttp2_session_get_stream_effective_recv_data_length( 201413498266Sopenharmony_ci ctx->h2, stream->id), 201513498266Sopenharmony_ci nghttp2_session_get_stream_effective_local_window_size( 201613498266Sopenharmony_ci ctx->h2, stream->id), 201713498266Sopenharmony_ci nghttp2_session_get_local_window_size(ctx->h2), 201813498266Sopenharmony_ci HTTP2_HUGE_WINDOW_SIZE); 201913498266Sopenharmony_ci 202013498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 202113498266Sopenharmony_ci return nread; 202213498266Sopenharmony_ci} 202313498266Sopenharmony_ci 202413498266Sopenharmony_cistatic ssize_t h2_submit(struct stream_ctx **pstream, 202513498266Sopenharmony_ci struct Curl_cfilter *cf, struct Curl_easy *data, 202613498266Sopenharmony_ci const void *buf, size_t len, CURLcode *err) 202713498266Sopenharmony_ci{ 202813498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 202913498266Sopenharmony_ci struct stream_ctx *stream = NULL; 203013498266Sopenharmony_ci struct dynhds h2_headers; 203113498266Sopenharmony_ci nghttp2_nv *nva = NULL; 203213498266Sopenharmony_ci const void *body = NULL; 203313498266Sopenharmony_ci size_t nheader, bodylen, i; 203413498266Sopenharmony_ci nghttp2_data_provider data_prd; 203513498266Sopenharmony_ci int32_t stream_id; 203613498266Sopenharmony_ci nghttp2_priority_spec pri_spec; 203713498266Sopenharmony_ci ssize_t nwritten; 203813498266Sopenharmony_ci 203913498266Sopenharmony_ci Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST); 204013498266Sopenharmony_ci 204113498266Sopenharmony_ci *err = http2_data_setup(cf, data, &stream); 204213498266Sopenharmony_ci if(*err) { 204313498266Sopenharmony_ci nwritten = -1; 204413498266Sopenharmony_ci goto out; 204513498266Sopenharmony_ci } 204613498266Sopenharmony_ci 204713498266Sopenharmony_ci nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err); 204813498266Sopenharmony_ci if(nwritten < 0) 204913498266Sopenharmony_ci goto out; 205013498266Sopenharmony_ci if(!stream->h1.done) { 205113498266Sopenharmony_ci /* need more data */ 205213498266Sopenharmony_ci goto out; 205313498266Sopenharmony_ci } 205413498266Sopenharmony_ci DEBUGASSERT(stream->h1.req); 205513498266Sopenharmony_ci 205613498266Sopenharmony_ci *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data); 205713498266Sopenharmony_ci if(*err) { 205813498266Sopenharmony_ci nwritten = -1; 205913498266Sopenharmony_ci goto out; 206013498266Sopenharmony_ci } 206113498266Sopenharmony_ci /* no longer needed */ 206213498266Sopenharmony_ci Curl_h1_req_parse_free(&stream->h1); 206313498266Sopenharmony_ci 206413498266Sopenharmony_ci nva = Curl_dynhds_to_nva(&h2_headers, &nheader); 206513498266Sopenharmony_ci if(!nva) { 206613498266Sopenharmony_ci *err = CURLE_OUT_OF_MEMORY; 206713498266Sopenharmony_ci nwritten = -1; 206813498266Sopenharmony_ci goto out; 206913498266Sopenharmony_ci } 207013498266Sopenharmony_ci 207113498266Sopenharmony_ci h2_pri_spec(data, &pri_spec); 207213498266Sopenharmony_ci if(!nghttp2_session_check_request_allowed(ctx->h2)) 207313498266Sopenharmony_ci CURL_TRC_CF(data, cf, "send request NOT allowed (via nghttp2)"); 207413498266Sopenharmony_ci 207513498266Sopenharmony_ci switch(data->state.httpreq) { 207613498266Sopenharmony_ci case HTTPREQ_POST: 207713498266Sopenharmony_ci case HTTPREQ_POST_FORM: 207813498266Sopenharmony_ci case HTTPREQ_POST_MIME: 207913498266Sopenharmony_ci case HTTPREQ_PUT: 208013498266Sopenharmony_ci if(data->state.infilesize != -1) 208113498266Sopenharmony_ci stream->upload_left = data->state.infilesize; 208213498266Sopenharmony_ci else 208313498266Sopenharmony_ci /* data sending without specifying the data amount up front */ 208413498266Sopenharmony_ci stream->upload_left = -1; /* unknown */ 208513498266Sopenharmony_ci 208613498266Sopenharmony_ci data_prd.read_callback = req_body_read_callback; 208713498266Sopenharmony_ci data_prd.source.ptr = NULL; 208813498266Sopenharmony_ci stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, 208913498266Sopenharmony_ci &data_prd, data); 209013498266Sopenharmony_ci break; 209113498266Sopenharmony_ci default: 209213498266Sopenharmony_ci stream->upload_left = 0; /* no request body */ 209313498266Sopenharmony_ci stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader, 209413498266Sopenharmony_ci NULL, data); 209513498266Sopenharmony_ci } 209613498266Sopenharmony_ci 209713498266Sopenharmony_ci if(stream_id < 0) { 209813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "send: nghttp2_submit_request error (%s)%u", 209913498266Sopenharmony_ci nghttp2_strerror(stream_id), stream_id); 210013498266Sopenharmony_ci *err = CURLE_SEND_ERROR; 210113498266Sopenharmony_ci nwritten = -1; 210213498266Sopenharmony_ci goto out; 210313498266Sopenharmony_ci } 210413498266Sopenharmony_ci 210513498266Sopenharmony_ci#define MAX_ACC 60000 /* <64KB to account for some overhead */ 210613498266Sopenharmony_ci if(Curl_trc_is_verbose(data)) { 210713498266Sopenharmony_ci size_t acc = 0; 210813498266Sopenharmony_ci 210913498266Sopenharmony_ci infof(data, "[HTTP/2] [%d] OPENED stream for %s", 211013498266Sopenharmony_ci stream_id, data->state.url); 211113498266Sopenharmony_ci for(i = 0; i < nheader; ++i) { 211213498266Sopenharmony_ci acc += nva[i].namelen + nva[i].valuelen; 211313498266Sopenharmony_ci 211413498266Sopenharmony_ci infof(data, "[HTTP/2] [%d] [%.*s: %.*s]", stream_id, 211513498266Sopenharmony_ci (int)nva[i].namelen, nva[i].name, 211613498266Sopenharmony_ci (int)nva[i].valuelen, nva[i].value); 211713498266Sopenharmony_ci } 211813498266Sopenharmony_ci 211913498266Sopenharmony_ci if(acc > MAX_ACC) { 212013498266Sopenharmony_ci infof(data, "[HTTP/2] Warning: The cumulative length of all " 212113498266Sopenharmony_ci "headers exceeds %d bytes and that could cause the " 212213498266Sopenharmony_ci "stream to be rejected.", MAX_ACC); 212313498266Sopenharmony_ci } 212413498266Sopenharmony_ci } 212513498266Sopenharmony_ci 212613498266Sopenharmony_ci stream->id = stream_id; 212713498266Sopenharmony_ci stream->local_window_size = H2_STREAM_WINDOW_SIZE; 212813498266Sopenharmony_ci if(data->set.max_recv_speed) { 212913498266Sopenharmony_ci /* We are asked to only receive `max_recv_speed` bytes per second. 213013498266Sopenharmony_ci * Let's limit our stream window size around that, otherwise the server 213113498266Sopenharmony_ci * will send in large bursts only. We make the window 50% larger to 213213498266Sopenharmony_ci * allow for data in flight and avoid stalling. */ 213313498266Sopenharmony_ci curl_off_t n = (((data->set.max_recv_speed - 1) / H2_CHUNK_SIZE) + 1); 213413498266Sopenharmony_ci n += CURLMAX((n/2), 1); 213513498266Sopenharmony_ci if(n < (H2_STREAM_WINDOW_SIZE / H2_CHUNK_SIZE) && 213613498266Sopenharmony_ci n < (UINT_MAX / H2_CHUNK_SIZE)) { 213713498266Sopenharmony_ci stream->local_window_size = (uint32_t)n * H2_CHUNK_SIZE; 213813498266Sopenharmony_ci } 213913498266Sopenharmony_ci } 214013498266Sopenharmony_ci 214113498266Sopenharmony_ci body = (const char *)buf + nwritten; 214213498266Sopenharmony_ci bodylen = len - nwritten; 214313498266Sopenharmony_ci 214413498266Sopenharmony_ci if(bodylen) { 214513498266Sopenharmony_ci /* We have request body to send in DATA frame */ 214613498266Sopenharmony_ci ssize_t n = Curl_bufq_write(&stream->sendbuf, body, bodylen, err); 214713498266Sopenharmony_ci if(n < 0) { 214813498266Sopenharmony_ci *err = CURLE_SEND_ERROR; 214913498266Sopenharmony_ci nwritten = -1; 215013498266Sopenharmony_ci goto out; 215113498266Sopenharmony_ci } 215213498266Sopenharmony_ci nwritten += n; 215313498266Sopenharmony_ci } 215413498266Sopenharmony_ci 215513498266Sopenharmony_ciout: 215613498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] submit -> %zd, %d", 215713498266Sopenharmony_ci stream? stream->id : -1, nwritten, *err); 215813498266Sopenharmony_ci Curl_safefree(nva); 215913498266Sopenharmony_ci *pstream = stream; 216013498266Sopenharmony_ci Curl_dynhds_free(&h2_headers); 216113498266Sopenharmony_ci return nwritten; 216213498266Sopenharmony_ci} 216313498266Sopenharmony_ci 216413498266Sopenharmony_cistatic ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data, 216513498266Sopenharmony_ci const void *buf, size_t len, CURLcode *err) 216613498266Sopenharmony_ci{ 216713498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 216813498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 216913498266Sopenharmony_ci struct cf_call_data save; 217013498266Sopenharmony_ci int rv; 217113498266Sopenharmony_ci ssize_t nwritten; 217213498266Sopenharmony_ci CURLcode result; 217313498266Sopenharmony_ci int blocked = 0, was_blocked = 0; 217413498266Sopenharmony_ci 217513498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 217613498266Sopenharmony_ci 217713498266Sopenharmony_ci if(stream && stream->id != -1) { 217813498266Sopenharmony_ci if(stream->upload_blocked_len) { 217913498266Sopenharmony_ci /* the data in `buf` has already been submitted or added to the 218013498266Sopenharmony_ci * buffers, but have been EAGAINed on the last invocation. */ 218113498266Sopenharmony_ci /* TODO: this assertion triggers in OSSFuzz runs and it is not 218213498266Sopenharmony_ci * clear why. Disable for now to let OSSFuzz continue its tests. */ 218313498266Sopenharmony_ci DEBUGASSERT(len >= stream->upload_blocked_len); 218413498266Sopenharmony_ci if(len < stream->upload_blocked_len) { 218513498266Sopenharmony_ci /* Did we get called again with a smaller `len`? This should not 218613498266Sopenharmony_ci * happen. We are not prepared to handle that. */ 218713498266Sopenharmony_ci failf(data, "HTTP/2 send again with decreased length (%zd vs %zd)", 218813498266Sopenharmony_ci len, stream->upload_blocked_len); 218913498266Sopenharmony_ci *err = CURLE_HTTP2; 219013498266Sopenharmony_ci nwritten = -1; 219113498266Sopenharmony_ci goto out; 219213498266Sopenharmony_ci } 219313498266Sopenharmony_ci nwritten = (ssize_t)stream->upload_blocked_len; 219413498266Sopenharmony_ci stream->upload_blocked_len = 0; 219513498266Sopenharmony_ci was_blocked = 1; 219613498266Sopenharmony_ci } 219713498266Sopenharmony_ci else if(stream->closed) { 219813498266Sopenharmony_ci if(stream->resp_hds_complete) { 219913498266Sopenharmony_ci /* Server decided to close the stream after having sent us a findl 220013498266Sopenharmony_ci * response. This is valid if it is not interested in the request 220113498266Sopenharmony_ci * body. This happens on 30x or 40x responses. 220213498266Sopenharmony_ci * We silently discard the data sent, since this is not a transport 220313498266Sopenharmony_ci * error situation. */ 220413498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] discarding data" 220513498266Sopenharmony_ci "on closed stream with response", stream->id); 220613498266Sopenharmony_ci *err = CURLE_OK; 220713498266Sopenharmony_ci nwritten = (ssize_t)len; 220813498266Sopenharmony_ci goto out; 220913498266Sopenharmony_ci } 221013498266Sopenharmony_ci infof(data, "stream %u closed", stream->id); 221113498266Sopenharmony_ci *err = CURLE_SEND_ERROR; 221213498266Sopenharmony_ci nwritten = -1; 221313498266Sopenharmony_ci goto out; 221413498266Sopenharmony_ci } 221513498266Sopenharmony_ci else { 221613498266Sopenharmony_ci /* If stream_id != -1, we have dispatched request HEADERS and 221713498266Sopenharmony_ci * optionally request body, and now are going to send or sending 221813498266Sopenharmony_ci * more request body in DATA frame */ 221913498266Sopenharmony_ci nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err); 222013498266Sopenharmony_ci if(nwritten < 0 && *err != CURLE_AGAIN) 222113498266Sopenharmony_ci goto out; 222213498266Sopenharmony_ci } 222313498266Sopenharmony_ci 222413498266Sopenharmony_ci if(!Curl_bufq_is_empty(&stream->sendbuf)) { 222513498266Sopenharmony_ci /* req body data is buffered, resume the potentially suspended stream */ 222613498266Sopenharmony_ci rv = nghttp2_session_resume_data(ctx->h2, stream->id); 222713498266Sopenharmony_ci if(nghttp2_is_fatal(rv)) { 222813498266Sopenharmony_ci *err = CURLE_SEND_ERROR; 222913498266Sopenharmony_ci nwritten = -1; 223013498266Sopenharmony_ci goto out; 223113498266Sopenharmony_ci } 223213498266Sopenharmony_ci } 223313498266Sopenharmony_ci } 223413498266Sopenharmony_ci else { 223513498266Sopenharmony_ci nwritten = h2_submit(&stream, cf, data, buf, len, err); 223613498266Sopenharmony_ci if(nwritten < 0) { 223713498266Sopenharmony_ci goto out; 223813498266Sopenharmony_ci } 223913498266Sopenharmony_ci DEBUGASSERT(stream); 224013498266Sopenharmony_ci } 224113498266Sopenharmony_ci 224213498266Sopenharmony_ci /* Call the nghttp2 send loop and flush to write ALL buffered data, 224313498266Sopenharmony_ci * headers and/or request body completely out to the network */ 224413498266Sopenharmony_ci result = h2_progress_egress(cf, data); 224513498266Sopenharmony_ci /* if the stream has been closed in egress handling (nghttp2 does that 224613498266Sopenharmony_ci * when it does not like the headers, for example */ 224713498266Sopenharmony_ci if(stream && stream->closed && !was_blocked) { 224813498266Sopenharmony_ci infof(data, "stream %u closed", stream->id); 224913498266Sopenharmony_ci *err = CURLE_SEND_ERROR; 225013498266Sopenharmony_ci nwritten = -1; 225113498266Sopenharmony_ci goto out; 225213498266Sopenharmony_ci } 225313498266Sopenharmony_ci else if(result == CURLE_AGAIN) { 225413498266Sopenharmony_ci blocked = 1; 225513498266Sopenharmony_ci } 225613498266Sopenharmony_ci else if(result) { 225713498266Sopenharmony_ci *err = result; 225813498266Sopenharmony_ci nwritten = -1; 225913498266Sopenharmony_ci goto out; 226013498266Sopenharmony_ci } 226113498266Sopenharmony_ci else if(stream && !Curl_bufq_is_empty(&stream->sendbuf)) { 226213498266Sopenharmony_ci /* although we wrote everything that nghttp2 wants to send now, 226313498266Sopenharmony_ci * there is data left in our stream send buffer unwritten. This may 226413498266Sopenharmony_ci * be due to the stream's HTTP/2 flow window being exhausted. */ 226513498266Sopenharmony_ci blocked = 1; 226613498266Sopenharmony_ci } 226713498266Sopenharmony_ci 226813498266Sopenharmony_ci if(stream && blocked && nwritten > 0) { 226913498266Sopenharmony_ci /* Unable to send all data, due to connection blocked or H2 window 227013498266Sopenharmony_ci * exhaustion. Data is left in our stream buffer, or nghttp2's internal 227113498266Sopenharmony_ci * frame buffer or our network out buffer. */ 227213498266Sopenharmony_ci size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2, 227313498266Sopenharmony_ci stream->id); 227413498266Sopenharmony_ci /* Whatever the cause, we need to return CURL_EAGAIN for this call. 227513498266Sopenharmony_ci * We have unwritten state that needs us being invoked again and EAGAIN 227613498266Sopenharmony_ci * is the only way to ensure that. */ 227713498266Sopenharmony_ci stream->upload_blocked_len = nwritten; 227813498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu " 227913498266Sopenharmony_ci "blocked_len=%zu", 228013498266Sopenharmony_ci stream->id, len, 228113498266Sopenharmony_ci nghttp2_session_get_remote_window_size(ctx->h2), rwin, 228213498266Sopenharmony_ci nwritten); 228313498266Sopenharmony_ci *err = CURLE_AGAIN; 228413498266Sopenharmony_ci nwritten = -1; 228513498266Sopenharmony_ci goto out; 228613498266Sopenharmony_ci } 228713498266Sopenharmony_ci else if(should_close_session(ctx)) { 228813498266Sopenharmony_ci /* nghttp2 thinks this session is done. If the stream has not been 228913498266Sopenharmony_ci * closed, this is an error state for out transfer */ 229013498266Sopenharmony_ci if(stream->closed) { 229113498266Sopenharmony_ci nwritten = http2_handle_stream_close(cf, data, stream, err); 229213498266Sopenharmony_ci } 229313498266Sopenharmony_ci else { 229413498266Sopenharmony_ci CURL_TRC_CF(data, cf, "send: nothing to do in this session"); 229513498266Sopenharmony_ci *err = CURLE_HTTP2; 229613498266Sopenharmony_ci nwritten = -1; 229713498266Sopenharmony_ci } 229813498266Sopenharmony_ci } 229913498266Sopenharmony_ci 230013498266Sopenharmony_ciout: 230113498266Sopenharmony_ci if(stream) { 230213498266Sopenharmony_ci CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, " 230313498266Sopenharmony_ci "upload_left=%" CURL_FORMAT_CURL_OFF_T ", " 230413498266Sopenharmony_ci "h2 windows %d-%d (stream-conn), " 230513498266Sopenharmony_ci "buffers %zu-%zu (stream-conn)", 230613498266Sopenharmony_ci stream->id, len, nwritten, *err, 230713498266Sopenharmony_ci stream->upload_left, 230813498266Sopenharmony_ci nghttp2_session_get_stream_remote_window_size( 230913498266Sopenharmony_ci ctx->h2, stream->id), 231013498266Sopenharmony_ci nghttp2_session_get_remote_window_size(ctx->h2), 231113498266Sopenharmony_ci Curl_bufq_len(&stream->sendbuf), 231213498266Sopenharmony_ci Curl_bufq_len(&ctx->outbufq)); 231313498266Sopenharmony_ci } 231413498266Sopenharmony_ci else { 231513498266Sopenharmony_ci CURL_TRC_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, " 231613498266Sopenharmony_ci "connection-window=%d, nw_send_buffer(%zu)", 231713498266Sopenharmony_ci len, nwritten, *err, 231813498266Sopenharmony_ci nghttp2_session_get_remote_window_size(ctx->h2), 231913498266Sopenharmony_ci Curl_bufq_len(&ctx->outbufq)); 232013498266Sopenharmony_ci } 232113498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 232213498266Sopenharmony_ci return nwritten; 232313498266Sopenharmony_ci} 232413498266Sopenharmony_ci 232513498266Sopenharmony_cistatic void cf_h2_adjust_pollset(struct Curl_cfilter *cf, 232613498266Sopenharmony_ci struct Curl_easy *data, 232713498266Sopenharmony_ci struct easy_pollset *ps) 232813498266Sopenharmony_ci{ 232913498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 233013498266Sopenharmony_ci curl_socket_t sock; 233113498266Sopenharmony_ci bool want_recv, want_send; 233213498266Sopenharmony_ci 233313498266Sopenharmony_ci if(!ctx->h2) 233413498266Sopenharmony_ci return; 233513498266Sopenharmony_ci 233613498266Sopenharmony_ci sock = Curl_conn_cf_get_socket(cf, data); 233713498266Sopenharmony_ci Curl_pollset_check(data, ps, sock, &want_recv, &want_send); 233813498266Sopenharmony_ci if(want_recv || want_send) { 233913498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 234013498266Sopenharmony_ci struct cf_call_data save; 234113498266Sopenharmony_ci bool c_exhaust, s_exhaust; 234213498266Sopenharmony_ci 234313498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 234413498266Sopenharmony_ci c_exhaust = want_send && !nghttp2_session_get_remote_window_size(ctx->h2); 234513498266Sopenharmony_ci s_exhaust = want_send && stream && stream->id >= 0 && 234613498266Sopenharmony_ci !nghttp2_session_get_stream_remote_window_size(ctx->h2, 234713498266Sopenharmony_ci stream->id); 234813498266Sopenharmony_ci want_recv = (want_recv || c_exhaust || s_exhaust); 234913498266Sopenharmony_ci want_send = (!s_exhaust && want_send) || 235013498266Sopenharmony_ci (!c_exhaust && nghttp2_session_want_write(ctx->h2)); 235113498266Sopenharmony_ci 235213498266Sopenharmony_ci Curl_pollset_set(data, ps, sock, want_recv, want_send); 235313498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 235413498266Sopenharmony_ci } 235513498266Sopenharmony_ci} 235613498266Sopenharmony_ci 235713498266Sopenharmony_cistatic CURLcode cf_h2_connect(struct Curl_cfilter *cf, 235813498266Sopenharmony_ci struct Curl_easy *data, 235913498266Sopenharmony_ci bool blocking, bool *done) 236013498266Sopenharmony_ci{ 236113498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 236213498266Sopenharmony_ci CURLcode result = CURLE_OK; 236313498266Sopenharmony_ci struct cf_call_data save; 236413498266Sopenharmony_ci 236513498266Sopenharmony_ci if(cf->connected) { 236613498266Sopenharmony_ci *done = TRUE; 236713498266Sopenharmony_ci return CURLE_OK; 236813498266Sopenharmony_ci } 236913498266Sopenharmony_ci 237013498266Sopenharmony_ci /* Connect the lower filters first */ 237113498266Sopenharmony_ci if(!cf->next->connected) { 237213498266Sopenharmony_ci result = Curl_conn_cf_connect(cf->next, data, blocking, done); 237313498266Sopenharmony_ci if(result || !*done) 237413498266Sopenharmony_ci return result; 237513498266Sopenharmony_ci } 237613498266Sopenharmony_ci 237713498266Sopenharmony_ci *done = FALSE; 237813498266Sopenharmony_ci 237913498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 238013498266Sopenharmony_ci if(!ctx->h2) { 238113498266Sopenharmony_ci result = cf_h2_ctx_init(cf, data, FALSE); 238213498266Sopenharmony_ci if(result) 238313498266Sopenharmony_ci goto out; 238413498266Sopenharmony_ci } 238513498266Sopenharmony_ci 238613498266Sopenharmony_ci result = h2_progress_ingress(cf, data); 238713498266Sopenharmony_ci if(result) 238813498266Sopenharmony_ci goto out; 238913498266Sopenharmony_ci 239013498266Sopenharmony_ci /* Send out our SETTINGS and ACKs and such. If that blocks, we 239113498266Sopenharmony_ci * have it buffered and can count this filter as being connected */ 239213498266Sopenharmony_ci result = h2_progress_egress(cf, data); 239313498266Sopenharmony_ci if(result == CURLE_AGAIN) 239413498266Sopenharmony_ci result = CURLE_OK; 239513498266Sopenharmony_ci else if(result) 239613498266Sopenharmony_ci goto out; 239713498266Sopenharmony_ci 239813498266Sopenharmony_ci *done = TRUE; 239913498266Sopenharmony_ci cf->connected = TRUE; 240013498266Sopenharmony_ci result = CURLE_OK; 240113498266Sopenharmony_ci 240213498266Sopenharmony_ciout: 240313498266Sopenharmony_ci CURL_TRC_CF(data, cf, "cf_connect() -> %d, %d, ", result, *done); 240413498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 240513498266Sopenharmony_ci return result; 240613498266Sopenharmony_ci} 240713498266Sopenharmony_ci 240813498266Sopenharmony_cistatic void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data) 240913498266Sopenharmony_ci{ 241013498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 241113498266Sopenharmony_ci 241213498266Sopenharmony_ci if(ctx) { 241313498266Sopenharmony_ci struct cf_call_data save; 241413498266Sopenharmony_ci 241513498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 241613498266Sopenharmony_ci cf_h2_ctx_clear(ctx); 241713498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 241813498266Sopenharmony_ci } 241913498266Sopenharmony_ci if(cf->next) 242013498266Sopenharmony_ci cf->next->cft->do_close(cf->next, data); 242113498266Sopenharmony_ci} 242213498266Sopenharmony_ci 242313498266Sopenharmony_cistatic void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data) 242413498266Sopenharmony_ci{ 242513498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 242613498266Sopenharmony_ci 242713498266Sopenharmony_ci (void)data; 242813498266Sopenharmony_ci if(ctx) { 242913498266Sopenharmony_ci cf_h2_ctx_free(ctx); 243013498266Sopenharmony_ci cf->ctx = NULL; 243113498266Sopenharmony_ci } 243213498266Sopenharmony_ci} 243313498266Sopenharmony_ci 243413498266Sopenharmony_cistatic CURLcode http2_data_pause(struct Curl_cfilter *cf, 243513498266Sopenharmony_ci struct Curl_easy *data, 243613498266Sopenharmony_ci bool pause) 243713498266Sopenharmony_ci{ 243813498266Sopenharmony_ci#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 243913498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 244013498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 244113498266Sopenharmony_ci 244213498266Sopenharmony_ci DEBUGASSERT(data); 244313498266Sopenharmony_ci if(ctx && ctx->h2 && stream) { 244413498266Sopenharmony_ci uint32_t window = pause? 0 : stream->local_window_size; 244513498266Sopenharmony_ci 244613498266Sopenharmony_ci int rv = nghttp2_session_set_local_window_size(ctx->h2, 244713498266Sopenharmony_ci NGHTTP2_FLAG_NONE, 244813498266Sopenharmony_ci stream->id, 244913498266Sopenharmony_ci window); 245013498266Sopenharmony_ci if(rv) { 245113498266Sopenharmony_ci failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)", 245213498266Sopenharmony_ci nghttp2_strerror(rv), rv); 245313498266Sopenharmony_ci return CURLE_HTTP2; 245413498266Sopenharmony_ci } 245513498266Sopenharmony_ci 245613498266Sopenharmony_ci if(!pause) 245713498266Sopenharmony_ci drain_stream(cf, data, stream); 245813498266Sopenharmony_ci 245913498266Sopenharmony_ci /* attempt to send the window update */ 246013498266Sopenharmony_ci (void)h2_progress_egress(cf, data); 246113498266Sopenharmony_ci 246213498266Sopenharmony_ci if(!pause) { 246313498266Sopenharmony_ci /* Unpausing a h2 transfer, requires it to be run again. The server 246413498266Sopenharmony_ci * may send new DATA on us increasing the flow window, and it may 246513498266Sopenharmony_ci * not. We may have already buffered and exhausted the new window 246613498266Sopenharmony_ci * by operating on things in flight during the handling of other 246713498266Sopenharmony_ci * transfers. */ 246813498266Sopenharmony_ci drain_stream(cf, data, stream); 246913498266Sopenharmony_ci Curl_expire(data, 0, EXPIRE_RUN_NOW); 247013498266Sopenharmony_ci } 247113498266Sopenharmony_ci DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u", 247213498266Sopenharmony_ci window, stream->id)); 247313498266Sopenharmony_ci 247413498266Sopenharmony_ci#ifdef DEBUGBUILD 247513498266Sopenharmony_ci { 247613498266Sopenharmony_ci /* read out the stream local window again */ 247713498266Sopenharmony_ci uint32_t window2 = 247813498266Sopenharmony_ci nghttp2_session_get_stream_local_window_size(ctx->h2, 247913498266Sopenharmony_ci stream->id); 248013498266Sopenharmony_ci DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u", 248113498266Sopenharmony_ci window2, stream->id)); 248213498266Sopenharmony_ci } 248313498266Sopenharmony_ci#endif 248413498266Sopenharmony_ci } 248513498266Sopenharmony_ci#endif 248613498266Sopenharmony_ci return CURLE_OK; 248713498266Sopenharmony_ci} 248813498266Sopenharmony_ci 248913498266Sopenharmony_cistatic CURLcode cf_h2_cntrl(struct Curl_cfilter *cf, 249013498266Sopenharmony_ci struct Curl_easy *data, 249113498266Sopenharmony_ci int event, int arg1, void *arg2) 249213498266Sopenharmony_ci{ 249313498266Sopenharmony_ci CURLcode result = CURLE_OK; 249413498266Sopenharmony_ci struct cf_call_data save; 249513498266Sopenharmony_ci 249613498266Sopenharmony_ci (void)arg2; 249713498266Sopenharmony_ci 249813498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 249913498266Sopenharmony_ci switch(event) { 250013498266Sopenharmony_ci case CF_CTRL_DATA_SETUP: 250113498266Sopenharmony_ci break; 250213498266Sopenharmony_ci case CF_CTRL_DATA_PAUSE: 250313498266Sopenharmony_ci result = http2_data_pause(cf, data, (arg1 != 0)); 250413498266Sopenharmony_ci break; 250513498266Sopenharmony_ci case CF_CTRL_DATA_DONE_SEND: 250613498266Sopenharmony_ci result = http2_data_done_send(cf, data); 250713498266Sopenharmony_ci break; 250813498266Sopenharmony_ci case CF_CTRL_DATA_DETACH: 250913498266Sopenharmony_ci http2_data_done(cf, data, TRUE); 251013498266Sopenharmony_ci break; 251113498266Sopenharmony_ci case CF_CTRL_DATA_DONE: 251213498266Sopenharmony_ci http2_data_done(cf, data, arg1 != 0); 251313498266Sopenharmony_ci break; 251413498266Sopenharmony_ci default: 251513498266Sopenharmony_ci break; 251613498266Sopenharmony_ci } 251713498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 251813498266Sopenharmony_ci return result; 251913498266Sopenharmony_ci} 252013498266Sopenharmony_ci 252113498266Sopenharmony_cistatic bool cf_h2_data_pending(struct Curl_cfilter *cf, 252213498266Sopenharmony_ci const struct Curl_easy *data) 252313498266Sopenharmony_ci{ 252413498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 252513498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 252613498266Sopenharmony_ci 252713498266Sopenharmony_ci if(ctx && (!Curl_bufq_is_empty(&ctx->inbufq) 252813498266Sopenharmony_ci || (stream && !Curl_bufq_is_empty(&stream->sendbuf)) 252913498266Sopenharmony_ci || (stream && !Curl_bufq_is_empty(&stream->recvbuf)))) 253013498266Sopenharmony_ci return TRUE; 253113498266Sopenharmony_ci return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE; 253213498266Sopenharmony_ci} 253313498266Sopenharmony_ci 253413498266Sopenharmony_cistatic bool cf_h2_is_alive(struct Curl_cfilter *cf, 253513498266Sopenharmony_ci struct Curl_easy *data, 253613498266Sopenharmony_ci bool *input_pending) 253713498266Sopenharmony_ci{ 253813498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 253913498266Sopenharmony_ci CURLcode result; 254013498266Sopenharmony_ci struct cf_call_data save; 254113498266Sopenharmony_ci 254213498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 254313498266Sopenharmony_ci result = (ctx && ctx->h2 && http2_connisalive(cf, data, input_pending)); 254413498266Sopenharmony_ci CURL_TRC_CF(data, cf, "conn alive -> %d, input_pending=%d", 254513498266Sopenharmony_ci result, *input_pending); 254613498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 254713498266Sopenharmony_ci return result; 254813498266Sopenharmony_ci} 254913498266Sopenharmony_ci 255013498266Sopenharmony_cistatic CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf, 255113498266Sopenharmony_ci struct Curl_easy *data) 255213498266Sopenharmony_ci{ 255313498266Sopenharmony_ci CURLcode result; 255413498266Sopenharmony_ci struct cf_call_data save; 255513498266Sopenharmony_ci 255613498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 255713498266Sopenharmony_ci result = http2_send_ping(cf, data); 255813498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 255913498266Sopenharmony_ci return result; 256013498266Sopenharmony_ci} 256113498266Sopenharmony_ci 256213498266Sopenharmony_cistatic CURLcode cf_h2_query(struct Curl_cfilter *cf, 256313498266Sopenharmony_ci struct Curl_easy *data, 256413498266Sopenharmony_ci int query, int *pres1, void *pres2) 256513498266Sopenharmony_ci{ 256613498266Sopenharmony_ci struct cf_h2_ctx *ctx = cf->ctx; 256713498266Sopenharmony_ci struct cf_call_data save; 256813498266Sopenharmony_ci size_t effective_max; 256913498266Sopenharmony_ci 257013498266Sopenharmony_ci switch(query) { 257113498266Sopenharmony_ci case CF_QUERY_MAX_CONCURRENT: 257213498266Sopenharmony_ci DEBUGASSERT(pres1); 257313498266Sopenharmony_ci 257413498266Sopenharmony_ci CF_DATA_SAVE(save, cf, data); 257513498266Sopenharmony_ci if(nghttp2_session_check_request_allowed(ctx->h2) == 0) { 257613498266Sopenharmony_ci /* the limit is what we have in use right now */ 257713498266Sopenharmony_ci effective_max = CONN_INUSE(cf->conn); 257813498266Sopenharmony_ci } 257913498266Sopenharmony_ci else { 258013498266Sopenharmony_ci effective_max = ctx->max_concurrent_streams; 258113498266Sopenharmony_ci } 258213498266Sopenharmony_ci *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max; 258313498266Sopenharmony_ci CF_DATA_RESTORE(cf, save); 258413498266Sopenharmony_ci return CURLE_OK; 258513498266Sopenharmony_ci default: 258613498266Sopenharmony_ci break; 258713498266Sopenharmony_ci } 258813498266Sopenharmony_ci return cf->next? 258913498266Sopenharmony_ci cf->next->cft->query(cf->next, data, query, pres1, pres2) : 259013498266Sopenharmony_ci CURLE_UNKNOWN_OPTION; 259113498266Sopenharmony_ci} 259213498266Sopenharmony_ci 259313498266Sopenharmony_cistruct Curl_cftype Curl_cft_nghttp2 = { 259413498266Sopenharmony_ci "HTTP/2", 259513498266Sopenharmony_ci CF_TYPE_MULTIPLEX, 259613498266Sopenharmony_ci CURL_LOG_LVL_NONE, 259713498266Sopenharmony_ci cf_h2_destroy, 259813498266Sopenharmony_ci cf_h2_connect, 259913498266Sopenharmony_ci cf_h2_close, 260013498266Sopenharmony_ci Curl_cf_def_get_host, 260113498266Sopenharmony_ci cf_h2_adjust_pollset, 260213498266Sopenharmony_ci cf_h2_data_pending, 260313498266Sopenharmony_ci cf_h2_send, 260413498266Sopenharmony_ci cf_h2_recv, 260513498266Sopenharmony_ci cf_h2_cntrl, 260613498266Sopenharmony_ci cf_h2_is_alive, 260713498266Sopenharmony_ci cf_h2_keep_alive, 260813498266Sopenharmony_ci cf_h2_query, 260913498266Sopenharmony_ci}; 261013498266Sopenharmony_ci 261113498266Sopenharmony_cistatic CURLcode http2_cfilter_add(struct Curl_cfilter **pcf, 261213498266Sopenharmony_ci struct Curl_easy *data, 261313498266Sopenharmony_ci struct connectdata *conn, 261413498266Sopenharmony_ci int sockindex) 261513498266Sopenharmony_ci{ 261613498266Sopenharmony_ci struct Curl_cfilter *cf = NULL; 261713498266Sopenharmony_ci struct cf_h2_ctx *ctx; 261813498266Sopenharmony_ci CURLcode result = CURLE_OUT_OF_MEMORY; 261913498266Sopenharmony_ci 262013498266Sopenharmony_ci DEBUGASSERT(data->conn); 262113498266Sopenharmony_ci ctx = calloc(1, sizeof(*ctx)); 262213498266Sopenharmony_ci if(!ctx) 262313498266Sopenharmony_ci goto out; 262413498266Sopenharmony_ci 262513498266Sopenharmony_ci result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx); 262613498266Sopenharmony_ci if(result) 262713498266Sopenharmony_ci goto out; 262813498266Sopenharmony_ci 262913498266Sopenharmony_ci Curl_conn_cf_add(data, conn, sockindex, cf); 263013498266Sopenharmony_ci result = CURLE_OK; 263113498266Sopenharmony_ci 263213498266Sopenharmony_ciout: 263313498266Sopenharmony_ci if(result) 263413498266Sopenharmony_ci cf_h2_ctx_free(ctx); 263513498266Sopenharmony_ci *pcf = result? NULL : cf; 263613498266Sopenharmony_ci return result; 263713498266Sopenharmony_ci} 263813498266Sopenharmony_ci 263913498266Sopenharmony_cistatic CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf, 264013498266Sopenharmony_ci struct Curl_easy *data) 264113498266Sopenharmony_ci{ 264213498266Sopenharmony_ci struct Curl_cfilter *cf_h2 = NULL; 264313498266Sopenharmony_ci struct cf_h2_ctx *ctx; 264413498266Sopenharmony_ci CURLcode result = CURLE_OUT_OF_MEMORY; 264513498266Sopenharmony_ci 264613498266Sopenharmony_ci (void)data; 264713498266Sopenharmony_ci ctx = calloc(1, sizeof(*ctx)); 264813498266Sopenharmony_ci if(!ctx) 264913498266Sopenharmony_ci goto out; 265013498266Sopenharmony_ci 265113498266Sopenharmony_ci result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx); 265213498266Sopenharmony_ci if(result) 265313498266Sopenharmony_ci goto out; 265413498266Sopenharmony_ci 265513498266Sopenharmony_ci Curl_conn_cf_insert_after(cf, cf_h2); 265613498266Sopenharmony_ci result = CURLE_OK; 265713498266Sopenharmony_ci 265813498266Sopenharmony_ciout: 265913498266Sopenharmony_ci if(result) 266013498266Sopenharmony_ci cf_h2_ctx_free(ctx); 266113498266Sopenharmony_ci return result; 266213498266Sopenharmony_ci} 266313498266Sopenharmony_ci 266413498266Sopenharmony_cistatic bool Curl_cf_is_http2(struct Curl_cfilter *cf, 266513498266Sopenharmony_ci const struct Curl_easy *data) 266613498266Sopenharmony_ci{ 266713498266Sopenharmony_ci (void)data; 266813498266Sopenharmony_ci for(; cf; cf = cf->next) { 266913498266Sopenharmony_ci if(cf->cft == &Curl_cft_nghttp2) 267013498266Sopenharmony_ci return TRUE; 267113498266Sopenharmony_ci if(cf->cft->flags & CF_TYPE_IP_CONNECT) 267213498266Sopenharmony_ci return FALSE; 267313498266Sopenharmony_ci } 267413498266Sopenharmony_ci return FALSE; 267513498266Sopenharmony_ci} 267613498266Sopenharmony_ci 267713498266Sopenharmony_cibool Curl_conn_is_http2(const struct Curl_easy *data, 267813498266Sopenharmony_ci const struct connectdata *conn, 267913498266Sopenharmony_ci int sockindex) 268013498266Sopenharmony_ci{ 268113498266Sopenharmony_ci return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE; 268213498266Sopenharmony_ci} 268313498266Sopenharmony_ci 268413498266Sopenharmony_cibool Curl_http2_may_switch(struct Curl_easy *data, 268513498266Sopenharmony_ci struct connectdata *conn, 268613498266Sopenharmony_ci int sockindex) 268713498266Sopenharmony_ci{ 268813498266Sopenharmony_ci (void)sockindex; 268913498266Sopenharmony_ci if(!Curl_conn_is_http2(data, conn, sockindex) && 269013498266Sopenharmony_ci data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) { 269113498266Sopenharmony_ci#ifndef CURL_DISABLE_PROXY 269213498266Sopenharmony_ci if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) { 269313498266Sopenharmony_ci /* We don't support HTTP/2 proxies yet. Also it's debatable 269413498266Sopenharmony_ci whether or not this setting should apply to HTTP/2 proxies. */ 269513498266Sopenharmony_ci infof(data, "Ignoring HTTP/2 prior knowledge due to proxy"); 269613498266Sopenharmony_ci return FALSE; 269713498266Sopenharmony_ci } 269813498266Sopenharmony_ci#endif 269913498266Sopenharmony_ci return TRUE; 270013498266Sopenharmony_ci } 270113498266Sopenharmony_ci return FALSE; 270213498266Sopenharmony_ci} 270313498266Sopenharmony_ci 270413498266Sopenharmony_ciCURLcode Curl_http2_switch(struct Curl_easy *data, 270513498266Sopenharmony_ci struct connectdata *conn, int sockindex) 270613498266Sopenharmony_ci{ 270713498266Sopenharmony_ci struct Curl_cfilter *cf; 270813498266Sopenharmony_ci CURLcode result; 270913498266Sopenharmony_ci 271013498266Sopenharmony_ci DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); 271113498266Sopenharmony_ci DEBUGF(infof(data, "switching to HTTP/2")); 271213498266Sopenharmony_ci 271313498266Sopenharmony_ci result = http2_cfilter_add(&cf, data, conn, sockindex); 271413498266Sopenharmony_ci if(result) 271513498266Sopenharmony_ci return result; 271613498266Sopenharmony_ci 271713498266Sopenharmony_ci result = cf_h2_ctx_init(cf, data, FALSE); 271813498266Sopenharmony_ci if(result) 271913498266Sopenharmony_ci return result; 272013498266Sopenharmony_ci 272113498266Sopenharmony_ci conn->httpversion = 20; /* we know we're on HTTP/2 now */ 272213498266Sopenharmony_ci conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ 272313498266Sopenharmony_ci conn->bundle->multiuse = BUNDLE_MULTIPLEX; 272413498266Sopenharmony_ci Curl_multi_connchanged(data->multi); 272513498266Sopenharmony_ci 272613498266Sopenharmony_ci if(cf->next) { 272713498266Sopenharmony_ci bool done; 272813498266Sopenharmony_ci return Curl_conn_cf_connect(cf, data, FALSE, &done); 272913498266Sopenharmony_ci } 273013498266Sopenharmony_ci return CURLE_OK; 273113498266Sopenharmony_ci} 273213498266Sopenharmony_ci 273313498266Sopenharmony_ciCURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data) 273413498266Sopenharmony_ci{ 273513498266Sopenharmony_ci struct Curl_cfilter *cf_h2; 273613498266Sopenharmony_ci CURLcode result; 273713498266Sopenharmony_ci 273813498266Sopenharmony_ci DEBUGASSERT(!Curl_cf_is_http2(cf, data)); 273913498266Sopenharmony_ci 274013498266Sopenharmony_ci result = http2_cfilter_insert_after(cf, data); 274113498266Sopenharmony_ci if(result) 274213498266Sopenharmony_ci return result; 274313498266Sopenharmony_ci 274413498266Sopenharmony_ci cf_h2 = cf->next; 274513498266Sopenharmony_ci result = cf_h2_ctx_init(cf_h2, data, FALSE); 274613498266Sopenharmony_ci if(result) 274713498266Sopenharmony_ci return result; 274813498266Sopenharmony_ci 274913498266Sopenharmony_ci cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */ 275013498266Sopenharmony_ci cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ 275113498266Sopenharmony_ci cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX; 275213498266Sopenharmony_ci Curl_multi_connchanged(data->multi); 275313498266Sopenharmony_ci 275413498266Sopenharmony_ci if(cf_h2->next) { 275513498266Sopenharmony_ci bool done; 275613498266Sopenharmony_ci return Curl_conn_cf_connect(cf_h2, data, FALSE, &done); 275713498266Sopenharmony_ci } 275813498266Sopenharmony_ci return CURLE_OK; 275913498266Sopenharmony_ci} 276013498266Sopenharmony_ci 276113498266Sopenharmony_ciCURLcode Curl_http2_upgrade(struct Curl_easy *data, 276213498266Sopenharmony_ci struct connectdata *conn, int sockindex, 276313498266Sopenharmony_ci const char *mem, size_t nread) 276413498266Sopenharmony_ci{ 276513498266Sopenharmony_ci struct Curl_cfilter *cf; 276613498266Sopenharmony_ci struct cf_h2_ctx *ctx; 276713498266Sopenharmony_ci CURLcode result; 276813498266Sopenharmony_ci 276913498266Sopenharmony_ci DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex)); 277013498266Sopenharmony_ci DEBUGF(infof(data, "upgrading to HTTP/2")); 277113498266Sopenharmony_ci DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED); 277213498266Sopenharmony_ci 277313498266Sopenharmony_ci result = http2_cfilter_add(&cf, data, conn, sockindex); 277413498266Sopenharmony_ci if(result) 277513498266Sopenharmony_ci return result; 277613498266Sopenharmony_ci 277713498266Sopenharmony_ci DEBUGASSERT(cf->cft == &Curl_cft_nghttp2); 277813498266Sopenharmony_ci ctx = cf->ctx; 277913498266Sopenharmony_ci 278013498266Sopenharmony_ci result = cf_h2_ctx_init(cf, data, TRUE); 278113498266Sopenharmony_ci if(result) 278213498266Sopenharmony_ci return result; 278313498266Sopenharmony_ci 278413498266Sopenharmony_ci if(nread > 0) { 278513498266Sopenharmony_ci /* Remaining data from the protocol switch reply is already using 278613498266Sopenharmony_ci * the switched protocol, ie. HTTP/2. We add that to the network 278713498266Sopenharmony_ci * inbufq. */ 278813498266Sopenharmony_ci ssize_t copied; 278913498266Sopenharmony_ci 279013498266Sopenharmony_ci copied = Curl_bufq_write(&ctx->inbufq, 279113498266Sopenharmony_ci (const unsigned char *)mem, nread, &result); 279213498266Sopenharmony_ci if(copied < 0) { 279313498266Sopenharmony_ci failf(data, "error on copying HTTP Upgrade response: %d", result); 279413498266Sopenharmony_ci return CURLE_RECV_ERROR; 279513498266Sopenharmony_ci } 279613498266Sopenharmony_ci if((size_t)copied < nread) { 279713498266Sopenharmony_ci failf(data, "connection buffer size could not take all data " 279813498266Sopenharmony_ci "from HTTP Upgrade response header: copied=%zd, datalen=%zu", 279913498266Sopenharmony_ci copied, nread); 280013498266Sopenharmony_ci return CURLE_HTTP2; 280113498266Sopenharmony_ci } 280213498266Sopenharmony_ci infof(data, "Copied HTTP/2 data in stream buffer to connection buffer" 280313498266Sopenharmony_ci " after upgrade: len=%zu", nread); 280413498266Sopenharmony_ci } 280513498266Sopenharmony_ci 280613498266Sopenharmony_ci conn->httpversion = 20; /* we know we're on HTTP/2 now */ 280713498266Sopenharmony_ci conn->bits.multiplex = TRUE; /* at least potentially multiplexed */ 280813498266Sopenharmony_ci conn->bundle->multiuse = BUNDLE_MULTIPLEX; 280913498266Sopenharmony_ci Curl_multi_connchanged(data->multi); 281013498266Sopenharmony_ci 281113498266Sopenharmony_ci if(cf->next) { 281213498266Sopenharmony_ci bool done; 281313498266Sopenharmony_ci return Curl_conn_cf_connect(cf, data, FALSE, &done); 281413498266Sopenharmony_ci } 281513498266Sopenharmony_ci return CURLE_OK; 281613498266Sopenharmony_ci} 281713498266Sopenharmony_ci 281813498266Sopenharmony_ci/* Only call this function for a transfer that already got an HTTP/2 281913498266Sopenharmony_ci CURLE_HTTP2_STREAM error! */ 282013498266Sopenharmony_cibool Curl_h2_http_1_1_error(struct Curl_easy *data) 282113498266Sopenharmony_ci{ 282213498266Sopenharmony_ci struct stream_ctx *stream = H2_STREAM_CTX(data); 282313498266Sopenharmony_ci return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED); 282413498266Sopenharmony_ci} 282513498266Sopenharmony_ci 282613498266Sopenharmony_ci#else /* !USE_NGHTTP2 */ 282713498266Sopenharmony_ci 282813498266Sopenharmony_ci/* Satisfy external references even if http2 is not compiled in. */ 282913498266Sopenharmony_ci#include <curl/curl.h> 283013498266Sopenharmony_ci 283113498266Sopenharmony_cichar *curl_pushheader_bynum(struct curl_pushheaders *h, size_t num) 283213498266Sopenharmony_ci{ 283313498266Sopenharmony_ci (void) h; 283413498266Sopenharmony_ci (void) num; 283513498266Sopenharmony_ci return NULL; 283613498266Sopenharmony_ci} 283713498266Sopenharmony_ci 283813498266Sopenharmony_cichar *curl_pushheader_byname(struct curl_pushheaders *h, const char *header) 283913498266Sopenharmony_ci{ 284013498266Sopenharmony_ci (void) h; 284113498266Sopenharmony_ci (void) header; 284213498266Sopenharmony_ci return NULL; 284313498266Sopenharmony_ci} 284413498266Sopenharmony_ci 284513498266Sopenharmony_ci#endif /* USE_NGHTTP2 */ 2846