Coverage Report

Created: 2025-11-09 06:25

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