Coverage Report

Created: 2025-12-14 06:23

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