Coverage Report

Created: 2025-10-13 06:43

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