Coverage Report

Created: 2025-08-29 06:06

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