quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

cf-h2-proxy.c (49961B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "curl_setup.h"
     26 
     27 #if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
     28 
     29 #include <nghttp2/nghttp2.h>
     30 #include "urldata.h"
     31 #include "url.h"
     32 #include "cfilters.h"
     33 #include "connect.h"
     34 #include "curl_trc.h"
     35 #include "bufq.h"
     36 #include "curlx/dynbuf.h"
     37 #include "dynhds.h"
     38 #include "http1.h"
     39 #include "http2.h"
     40 #include "http_proxy.h"
     41 #include "multiif.h"
     42 #include "sendf.h"
     43 #include "cf-h2-proxy.h"
     44 
     45 /* The last 3 #include files should be in this order */
     46 #include "curl_printf.h"
     47 #include "curl_memory.h"
     48 #include "memdebug.h"
     49 
     50 #define PROXY_H2_CHUNK_SIZE  (16*1024)
     51 
     52 #define PROXY_HTTP2_HUGE_WINDOW_SIZE (100 * 1024 * 1024)
     53 #define H2_TUNNEL_WINDOW_SIZE        (10 * 1024 * 1024)
     54 
     55 #define PROXY_H2_NW_RECV_CHUNKS  (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE)
     56 #define PROXY_H2_NW_SEND_CHUNKS   1
     57 
     58 #define H2_TUNNEL_RECV_CHUNKS   (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE)
     59 #define H2_TUNNEL_SEND_CHUNKS   ((128 * 1024) / PROXY_H2_CHUNK_SIZE)
     60 
     61 
     62 typedef enum {
     63     H2_TUNNEL_INIT,     /* init/default/no tunnel state */
     64     H2_TUNNEL_CONNECT,  /* CONNECT request is being send */
     65     H2_TUNNEL_RESPONSE, /* CONNECT response received completely */
     66     H2_TUNNEL_ESTABLISHED,
     67     H2_TUNNEL_FAILED
     68 } h2_tunnel_state;
     69 
     70 struct tunnel_stream {
     71   struct http_resp *resp;
     72   struct bufq recvbuf;
     73   struct bufq sendbuf;
     74   char *authority;
     75   int32_t stream_id;
     76   uint32_t error;
     77   h2_tunnel_state state;
     78   BIT(has_final_response);
     79   BIT(closed);
     80   BIT(reset);
     81 };
     82 
     83 static CURLcode tunnel_stream_init(struct Curl_cfilter *cf,
     84                                    struct tunnel_stream *ts)
     85 {
     86   const char *hostname;
     87   int port;
     88   bool ipv6_ip;
     89   CURLcode result;
     90 
     91   ts->state = H2_TUNNEL_INIT;
     92   ts->stream_id = -1;
     93   Curl_bufq_init2(&ts->recvbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
     94                   BUFQ_OPT_SOFT_LIMIT);
     95   Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
     96 
     97   result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
     98   if(result)
     99     return result;
    100 
    101   ts->authority = /* host:port with IPv6 support */
    102     aprintf("%s%s%s:%d", ipv6_ip ? "[":"", hostname,
    103             ipv6_ip ? "]" : "", port);
    104   if(!ts->authority)
    105     return CURLE_OUT_OF_MEMORY;
    106 
    107   return CURLE_OK;
    108 }
    109 
    110 static void tunnel_stream_clear(struct tunnel_stream *ts)
    111 {
    112   Curl_http_resp_free(ts->resp);
    113   Curl_bufq_free(&ts->recvbuf);
    114   Curl_bufq_free(&ts->sendbuf);
    115   Curl_safefree(ts->authority);
    116   memset(ts, 0, sizeof(*ts));
    117   ts->state = H2_TUNNEL_INIT;
    118 }
    119 
    120 static void h2_tunnel_go_state(struct Curl_cfilter *cf,
    121                                struct tunnel_stream *ts,
    122                                h2_tunnel_state new_state,
    123                                struct Curl_easy *data)
    124 {
    125   (void)cf;
    126 
    127   if(ts->state == new_state)
    128     return;
    129   /* leaving this one */
    130   switch(ts->state) {
    131   case H2_TUNNEL_CONNECT:
    132     data->req.ignorebody = FALSE;
    133     break;
    134   default:
    135     break;
    136   }
    137   /* entering this one */
    138   switch(new_state) {
    139   case H2_TUNNEL_INIT:
    140     CURL_TRC_CF(data, cf, "[%d] new tunnel state 'init'", ts->stream_id);
    141     tunnel_stream_clear(ts);
    142     break;
    143 
    144   case H2_TUNNEL_CONNECT:
    145     CURL_TRC_CF(data, cf, "[%d] new tunnel state 'connect'", ts->stream_id);
    146     ts->state = H2_TUNNEL_CONNECT;
    147     break;
    148 
    149   case H2_TUNNEL_RESPONSE:
    150     CURL_TRC_CF(data, cf, "[%d] new tunnel state 'response'", ts->stream_id);
    151     ts->state = H2_TUNNEL_RESPONSE;
    152     break;
    153 
    154   case H2_TUNNEL_ESTABLISHED:
    155     CURL_TRC_CF(data, cf, "[%d] new tunnel state 'established'",
    156                 ts->stream_id);
    157     infof(data, "CONNECT phase completed");
    158     data->state.authproxy.done = TRUE;
    159     data->state.authproxy.multipass = FALSE;
    160     FALLTHROUGH();
    161   case H2_TUNNEL_FAILED:
    162     if(new_state == H2_TUNNEL_FAILED)
    163       CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id);
    164     ts->state = new_state;
    165     /* If a proxy-authorization header was used for the proxy, then we should
    166        make sure that it is not accidentally used for the document request
    167        after we have connected. So let's free and clear it here. */
    168     Curl_safefree(data->state.aptr.proxyuserpwd);
    169     break;
    170   }
    171 }
    172 
    173 struct cf_h2_proxy_ctx {
    174   nghttp2_session *h2;
    175   /* The easy handle used in the current filter call, cleared at return */
    176   struct cf_call_data call_data;
    177 
    178   struct bufq inbufq;  /* network receive buffer */
    179   struct bufq outbufq; /* network send buffer */
    180 
    181   struct tunnel_stream tunnel; /* our tunnel CONNECT stream */
    182   int32_t goaway_error;
    183   int32_t last_stream_id;
    184   BIT(conn_closed);
    185   BIT(rcvd_goaway);
    186   BIT(sent_goaway);
    187   BIT(nw_out_blocked);
    188 };
    189 
    190 /* How to access `call_data` from a cf_h2 filter */
    191 #undef CF_CTX_CALL_DATA
    192 #define CF_CTX_CALL_DATA(cf)  \
    193   ((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data
    194 
    195 static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx)
    196 {
    197   struct cf_call_data save = ctx->call_data;
    198 
    199   if(ctx->h2) {
    200     nghttp2_session_del(ctx->h2);
    201   }
    202   Curl_bufq_free(&ctx->inbufq);
    203   Curl_bufq_free(&ctx->outbufq);
    204   tunnel_stream_clear(&ctx->tunnel);
    205   memset(ctx, 0, sizeof(*ctx));
    206   ctx->call_data = save;
    207 }
    208 
    209 static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
    210 {
    211   if(ctx) {
    212     cf_h2_proxy_ctx_clear(ctx);
    213     free(ctx);
    214   }
    215 }
    216 
    217 static void drain_tunnel(struct Curl_cfilter *cf,
    218                          struct Curl_easy *data,
    219                          struct tunnel_stream *tunnel)
    220 {
    221   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    222   (void)cf;
    223   if(!tunnel->closed && !tunnel->reset &&
    224      !Curl_bufq_is_empty(&ctx->tunnel.sendbuf))
    225     Curl_multi_mark_dirty(data);
    226 }
    227 
    228 static CURLcode proxy_h2_nw_out_writer(void *writer_ctx,
    229                                        const unsigned char *buf, size_t buflen,
    230                                        size_t *pnwritten)
    231 {
    232   struct Curl_cfilter *cf = writer_ctx;
    233   *pnwritten = 0;
    234   if(cf) {
    235     struct Curl_easy *data = CF_DATA_CURRENT(cf);
    236     CURLcode result;
    237     result = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen,
    238                                FALSE, pnwritten);
    239     CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %d, %zu",
    240                 buflen, result, *pnwritten);
    241     return result;
    242   }
    243   return CURLE_FAILED_INIT;
    244 }
    245 
    246 static int proxy_h2_client_new(struct Curl_cfilter *cf,
    247                                nghttp2_session_callbacks *cbs)
    248 {
    249   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    250   nghttp2_option *o;
    251   nghttp2_mem mem = {NULL, Curl_nghttp2_malloc, Curl_nghttp2_free,
    252                      Curl_nghttp2_calloc, Curl_nghttp2_realloc};
    253 
    254   int rc = nghttp2_option_new(&o);
    255   if(rc)
    256     return rc;
    257   /* We handle window updates ourself to enforce buffer limits */
    258   nghttp2_option_set_no_auto_window_update(o, 1);
    259 #if NGHTTP2_VERSION_NUM >= 0x013200
    260   /* with 1.50.0 */
    261   /* turn off RFC 9113 leading and trailing white spaces validation against
    262      HTTP field value. */
    263   nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
    264 #endif
    265   rc = nghttp2_session_client_new3(&ctx->h2, cbs, cf, o, &mem);
    266   nghttp2_option_del(o);
    267   return rc;
    268 }
    269 
    270 static ssize_t on_session_send(nghttp2_session *h2,
    271                                const uint8_t *buf, size_t blen,
    272                                int flags, void *userp);
    273 static int proxy_h2_on_frame_recv(nghttp2_session *session,
    274                                   const nghttp2_frame *frame,
    275                                   void *userp);
    276 #ifndef CURL_DISABLE_VERBOSE_STRINGS
    277 static int proxy_h2_on_frame_send(nghttp2_session *session,
    278                                   const nghttp2_frame *frame,
    279                                   void *userp);
    280 #endif
    281 static int proxy_h2_on_stream_close(nghttp2_session *session,
    282                                     int32_t stream_id,
    283                                     uint32_t error_code, void *userp);
    284 static int proxy_h2_on_header(nghttp2_session *session,
    285                               const nghttp2_frame *frame,
    286                               const uint8_t *name, size_t namelen,
    287                               const uint8_t *value, size_t valuelen,
    288                               uint8_t flags,
    289                               void *userp);
    290 static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
    291                                 int32_t stream_id,
    292                                 const uint8_t *mem, size_t len, void *userp);
    293 
    294 /*
    295  * Initialize the cfilter context
    296  */
    297 static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf,
    298                                      struct Curl_easy *data)
    299 {
    300   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    301   CURLcode result = CURLE_OUT_OF_MEMORY;
    302   nghttp2_session_callbacks *cbs = NULL;
    303   int rc;
    304 
    305   DEBUGASSERT(!ctx->h2);
    306   memset(&ctx->tunnel, 0, sizeof(ctx->tunnel));
    307 
    308   Curl_bufq_init(&ctx->inbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS);
    309   Curl_bufq_init(&ctx->outbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS);
    310 
    311   if(tunnel_stream_init(cf, &ctx->tunnel))
    312     goto out;
    313 
    314   rc = nghttp2_session_callbacks_new(&cbs);
    315   if(rc) {
    316     failf(data, "Couldn't initialize nghttp2 callbacks");
    317     goto out;
    318   }
    319 
    320   nghttp2_session_callbacks_set_send_callback(cbs, on_session_send);
    321   nghttp2_session_callbacks_set_on_frame_recv_callback(
    322     cbs, proxy_h2_on_frame_recv);
    323 #ifndef CURL_DISABLE_VERBOSE_STRINGS
    324   nghttp2_session_callbacks_set_on_frame_send_callback(cbs,
    325                                                        proxy_h2_on_frame_send);
    326 #endif
    327   nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
    328     cbs, tunnel_recv_callback);
    329   nghttp2_session_callbacks_set_on_stream_close_callback(
    330     cbs, proxy_h2_on_stream_close);
    331   nghttp2_session_callbacks_set_on_header_callback(cbs, proxy_h2_on_header);
    332 
    333   /* The nghttp2 session is not yet setup, do it */
    334   rc = proxy_h2_client_new(cf, cbs);
    335   if(rc) {
    336     failf(data, "Couldn't initialize nghttp2");
    337     goto out;
    338   }
    339 
    340   {
    341     nghttp2_settings_entry iv[3];
    342 
    343     iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
    344     iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
    345     iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
    346     iv[1].value = H2_TUNNEL_WINDOW_SIZE;
    347     iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
    348     iv[2].value = 0;
    349     rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, 3);
    350     if(rc) {
    351       failf(data, "nghttp2_submit_settings() failed: %s(%d)",
    352             nghttp2_strerror(rc), rc);
    353       result = CURLE_HTTP2;
    354       goto out;
    355     }
    356   }
    357 
    358   rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
    359                                              PROXY_HTTP2_HUGE_WINDOW_SIZE);
    360   if(rc) {
    361     failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
    362           nghttp2_strerror(rc), rc);
    363     result = CURLE_HTTP2;
    364     goto out;
    365   }
    366 
    367 
    368   /* all set, traffic will be send on connect */
    369   result = CURLE_OK;
    370 
    371 out:
    372   if(cbs)
    373     nghttp2_session_callbacks_del(cbs);
    374   CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", result);
    375   return result;
    376 }
    377 
    378 static int proxy_h2_should_close_session(struct cf_h2_proxy_ctx *ctx)
    379 {
    380   return !nghttp2_session_want_read(ctx->h2) &&
    381     !nghttp2_session_want_write(ctx->h2);
    382 }
    383 
    384 static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf,
    385                                       struct Curl_easy *data)
    386 {
    387   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    388   size_t nwritten;
    389   CURLcode result;
    390 
    391   (void)data;
    392   if(Curl_bufq_is_empty(&ctx->outbufq))
    393     return CURLE_OK;
    394 
    395   result = Curl_bufq_pass(&ctx->outbufq, proxy_h2_nw_out_writer, cf,
    396                           &nwritten);
    397   if(result) {
    398     if(result == CURLE_AGAIN) {
    399       CURL_TRC_CF(data, cf, "[0] flush nw send buffer(%zu) -> EAGAIN",
    400                   Curl_bufq_len(&ctx->outbufq));
    401       ctx->nw_out_blocked = 1;
    402     }
    403     return result;
    404   }
    405   CURL_TRC_CF(data, cf, "[0] nw send buffer flushed");
    406   return Curl_bufq_is_empty(&ctx->outbufq) ? CURLE_OK : CURLE_AGAIN;
    407 }
    408 
    409 /*
    410  * Processes pending input left in network input buffer.
    411  * This function returns 0 if it succeeds, or -1 and error code will
    412  * be assigned to *err.
    413  */
    414 static int proxy_h2_process_pending_input(struct Curl_cfilter *cf,
    415                                           struct Curl_easy *data,
    416                                           CURLcode *err)
    417 {
    418   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    419   const unsigned char *buf;
    420   size_t blen;
    421   ssize_t rv;
    422 
    423   while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) {
    424 
    425     rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen);
    426     CURL_TRC_CF(data, cf, "[0] %zu bytes to nghttp2 -> %zd", blen, rv);
    427     if(rv < 0) {
    428       failf(data,
    429             "process_pending_input: nghttp2_session_mem_recv() returned "
    430             "%zd:%s", rv, nghttp2_strerror((int)rv));
    431       *err = CURLE_RECV_ERROR;
    432       return -1;
    433     }
    434     Curl_bufq_skip(&ctx->inbufq, (size_t)rv);
    435     if(Curl_bufq_is_empty(&ctx->inbufq)) {
    436       CURL_TRC_CF(data, cf, "[0] all data in connection buffer processed");
    437       break;
    438     }
    439     else {
    440       CURL_TRC_CF(data, cf, "[0] process_pending_input: %zu bytes left "
    441                   "in connection buffer", Curl_bufq_len(&ctx->inbufq));
    442     }
    443   }
    444 
    445   return 0;
    446 }
    447 
    448 static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf,
    449                                           struct Curl_easy *data)
    450 {
    451   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    452   CURLcode result = CURLE_OK;
    453   size_t nread;
    454 
    455   /* Process network input buffer fist */
    456   if(!Curl_bufq_is_empty(&ctx->inbufq)) {
    457     CURL_TRC_CF(data, cf, "[0] process %zu bytes in connection buffer",
    458                 Curl_bufq_len(&ctx->inbufq));
    459     if(proxy_h2_process_pending_input(cf, data, &result) < 0)
    460       return result;
    461   }
    462 
    463   /* Receive data from the "lower" filters, e.g. network until
    464    * it is time to stop or we have enough data for this stream */
    465   while(!ctx->conn_closed &&               /* not closed the connection */
    466         !ctx->tunnel.closed &&             /* nor the tunnel */
    467         Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */
    468         !Curl_bufq_is_full(&ctx->tunnel.recvbuf)) {
    469 
    470     result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread);
    471     CURL_TRC_CF(data, cf, "[0] read %zu bytes nw data -> %d, %zu",
    472                 Curl_bufq_len(&ctx->inbufq), result, nread);
    473     if(result) {
    474       if(result != CURLE_AGAIN) {
    475         failf(data, "Failed receiving HTTP2 data");
    476         return result;
    477       }
    478       break;
    479     }
    480     else if(nread == 0) {
    481       ctx->conn_closed = TRUE;
    482       break;
    483     }
    484 
    485     if(proxy_h2_process_pending_input(cf, data, &result))
    486       return result;
    487   }
    488 
    489   if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
    490     connclose(cf->conn, "GOAWAY received");
    491   }
    492 
    493   return CURLE_OK;
    494 }
    495 
    496 static CURLcode proxy_h2_progress_egress(struct Curl_cfilter *cf,
    497                                          struct Curl_easy *data)
    498 {
    499   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    500   int rv = 0;
    501 
    502   ctx->nw_out_blocked = 0;
    503   while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2))
    504     rv = nghttp2_session_send(ctx->h2);
    505 
    506   if(nghttp2_is_fatal(rv)) {
    507     CURL_TRC_CF(data, cf, "[0] nghttp2_session_send error (%s)%d",
    508                 nghttp2_strerror(rv), rv);
    509     return CURLE_SEND_ERROR;
    510   }
    511   return proxy_h2_nw_out_flush(cf, data);
    512 }
    513 
    514 static ssize_t on_session_send(nghttp2_session *h2,
    515                                const uint8_t *buf, size_t blen, int flags,
    516                                void *userp)
    517 {
    518   struct Curl_cfilter *cf = userp;
    519   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    520   struct Curl_easy *data = CF_DATA_CURRENT(cf);
    521   size_t nwritten;
    522   CURLcode result = CURLE_OK;
    523 
    524   (void)h2;
    525   (void)flags;
    526   DEBUGASSERT(data);
    527 
    528   result = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
    529                                 proxy_h2_nw_out_writer, cf, &nwritten);
    530   if(result) {
    531     if(result == CURLE_AGAIN) {
    532       ctx->nw_out_blocked = 1;
    533       return NGHTTP2_ERR_WOULDBLOCK;
    534     }
    535     failf(data, "Failed sending HTTP2 data");
    536     return NGHTTP2_ERR_CALLBACK_FAILURE;
    537   }
    538 
    539   if(!nwritten)
    540     return NGHTTP2_ERR_WOULDBLOCK;
    541 
    542   return (nwritten  > SSIZE_T_MAX) ?
    543     NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nwritten;
    544 }
    545 
    546 #ifndef CURL_DISABLE_VERBOSE_STRINGS
    547 static int proxy_h2_fr_print(const nghttp2_frame *frame,
    548                              char *buffer, size_t blen)
    549 {
    550   switch(frame->hd.type) {
    551     case NGHTTP2_DATA: {
    552       return msnprintf(buffer, blen,
    553                        "FRAME[DATA, len=%d, eos=%d, padlen=%d]",
    554                        (int)frame->hd.length,
    555                        !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM),
    556                        (int)frame->data.padlen);
    557     }
    558     case NGHTTP2_HEADERS: {
    559       return msnprintf(buffer, blen,
    560                        "FRAME[HEADERS, len=%d, hend=%d, eos=%d]",
    561                        (int)frame->hd.length,
    562                        !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
    563                        !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
    564     }
    565     case NGHTTP2_PRIORITY: {
    566       return msnprintf(buffer, blen,
    567                        "FRAME[PRIORITY, len=%d, flags=%d]",
    568                        (int)frame->hd.length, frame->hd.flags);
    569     }
    570     case NGHTTP2_RST_STREAM: {
    571       return msnprintf(buffer, blen,
    572                        "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]",
    573                        (int)frame->hd.length, frame->hd.flags,
    574                        frame->rst_stream.error_code);
    575     }
    576     case NGHTTP2_SETTINGS: {
    577       if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
    578         return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]");
    579       }
    580       return msnprintf(buffer, blen,
    581                        "FRAME[SETTINGS, len=%d]", (int)frame->hd.length);
    582     }
    583     case NGHTTP2_PUSH_PROMISE:
    584       return msnprintf(buffer, blen,
    585                        "FRAME[PUSH_PROMISE, len=%d, hend=%d]",
    586                        (int)frame->hd.length,
    587                        !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS));
    588     case NGHTTP2_PING:
    589       return msnprintf(buffer, blen,
    590                        "FRAME[PING, len=%d, ack=%d]",
    591                        (int)frame->hd.length,
    592                        frame->hd.flags & NGHTTP2_FLAG_ACK);
    593     case NGHTTP2_GOAWAY: {
    594       char scratch[128];
    595       size_t s_len = CURL_ARRAYSIZE(scratch);
    596       size_t len = (frame->goaway.opaque_data_len < s_len) ?
    597         frame->goaway.opaque_data_len : s_len-1;
    598       if(len)
    599         memcpy(scratch, frame->goaway.opaque_data, len);
    600       scratch[len] = '\0';
    601       return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', "
    602                        "last_stream=%d]", frame->goaway.error_code,
    603                        scratch, frame->goaway.last_stream_id);
    604     }
    605     case NGHTTP2_WINDOW_UPDATE: {
    606       return msnprintf(buffer, blen,
    607                        "FRAME[WINDOW_UPDATE, incr=%d]",
    608                        frame->window_update.window_size_increment);
    609     }
    610     default:
    611       return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]",
    612                        frame->hd.type, (int)frame->hd.length,
    613                        frame->hd.flags);
    614   }
    615 }
    616 
    617 static int proxy_h2_on_frame_send(nghttp2_session *session,
    618                                   const nghttp2_frame *frame,
    619                                   void *userp)
    620 {
    621   struct Curl_cfilter *cf = userp;
    622   struct Curl_easy *data = CF_DATA_CURRENT(cf);
    623 
    624   (void)session;
    625   DEBUGASSERT(data);
    626   if(data && Curl_trc_cf_is_verbose(cf, data)) {
    627     char buffer[256];
    628     int len;
    629     len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1);
    630     buffer[len] = 0;
    631     CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer);
    632   }
    633   return 0;
    634 }
    635 #endif /* !CURL_DISABLE_VERBOSE_STRINGS */
    636 
    637 static int proxy_h2_on_frame_recv(nghttp2_session *session,
    638                                   const nghttp2_frame *frame,
    639                                   void *userp)
    640 {
    641   struct Curl_cfilter *cf = userp;
    642   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    643   struct Curl_easy *data = CF_DATA_CURRENT(cf);
    644   int32_t stream_id = frame->hd.stream_id;
    645 
    646   (void)session;
    647   DEBUGASSERT(data);
    648 #ifndef CURL_DISABLE_VERBOSE_STRINGS
    649   if(Curl_trc_cf_is_verbose(cf, data)) {
    650     char buffer[256];
    651     int len;
    652     len = proxy_h2_fr_print(frame, buffer, sizeof(buffer)-1);
    653     buffer[len] = 0;
    654     CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer);
    655   }
    656 #endif /* !CURL_DISABLE_VERBOSE_STRINGS */
    657 
    658   if(!stream_id) {
    659     /* stream ID zero is for connection-oriented stuff */
    660     DEBUGASSERT(data);
    661     switch(frame->hd.type) {
    662     case NGHTTP2_SETTINGS:
    663       /* Since the initial stream window is 64K, a request might be on HOLD,
    664        * due to exhaustion. The (initial) SETTINGS may announce a much larger
    665        * window and *assume* that we treat this like a WINDOW_UPDATE. Some
    666        * servers send an explicit WINDOW_UPDATE, but not all seem to do that.
    667        * To be safe, we UNHOLD a stream in order not to stall. */
    668       if(CURL_WANT_SEND(data)) {
    669         drain_tunnel(cf, data, &ctx->tunnel);
    670       }
    671       break;
    672     case NGHTTP2_GOAWAY:
    673       ctx->rcvd_goaway = TRUE;
    674       break;
    675     default:
    676       break;
    677     }
    678     return 0;
    679   }
    680 
    681   if(stream_id != ctx->tunnel.stream_id) {
    682     CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id);
    683     return NGHTTP2_ERR_CALLBACK_FAILURE;
    684   }
    685 
    686   switch(frame->hd.type) {
    687   case NGHTTP2_HEADERS:
    688     /* nghttp2 guarantees that :status is received, and we store it to
    689        stream->status_code. Fuzzing has proven this can still be reached
    690        without status code having been set. */
    691     if(!ctx->tunnel.resp)
    692       return NGHTTP2_ERR_CALLBACK_FAILURE;
    693     /* Only final status code signals the end of header */
    694     CURL_TRC_CF(data, cf, "[%d] got http status: %d",
    695                 stream_id, ctx->tunnel.resp->status);
    696     if(!ctx->tunnel.has_final_response) {
    697       if(ctx->tunnel.resp->status / 100 != 1) {
    698         ctx->tunnel.has_final_response = TRUE;
    699       }
    700     }
    701     break;
    702   case NGHTTP2_WINDOW_UPDATE:
    703     if(CURL_WANT_SEND(data)) {
    704       drain_tunnel(cf, data, &ctx->tunnel);
    705     }
    706     break;
    707   default:
    708     break;
    709   }
    710   return 0;
    711 }
    712 
    713 static int proxy_h2_on_header(nghttp2_session *session,
    714                               const nghttp2_frame *frame,
    715                               const uint8_t *name, size_t namelen,
    716                               const uint8_t *value, size_t valuelen,
    717                               uint8_t flags,
    718                               void *userp)
    719 {
    720   struct Curl_cfilter *cf = userp;
    721   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    722   struct Curl_easy *data = CF_DATA_CURRENT(cf);
    723   int32_t stream_id = frame->hd.stream_id;
    724   CURLcode result;
    725 
    726   (void)flags;
    727   (void)data;
    728   (void)session;
    729   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
    730   if(stream_id != ctx->tunnel.stream_id) {
    731     CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: "
    732                 "%.*s: %.*s", stream_id,
    733                 (int)namelen, name, (int)valuelen, value);
    734     return NGHTTP2_ERR_CALLBACK_FAILURE;
    735   }
    736 
    737   if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
    738     return NGHTTP2_ERR_CALLBACK_FAILURE;
    739 
    740   if(ctx->tunnel.has_final_response) {
    741     /* we do not do anything with trailers for tunnel streams */
    742     return 0;
    743   }
    744 
    745   if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
    746      memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
    747     int http_status;
    748     struct http_resp *resp;
    749 
    750     /* status: always comes first, we might get more than one response,
    751      * link the previous ones for keepers */
    752     result = Curl_http_decode_status(&http_status,
    753                                     (const char *)value, valuelen);
    754     if(result)
    755       return NGHTTP2_ERR_CALLBACK_FAILURE;
    756     result = Curl_http_resp_make(&resp, http_status, NULL);
    757     if(result)
    758       return NGHTTP2_ERR_CALLBACK_FAILURE;
    759     resp->prev = ctx->tunnel.resp;
    760     ctx->tunnel.resp = resp;
    761     CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d",
    762                 stream_id, ctx->tunnel.resp->status);
    763     return 0;
    764   }
    765 
    766   if(!ctx->tunnel.resp)
    767     return NGHTTP2_ERR_CALLBACK_FAILURE;
    768 
    769   result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
    770                            (const char *)name, namelen,
    771                            (const char *)value, valuelen);
    772   if(result)
    773     return NGHTTP2_ERR_CALLBACK_FAILURE;
    774 
    775   CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s",
    776               stream_id, (int)namelen, name, (int)valuelen, value);
    777 
    778   return 0; /* 0 is successful */
    779 }
    780 
    781 static ssize_t tunnel_send_callback(nghttp2_session *session,
    782                                     int32_t stream_id,
    783                                     uint8_t *buf, size_t length,
    784                                     uint32_t *data_flags,
    785                                     nghttp2_data_source *source,
    786                                     void *userp)
    787 {
    788   struct Curl_cfilter *cf = userp;
    789   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    790   struct Curl_easy *data = CF_DATA_CURRENT(cf);
    791   struct tunnel_stream *ts;
    792   CURLcode result;
    793   size_t nread;
    794 
    795   (void)source;
    796   (void)data;
    797   (void)ctx;
    798 
    799   if(!stream_id)
    800     return NGHTTP2_ERR_INVALID_ARGUMENT;
    801 
    802   ts = nghttp2_session_get_stream_user_data(session, stream_id);
    803   if(!ts)
    804     return NGHTTP2_ERR_CALLBACK_FAILURE;
    805   DEBUGASSERT(ts == &ctx->tunnel);
    806 
    807   result = Curl_bufq_read(&ts->sendbuf, buf, length, &nread);
    808   if(result) {
    809     if(result != CURLE_AGAIN)
    810       return NGHTTP2_ERR_CALLBACK_FAILURE;
    811     return NGHTTP2_ERR_DEFERRED;
    812   }
    813   if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
    814     *data_flags = NGHTTP2_DATA_FLAG_EOF;
    815 
    816   CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd",
    817               ts->stream_id, nread);
    818   return (nread  > SSIZE_T_MAX) ?
    819     NGHTTP2_ERR_CALLBACK_FAILURE : (ssize_t)nread;
    820 }
    821 
    822 static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
    823                                 int32_t stream_id,
    824                                 const uint8_t *mem, size_t len, void *userp)
    825 {
    826   struct Curl_cfilter *cf = userp;
    827   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    828   size_t nwritten;
    829   CURLcode result;
    830 
    831   (void)flags;
    832   (void)session;
    833   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
    834 
    835   if(stream_id != ctx->tunnel.stream_id)
    836     return NGHTTP2_ERR_CALLBACK_FAILURE;
    837 
    838   result = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &nwritten);
    839   if(result) {
    840     if(result != CURLE_AGAIN)
    841       return NGHTTP2_ERR_CALLBACK_FAILURE;
    842 #ifdef DEBUGBUILD
    843     nwritten = 0;
    844 #endif
    845   }
    846   /* tunnel.recbuf has soft limit, any success MUST add all data */
    847   DEBUGASSERT((size_t)nwritten == len);
    848   return 0;
    849 }
    850 
    851 static int proxy_h2_on_stream_close(nghttp2_session *session,
    852                                     int32_t stream_id,
    853                                     uint32_t error_code, void *userp)
    854 {
    855   struct Curl_cfilter *cf = userp;
    856   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    857   struct Curl_easy *data = CF_DATA_CURRENT(cf);
    858 
    859   (void)session;
    860   (void)data;
    861 
    862   if(stream_id != ctx->tunnel.stream_id)
    863     return 0;
    864 
    865   CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
    866               stream_id, nghttp2_http2_strerror(error_code), error_code);
    867   ctx->tunnel.closed = TRUE;
    868   ctx->tunnel.error = error_code;
    869 
    870   return 0;
    871 }
    872 
    873 static CURLcode proxy_h2_submit(int32_t *pstream_id,
    874                                 struct Curl_cfilter *cf,
    875                                 struct Curl_easy *data,
    876                                 nghttp2_session *h2,
    877                                 struct httpreq *req,
    878                                 const nghttp2_priority_spec *pri_spec,
    879                                 void *stream_user_data,
    880                                nghttp2_data_source_read_callback read_callback,
    881                                 void *read_ctx)
    882 {
    883   struct dynhds h2_headers;
    884   nghttp2_nv *nva = NULL;
    885   int32_t stream_id = -1;
    886   size_t nheader;
    887   CURLcode result;
    888 
    889   (void)cf;
    890   Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
    891   result = Curl_http_req_to_h2(&h2_headers, req, data);
    892   if(result)
    893     goto out;
    894 
    895   nva = Curl_dynhds_to_nva(&h2_headers, &nheader);
    896   if(!nva) {
    897     result = CURLE_OUT_OF_MEMORY;
    898     goto out;
    899   }
    900 
    901   if(read_callback) {
    902     nghttp2_data_provider data_prd;
    903 
    904     data_prd.read_callback = read_callback;
    905     data_prd.source.ptr = read_ctx;
    906     stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
    907                                        &data_prd, stream_user_data);
    908   }
    909   else {
    910     stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
    911                                        NULL, stream_user_data);
    912   }
    913 
    914   if(stream_id < 0) {
    915     failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
    916           nghttp2_strerror(stream_id), stream_id);
    917     result = CURLE_SEND_ERROR;
    918     goto out;
    919   }
    920   result = CURLE_OK;
    921 
    922 out:
    923   free(nva);
    924   Curl_dynhds_free(&h2_headers);
    925   *pstream_id = stream_id;
    926   return result;
    927 }
    928 
    929 static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
    930                                struct Curl_easy *data,
    931                                struct tunnel_stream *ts)
    932 {
    933   struct cf_h2_proxy_ctx *ctx = cf->ctx;
    934   CURLcode result;
    935   struct httpreq *req = NULL;
    936 
    937   result = Curl_http_proxy_create_CONNECT(&req, cf, data, 2);
    938   if(result)
    939     goto out;
    940   result = Curl_creader_set_null(data);
    941   if(result)
    942     goto out;
    943 
    944   infof(data, "Establish HTTP/2 proxy tunnel to %s", req->authority);
    945 
    946   result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
    947                            NULL, ts, tunnel_send_callback, cf);
    948   if(result) {
    949     CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s",
    950                 ts->stream_id, nghttp2_strerror(ts->stream_id));
    951   }
    952 
    953 out:
    954   if(req)
    955     Curl_http_req_free(req);
    956   if(result)
    957     failf(data, "Failed sending CONNECT to proxy");
    958   return result;
    959 }
    960 
    961 static CURLcode inspect_response(struct Curl_cfilter *cf,
    962                                  struct Curl_easy *data,
    963                                  struct tunnel_stream *ts)
    964 {
    965   CURLcode result = CURLE_OK;
    966   struct dynhds_entry *auth_reply = NULL;
    967   (void)cf;
    968 
    969   DEBUGASSERT(ts->resp);
    970   if(ts->resp->status/100 == 2) {
    971     infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
    972     h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data);
    973     return CURLE_OK;
    974   }
    975 
    976   if(ts->resp->status == 401) {
    977     auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
    978   }
    979   else if(ts->resp->status == 407) {
    980     auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
    981   }
    982 
    983   if(auth_reply) {
    984     CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'",
    985                 auth_reply->value);
    986     result = Curl_http_input_auth(data, ts->resp->status == 407,
    987                                   auth_reply->value);
    988     if(result)
    989       return result;
    990     if(data->req.newurl) {
    991       /* Indicator that we should try again */
    992       Curl_safefree(data->req.newurl);
    993       h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
    994       return CURLE_OK;
    995     }
    996   }
    997 
    998   /* Seems to have failed */
    999   return CURLE_RECV_ERROR;
   1000 }
   1001 
   1002 static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
   1003                            struct Curl_easy *data,
   1004                            struct tunnel_stream *ts)
   1005 {
   1006   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1007   CURLcode result = CURLE_OK;
   1008 
   1009   DEBUGASSERT(ts);
   1010   DEBUGASSERT(ts->authority);
   1011   do {
   1012     switch(ts->state) {
   1013     case H2_TUNNEL_INIT:
   1014       /* Prepare the CONNECT request and make a first attempt to send. */
   1015       CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority);
   1016       result = submit_CONNECT(cf, data, ts);
   1017       if(result)
   1018         goto out;
   1019       h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data);
   1020       FALLTHROUGH();
   1021 
   1022     case H2_TUNNEL_CONNECT:
   1023       /* see that the request is completely sent */
   1024       result = proxy_h2_progress_ingress(cf, data);
   1025       if(!result)
   1026         result = proxy_h2_progress_egress(cf, data);
   1027       if(result && result != CURLE_AGAIN) {
   1028         h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
   1029         break;
   1030       }
   1031 
   1032       if(ts->has_final_response) {
   1033         h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data);
   1034       }
   1035       else {
   1036         result = CURLE_OK;
   1037         goto out;
   1038       }
   1039       FALLTHROUGH();
   1040 
   1041     case H2_TUNNEL_RESPONSE:
   1042       DEBUGASSERT(ts->has_final_response);
   1043       result = inspect_response(cf, data, ts);
   1044       if(result)
   1045         goto out;
   1046       break;
   1047 
   1048     case H2_TUNNEL_ESTABLISHED:
   1049       return CURLE_OK;
   1050 
   1051     case H2_TUNNEL_FAILED:
   1052       return CURLE_RECV_ERROR;
   1053 
   1054     default:
   1055       break;
   1056     }
   1057 
   1058   } while(ts->state == H2_TUNNEL_INIT);
   1059 
   1060 out:
   1061   if((result && (result != CURLE_AGAIN)) || ctx->tunnel.closed)
   1062     h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
   1063   return result;
   1064 }
   1065 
   1066 static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
   1067                                     struct Curl_easy *data,
   1068                                     bool *done)
   1069 {
   1070   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1071   CURLcode result = CURLE_OK;
   1072   struct cf_call_data save;
   1073   timediff_t check;
   1074   struct tunnel_stream *ts = &ctx->tunnel;
   1075 
   1076   if(cf->connected) {
   1077     *done = TRUE;
   1078     return CURLE_OK;
   1079   }
   1080 
   1081   /* Connect the lower filters first */
   1082   if(!cf->next->connected) {
   1083     result = Curl_conn_cf_connect(cf->next, data, done);
   1084     if(result || !*done)
   1085       return result;
   1086   }
   1087 
   1088   *done = FALSE;
   1089 
   1090   CF_DATA_SAVE(save, cf, data);
   1091   if(!ctx->h2) {
   1092     result = cf_h2_proxy_ctx_init(cf, data);
   1093     if(result)
   1094       goto out;
   1095   }
   1096   DEBUGASSERT(ts->authority);
   1097 
   1098   check = Curl_timeleft(data, NULL, TRUE);
   1099   if(check <= 0) {
   1100     failf(data, "Proxy CONNECT aborted due to timeout");
   1101     result = CURLE_OPERATION_TIMEDOUT;
   1102     goto out;
   1103   }
   1104 
   1105   /* for the secondary socket (FTP), use the "connect to host"
   1106    * but ignore the "connect to port" (use the secondary port)
   1107    */
   1108   result = H2_CONNECT(cf, data, ts);
   1109 
   1110 out:
   1111   *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED);
   1112   if(*done) {
   1113     cf->connected = TRUE;
   1114     /* The real request will follow the CONNECT, reset request partially */
   1115     Curl_req_soft_reset(&data->req, data);
   1116     Curl_client_reset(data);
   1117   }
   1118   CF_DATA_RESTORE(cf, save);
   1119   return result;
   1120 }
   1121 
   1122 static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   1123 {
   1124   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1125 
   1126   if(ctx) {
   1127     struct cf_call_data save;
   1128 
   1129     CF_DATA_SAVE(save, cf, data);
   1130     cf_h2_proxy_ctx_clear(ctx);
   1131     CF_DATA_RESTORE(cf, save);
   1132   }
   1133   if(cf->next)
   1134     cf->next->cft->do_close(cf->next, data);
   1135 }
   1136 
   1137 static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
   1138                                 struct Curl_easy *data)
   1139 {
   1140   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1141 
   1142   (void)data;
   1143   if(ctx) {
   1144     cf_h2_proxy_ctx_free(ctx);
   1145     cf->ctx = NULL;
   1146   }
   1147 }
   1148 
   1149 static CURLcode cf_h2_proxy_shutdown(struct Curl_cfilter *cf,
   1150                                      struct Curl_easy *data, bool *done)
   1151 {
   1152   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1153   struct cf_call_data save;
   1154   CURLcode result;
   1155   int rv;
   1156 
   1157   if(!cf->connected || !ctx->h2 || cf->shutdown || ctx->conn_closed) {
   1158     *done = TRUE;
   1159     return CURLE_OK;
   1160   }
   1161 
   1162   CF_DATA_SAVE(save, cf, data);
   1163 
   1164   if(!ctx->sent_goaway) {
   1165     rv = nghttp2_submit_goaway(ctx->h2, NGHTTP2_FLAG_NONE,
   1166                                0, 0,
   1167                                (const uint8_t *)"shutdown",
   1168                                sizeof("shutdown"));
   1169     if(rv) {
   1170       failf(data, "nghttp2_submit_goaway() failed: %s(%d)",
   1171             nghttp2_strerror(rv), rv);
   1172       result = CURLE_SEND_ERROR;
   1173       goto out;
   1174     }
   1175     ctx->sent_goaway = TRUE;
   1176   }
   1177   /* GOAWAY submitted, process egress and ingress until nghttp2 is done. */
   1178   result = CURLE_OK;
   1179   if(nghttp2_session_want_write(ctx->h2))
   1180     result = proxy_h2_progress_egress(cf, data);
   1181   if(!result && nghttp2_session_want_read(ctx->h2))
   1182     result = proxy_h2_progress_ingress(cf, data);
   1183 
   1184   *done = (ctx->conn_closed ||
   1185            (!result && !nghttp2_session_want_write(ctx->h2) &&
   1186             !nghttp2_session_want_read(ctx->h2)));
   1187 out:
   1188   CF_DATA_RESTORE(cf, save);
   1189   cf->shutdown = (result || *done);
   1190   return result;
   1191 }
   1192 
   1193 static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
   1194                                      const struct Curl_easy *data)
   1195 {
   1196   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1197   if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
   1198      (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED &&
   1199       !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
   1200     return TRUE;
   1201   return cf->next ? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
   1202 }
   1203 
   1204 static void cf_h2_proxy_adjust_pollset(struct Curl_cfilter *cf,
   1205                                        struct Curl_easy *data,
   1206                                        struct easy_pollset *ps)
   1207 {
   1208   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1209   struct cf_call_data save;
   1210   curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
   1211   bool want_recv, want_send;
   1212 
   1213   if(!cf->connected && ctx->h2) {
   1214     want_send = nghttp2_session_want_write(ctx->h2) ||
   1215                 !Curl_bufq_is_empty(&ctx->outbufq) ||
   1216                 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
   1217     want_recv = nghttp2_session_want_read(ctx->h2);
   1218   }
   1219   else
   1220     Curl_pollset_check(data, ps, sock, &want_recv, &want_send);
   1221 
   1222   if(ctx->h2 && (want_recv || want_send)) {
   1223     bool c_exhaust, s_exhaust;
   1224 
   1225     CF_DATA_SAVE(save, cf, data);
   1226     c_exhaust = !nghttp2_session_get_remote_window_size(ctx->h2);
   1227     s_exhaust = ctx->tunnel.stream_id >= 0 &&
   1228                 !nghttp2_session_get_stream_remote_window_size(
   1229                    ctx->h2, ctx->tunnel.stream_id);
   1230     want_recv = (want_recv || c_exhaust || s_exhaust);
   1231     want_send = (!s_exhaust && want_send) ||
   1232                 (!c_exhaust && nghttp2_session_want_write(ctx->h2)) ||
   1233                 !Curl_bufq_is_empty(&ctx->outbufq) ||
   1234                 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
   1235 
   1236     Curl_pollset_set(data, ps, sock, want_recv, want_send);
   1237     CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d",
   1238                 want_recv, want_send);
   1239     CF_DATA_RESTORE(cf, save);
   1240   }
   1241   else if(ctx->sent_goaway && !cf->shutdown) {
   1242     /* shutdown in progress */
   1243     CF_DATA_SAVE(save, cf, data);
   1244     want_send = nghttp2_session_want_write(ctx->h2) ||
   1245                 !Curl_bufq_is_empty(&ctx->outbufq) ||
   1246                 !Curl_bufq_is_empty(&ctx->tunnel.sendbuf);
   1247     want_recv = nghttp2_session_want_read(ctx->h2);
   1248     Curl_pollset_set(data, ps, sock, want_recv, want_send);
   1249     CURL_TRC_CF(data, cf, "adjust_pollset, want_recv=%d want_send=%d",
   1250                 want_recv, want_send);
   1251     CF_DATA_RESTORE(cf, save);
   1252   }
   1253 }
   1254 
   1255 static CURLcode h2_handle_tunnel_close(struct Curl_cfilter *cf,
   1256                                        struct Curl_easy *data,
   1257                                        size_t *pnread)
   1258 {
   1259   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1260 
   1261   *pnread = 0;
   1262   if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
   1263     CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
   1264                 "connection", ctx->tunnel.stream_id);
   1265     connclose(cf->conn, "REFUSED_STREAM"); /* do not use this anymore */
   1266     return CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
   1267   }
   1268   else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
   1269     failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
   1270           ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
   1271           ctx->tunnel.error);
   1272     return CURLE_HTTP2_STREAM;
   1273   }
   1274   else if(ctx->tunnel.reset) {
   1275     failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
   1276     return CURLE_RECV_ERROR;
   1277   }
   1278 
   1279   CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> 0",
   1280               ctx->tunnel.stream_id);
   1281   return CURLE_OK;
   1282 }
   1283 
   1284 static CURLcode tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
   1285                             char *buf, size_t len, size_t *pnread)
   1286 {
   1287   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1288   CURLcode result = CURLE_AGAIN;
   1289 
   1290   *pnread = 0;
   1291   if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf))
   1292     result = Curl_bufq_cread(&ctx->tunnel.recvbuf, buf, len, pnread);
   1293   else {
   1294     if(ctx->tunnel.closed) {
   1295       result = h2_handle_tunnel_close(cf, data, pnread);
   1296     }
   1297     else if(ctx->tunnel.reset ||
   1298             (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
   1299             (ctx->rcvd_goaway &&
   1300              ctx->last_stream_id < ctx->tunnel.stream_id)) {
   1301       result = CURLE_RECV_ERROR;
   1302     }
   1303     else
   1304       result = CURLE_AGAIN;
   1305   }
   1306 
   1307   CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %d, %zu",
   1308               ctx->tunnel.stream_id, len, result, *pnread);
   1309   return result;
   1310 }
   1311 
   1312 static CURLcode cf_h2_proxy_recv(struct Curl_cfilter *cf,
   1313                                  struct Curl_easy *data,
   1314                                  char *buf, size_t len,
   1315                                  size_t *pnread)
   1316 {
   1317   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1318   struct cf_call_data save;
   1319   CURLcode result;
   1320 
   1321   *pnread = 0;
   1322   CF_DATA_SAVE(save, cf, data);
   1323 
   1324   if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
   1325     result = CURLE_RECV_ERROR;
   1326     goto out;
   1327   }
   1328 
   1329   if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
   1330     result = proxy_h2_progress_ingress(cf, data);
   1331     if(result)
   1332       goto out;
   1333   }
   1334 
   1335   result = tunnel_recv(cf, data, buf, len, pnread);
   1336 
   1337   if(!result) {
   1338     CURL_TRC_CF(data, cf, "[%d] increase window by %zu",
   1339                 ctx->tunnel.stream_id, *pnread);
   1340     nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, *pnread);
   1341   }
   1342 
   1343   result = Curl_1st_fatal(result, proxy_h2_progress_egress(cf, data));
   1344 
   1345 out:
   1346   if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
   1347      (!result || (result == CURLE_AGAIN))) {
   1348     /* data pending and no fatal error to report. Need to trigger
   1349      * draining to avoid stalling when no socket events happen. */
   1350     drain_tunnel(cf, data, &ctx->tunnel);
   1351   }
   1352   CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %d, %zu",
   1353               ctx->tunnel.stream_id, len, result, *pnread);
   1354   CF_DATA_RESTORE(cf, save);
   1355   return result;
   1356 }
   1357 
   1358 static CURLcode cf_h2_proxy_send(struct Curl_cfilter *cf,
   1359                                  struct Curl_easy *data,
   1360                                  const void *buf, size_t len, bool eos,
   1361                                  size_t *pnwritten)
   1362 {
   1363   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1364   struct cf_call_data save;
   1365   int rv;
   1366   CURLcode result;
   1367 
   1368   (void)eos;
   1369   *pnwritten = 0;
   1370   CF_DATA_SAVE(save, cf, data);
   1371 
   1372   if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
   1373     result = CURLE_SEND_ERROR;
   1374     goto out;
   1375   }
   1376 
   1377   if(ctx->tunnel.closed) {
   1378     result = CURLE_SEND_ERROR;
   1379     goto out;
   1380   }
   1381 
   1382   result = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, pnwritten);
   1383   CURL_TRC_CF(data, cf, "cf_send(), bufq_write %d, %zd", result, *pnwritten);
   1384   if(result && (result != CURLE_AGAIN))
   1385     goto out;
   1386 
   1387   if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
   1388     /* req body data is buffered, resume the potentially suspended stream */
   1389     rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
   1390     if(nghttp2_is_fatal(rv)) {
   1391       result = CURLE_SEND_ERROR;
   1392       goto out;
   1393     }
   1394   }
   1395 
   1396   result = Curl_1st_fatal(result, proxy_h2_progress_ingress(cf, data));
   1397   result = Curl_1st_fatal(result, proxy_h2_progress_egress(cf, data));
   1398 
   1399   if(!result && proxy_h2_should_close_session(ctx)) {
   1400     /* nghttp2 thinks this session is done. If the stream has not been
   1401      * closed, this is an error state for out transfer */
   1402     if(ctx->tunnel.closed) {
   1403       result = CURLE_SEND_ERROR;
   1404     }
   1405     else {
   1406       CURL_TRC_CF(data, cf, "[0] send: nothing to do in this session");
   1407       result = CURLE_HTTP2;
   1408     }
   1409   }
   1410 
   1411 out:
   1412   if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
   1413      (!result || (result == CURLE_AGAIN))) {
   1414     /* data pending and no fatal error to report. Need to trigger
   1415      * draining to avoid stalling when no socket events happen. */
   1416     drain_tunnel(cf, data, &ctx->tunnel);
   1417   }
   1418   CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %d, %zu, "
   1419               "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
   1420               ctx->tunnel.stream_id, len, result, *pnwritten,
   1421               nghttp2_session_get_stream_remote_window_size(
   1422                   ctx->h2, ctx->tunnel.stream_id),
   1423               nghttp2_session_get_remote_window_size(ctx->h2),
   1424               Curl_bufq_len(&ctx->tunnel.sendbuf),
   1425               Curl_bufq_len(&ctx->outbufq));
   1426   CF_DATA_RESTORE(cf, save);
   1427   return result;
   1428 }
   1429 
   1430 static CURLcode cf_h2_proxy_flush(struct Curl_cfilter *cf,
   1431                                   struct Curl_easy *data)
   1432 {
   1433   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1434   struct cf_call_data save;
   1435   CURLcode result = CURLE_OK;
   1436 
   1437   CF_DATA_SAVE(save, cf, data);
   1438   if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
   1439     /* resume the potentially suspended tunnel */
   1440     int rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
   1441     if(nghttp2_is_fatal(rv)) {
   1442       result = CURLE_SEND_ERROR;
   1443       goto out;
   1444     }
   1445   }
   1446 
   1447   result = proxy_h2_progress_egress(cf, data);
   1448 
   1449 out:
   1450   CURL_TRC_CF(data, cf, "[%d] flush -> %d, "
   1451               "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
   1452               ctx->tunnel.stream_id, result,
   1453               nghttp2_session_get_stream_remote_window_size(
   1454                 ctx->h2, ctx->tunnel.stream_id),
   1455               nghttp2_session_get_remote_window_size(ctx->h2),
   1456               Curl_bufq_len(&ctx->tunnel.sendbuf),
   1457               Curl_bufq_len(&ctx->outbufq));
   1458   CF_DATA_RESTORE(cf, save);
   1459   return result;
   1460 }
   1461 
   1462 static bool proxy_h2_connisalive(struct Curl_cfilter *cf,
   1463                                  struct Curl_easy *data,
   1464                                  bool *input_pending)
   1465 {
   1466   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1467   bool alive = TRUE;
   1468 
   1469   *input_pending = FALSE;
   1470   if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
   1471     return FALSE;
   1472 
   1473   if(*input_pending) {
   1474     /* This happens before we have sent off a request and the connection is
   1475        not in use by any other transfer, there should not be any data here,
   1476        only "protocol frames" */
   1477     CURLcode result;
   1478     size_t nread;
   1479 
   1480     *input_pending = FALSE;
   1481     result = Curl_cf_recv_bufq(cf->next, data, &ctx->inbufq, 0, &nread);
   1482     if(!result) {
   1483       if(proxy_h2_process_pending_input(cf, data, &result) < 0)
   1484         /* immediate error, considered dead */
   1485         alive = FALSE;
   1486       else {
   1487         alive = !proxy_h2_should_close_session(ctx);
   1488       }
   1489     }
   1490     else if(result != CURLE_AGAIN) {
   1491       /* the read failed so let's say this is dead anyway */
   1492       alive = FALSE;
   1493     }
   1494   }
   1495 
   1496   return alive;
   1497 }
   1498 
   1499 static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf,
   1500                                  struct Curl_easy *data,
   1501                                  bool *input_pending)
   1502 {
   1503   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1504   bool alive;
   1505   struct cf_call_data save;
   1506 
   1507   *input_pending = FALSE;
   1508   CF_DATA_SAVE(save, cf, data);
   1509   alive = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending));
   1510   CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d",
   1511               alive, *input_pending);
   1512   CF_DATA_RESTORE(cf, save);
   1513   return alive;
   1514 }
   1515 
   1516 static CURLcode cf_h2_proxy_query(struct Curl_cfilter *cf,
   1517                                   struct Curl_easy *data,
   1518                                   int query, int *pres1, void *pres2)
   1519 {
   1520   struct cf_h2_proxy_ctx *ctx = cf->ctx;
   1521 
   1522   switch(query) {
   1523   case CF_QUERY_HOST_PORT:
   1524     *pres1 = (int)cf->conn->http_proxy.port;
   1525     *((const char **)pres2) = cf->conn->http_proxy.host.name;
   1526     return CURLE_OK;
   1527   case CF_QUERY_NEED_FLUSH: {
   1528     if(!Curl_bufq_is_empty(&ctx->outbufq) ||
   1529        !Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
   1530       CURL_TRC_CF(data, cf, "needs flush");
   1531       *pres1 = TRUE;
   1532       return CURLE_OK;
   1533     }
   1534     break;
   1535   }
   1536   default:
   1537     break;
   1538   }
   1539   return cf->next ?
   1540     cf->next->cft->query(cf->next, data, query, pres1, pres2) :
   1541     CURLE_UNKNOWN_OPTION;
   1542 }
   1543 
   1544 static CURLcode cf_h2_proxy_cntrl(struct Curl_cfilter *cf,
   1545                                   struct Curl_easy *data,
   1546                                   int event, int arg1, void *arg2)
   1547 {
   1548   CURLcode result = CURLE_OK;
   1549   struct cf_call_data save;
   1550 
   1551   (void)arg1;
   1552   (void)arg2;
   1553 
   1554   switch(event) {
   1555   case CF_CTRL_FLUSH:
   1556     CF_DATA_SAVE(save, cf, data);
   1557     result = cf_h2_proxy_flush(cf, data);
   1558     CF_DATA_RESTORE(cf, save);
   1559     break;
   1560   default:
   1561     break;
   1562   }
   1563   return result;
   1564 }
   1565 
   1566 struct Curl_cftype Curl_cft_h2_proxy = {
   1567   "H2-PROXY",
   1568   CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
   1569   CURL_LOG_LVL_NONE,
   1570   cf_h2_proxy_destroy,
   1571   cf_h2_proxy_connect,
   1572   cf_h2_proxy_close,
   1573   cf_h2_proxy_shutdown,
   1574   cf_h2_proxy_adjust_pollset,
   1575   cf_h2_proxy_data_pending,
   1576   cf_h2_proxy_send,
   1577   cf_h2_proxy_recv,
   1578   cf_h2_proxy_cntrl,
   1579   cf_h2_proxy_is_alive,
   1580   Curl_cf_def_conn_keep_alive,
   1581   cf_h2_proxy_query,
   1582 };
   1583 
   1584 CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
   1585                                        struct Curl_easy *data)
   1586 {
   1587   struct Curl_cfilter *cf_h2_proxy = NULL;
   1588   struct cf_h2_proxy_ctx *ctx;
   1589   CURLcode result = CURLE_OUT_OF_MEMORY;
   1590 
   1591   (void)data;
   1592   ctx = calloc(1, sizeof(*ctx));
   1593   if(!ctx)
   1594     goto out;
   1595 
   1596   result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
   1597   if(result)
   1598     goto out;
   1599 
   1600   Curl_conn_cf_insert_after(cf, cf_h2_proxy);
   1601   result = CURLE_OK;
   1602 
   1603 out:
   1604   if(result)
   1605     cf_h2_proxy_ctx_free(ctx);
   1606   return result;
   1607 }
   1608 
   1609 #endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */