Coverage Report

Created: 2026-03-12 06:35

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