Coverage Report

Created: 2023-06-07 07:02

/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(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
28
29
#include <nghttp2/nghttp2.h>
30
#include "urldata.h"
31
#include "cfilters.h"
32
#include "connect.h"
33
#include "curl_log.h"
34
#include "bufq.h"
35
#include "dynbuf.h"
36
#include "dynhds.h"
37
#include "http1.h"
38
#include "http_proxy.h"
39
#include "multiif.h"
40
#include "cf-h2-proxy.h"
41
42
/* The last 3 #include files should be in this order */
43
#include "curl_printf.h"
44
#include "curl_memory.h"
45
#include "memdebug.h"
46
47
0
#define H2_NW_CHUNK_SIZE  (128*1024)
48
0
#define H2_NW_RECV_CHUNKS   1
49
0
#define H2_NW_SEND_CHUNKS   1
50
51
0
#define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
52
53
0
#define H2_TUNNEL_WINDOW_SIZE (1024 * 1024)
54
0
#define H2_TUNNEL_CHUNK_SIZE   (32 * 1024)
55
#define H2_TUNNEL_RECV_CHUNKS \
56
0
          (H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE)
57
#define H2_TUNNEL_SEND_CHUNKS \
58
0
          (H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE)
59
60
typedef enum {
61
    TUNNEL_INIT,     /* init/default/no tunnel state */
62
    TUNNEL_CONNECT,  /* CONNECT request is being send */
63
    TUNNEL_RESPONSE, /* CONNECT response received completely */
64
    TUNNEL_ESTABLISHED,
65
    TUNNEL_FAILED
66
} tunnel_state;
67
68
struct tunnel_stream {
69
  struct http_resp *resp;
70
  struct bufq recvbuf;
71
  struct bufq sendbuf;
72
  char *authority;
73
  int32_t stream_id;
74
  uint32_t error;
75
  tunnel_state state;
76
  bool has_final_response;
77
  bool closed;
78
  bool reset;
79
};
80
81
static CURLcode tunnel_stream_init(struct Curl_cfilter *cf,
82
                                    struct tunnel_stream *ts)
83
0
{
84
0
  const char *hostname;
85
0
  int port;
86
0
  bool ipv6_ip = cf->conn->bits.ipv6_ip;
87
88
0
  ts->state = TUNNEL_INIT;
89
0
  ts->stream_id = -1;
90
0
  Curl_bufq_init2(&ts->recvbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
91
0
                  BUFQ_OPT_SOFT_LIMIT);
92
0
  Curl_bufq_init(&ts->sendbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
93
94
0
  if(cf->conn->bits.conn_to_host)
95
0
    hostname = cf->conn->conn_to_host.name;
96
0
  else if(cf->sockindex == SECONDARYSOCKET)
97
0
    hostname = cf->conn->secondaryhostname;
98
0
  else
99
0
    hostname = cf->conn->host.name;
100
101
0
  if(cf->sockindex == SECONDARYSOCKET)
102
0
    port = cf->conn->secondary_port;
103
0
  else if(cf->conn->bits.conn_to_port)
104
0
    port = cf->conn->conn_to_port;
105
0
  else
106
0
    port = cf->conn->remote_port;
107
108
0
  if(hostname != cf->conn->host.name)
109
0
    ipv6_ip = (strchr(hostname, ':') != NULL);
110
111
0
  ts->authority = /* host:port with IPv6 support */
112
0
    aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port);
113
0
  if(!ts->authority)
114
0
    return CURLE_OUT_OF_MEMORY;
115
116
0
  return CURLE_OK;
117
0
}
118
119
static void tunnel_stream_clear(struct tunnel_stream *ts)
120
0
{
121
0
  Curl_http_resp_free(ts->resp);
122
0
  Curl_bufq_free(&ts->recvbuf);
123
0
  Curl_bufq_free(&ts->sendbuf);
124
0
  Curl_safefree(ts->authority);
125
0
  memset(ts, 0, sizeof(*ts));
126
0
  ts->state = TUNNEL_INIT;
127
0
}
128
129
static void tunnel_go_state(struct Curl_cfilter *cf,
130
                            struct tunnel_stream *ts,
131
                            tunnel_state new_state,
132
                            struct Curl_easy *data)
133
0
{
134
0
  (void)cf;
135
136
0
  if(ts->state == new_state)
137
0
    return;
138
  /* leaving this one */
139
0
  switch(ts->state) {
140
0
  case TUNNEL_CONNECT:
141
0
    data->req.ignorebody = FALSE;
142
0
    break;
143
0
  default:
144
0
    break;
145
0
  }
146
  /* entering this one */
147
0
  switch(new_state) {
148
0
  case TUNNEL_INIT:
149
0
    DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'"));
150
0
    tunnel_stream_clear(ts);
151
0
    break;
152
153
0
  case TUNNEL_CONNECT:
154
0
    DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'"));
155
0
    ts->state = TUNNEL_CONNECT;
156
0
    break;
157
158
0
  case TUNNEL_RESPONSE:
159
0
    DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'"));
160
0
    ts->state = TUNNEL_RESPONSE;
161
0
    break;
162
163
0
  case TUNNEL_ESTABLISHED:
164
0
    DEBUGF(LOG_CF(data, cf, "new tunnel state 'established'"));
165
0
    infof(data, "CONNECT phase completed");
166
0
    data->state.authproxy.done = TRUE;
167
0
    data->state.authproxy.multipass = FALSE;
168
    /* FALLTHROUGH */
169
0
  case TUNNEL_FAILED:
170
0
    if(new_state == TUNNEL_FAILED)
171
0
      DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'"));
172
0
    ts->state = new_state;
173
    /* If a proxy-authorization header was used for the proxy, then we should
174
       make sure that it isn't accidentally used for the document request
175
       after we've connected. So let's free and clear it here. */
176
0
    Curl_safefree(data->state.aptr.proxyuserpwd);
177
0
    break;
178
0
  }
179
0
}
180
181
struct cf_h2_proxy_ctx {
182
  nghttp2_session *h2;
183
  /* The easy handle used in the current filter call, cleared at return */
184
  struct cf_call_data call_data;
185
186
  struct bufq inbufq;  /* network receive buffer */
187
  struct bufq outbufq; /* network send buffer */
188
189
  struct tunnel_stream tunnel; /* our tunnel CONNECT stream */
190
  int32_t goaway_error;
191
  int32_t last_stream_id;
192
  BIT(conn_closed);
193
  BIT(goaway);
194
};
195
196
/* How to access `call_data` from a cf_h2 filter */
197
#define CF_CTX_CALL_DATA(cf)  \
198
0
  ((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data
199
200
static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx)
201
0
{
202
0
  struct cf_call_data save = ctx->call_data;
203
204
0
  if(ctx->h2) {
205
0
    nghttp2_session_del(ctx->h2);
206
0
  }
207
0
  Curl_bufq_free(&ctx->inbufq);
208
0
  Curl_bufq_free(&ctx->outbufq);
209
0
  tunnel_stream_clear(&ctx->tunnel);
210
0
  memset(ctx, 0, sizeof(*ctx));
211
0
  ctx->call_data = save;
212
0
}
213
214
static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
215
0
{
216
0
  if(ctx) {
217
0
    cf_h2_proxy_ctx_clear(ctx);
218
0
    free(ctx);
219
0
  }
220
0
}
221
222
static ssize_t nw_in_reader(void *reader_ctx,
223
                              unsigned char *buf, size_t buflen,
224
                              CURLcode *err)
225
0
{
226
0
  struct Curl_cfilter *cf = reader_ctx;
227
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
228
0
  ssize_t nread;
229
230
0
  nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err);
231
0
  DEBUGF(LOG_CF(data, cf, "nw_in recv(len=%zu) -> %zd, %d",
232
0
         buflen, nread, *err));
233
0
  return nread;
234
0
}
235
236
static ssize_t nw_out_writer(void *writer_ctx,
237
                             const unsigned char *buf, size_t buflen,
238
                             CURLcode *err)
239
0
{
240
0
  struct Curl_cfilter *cf = writer_ctx;
241
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
242
0
  ssize_t nwritten;
243
244
0
  nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen, err);
245
0
  DEBUGF(LOG_CF(data, cf, "nw_out send(len=%zu) -> %zd", buflen, nwritten));
246
0
  return nwritten;
247
0
}
248
249
static int h2_client_new(struct Curl_cfilter *cf,
250
                         nghttp2_session_callbacks *cbs)
251
0
{
252
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
253
0
  nghttp2_option *o;
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
#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
  nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
265
#endif
266
0
  rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
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 on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
275
                         void *userp);
276
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
277
                           uint32_t error_code, void *userp);
278
static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
279
                     const uint8_t *name, size_t namelen,
280
                     const uint8_t *value, size_t valuelen,
281
                     uint8_t flags,
282
                     void *userp);
283
static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
284
                                int32_t stream_id,
285
                                const uint8_t *mem, size_t len, void *userp);
286
287
/*
288
 * Initialize the cfilter context
289
 */
290
static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf,
291
                                     struct Curl_easy *data)
292
0
{
293
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
294
0
  CURLcode result = CURLE_OUT_OF_MEMORY;
295
0
  nghttp2_session_callbacks *cbs = NULL;
296
0
  int rc;
297
298
0
  DEBUGASSERT(!ctx->h2);
299
0
  memset(&ctx->tunnel, 0, sizeof(ctx->tunnel));
300
301
0
  Curl_bufq_init(&ctx->inbufq, H2_NW_CHUNK_SIZE, H2_NW_RECV_CHUNKS);
302
0
  Curl_bufq_init(&ctx->outbufq, H2_NW_CHUNK_SIZE, H2_NW_SEND_CHUNKS);
303
304
0
  if(tunnel_stream_init(cf, &ctx->tunnel))
305
0
    goto out;
306
307
0
  rc = nghttp2_session_callbacks_new(&cbs);
308
0
  if(rc) {
309
0
    failf(data, "Couldn't initialize nghttp2 callbacks");
310
0
    goto out;
311
0
  }
312
313
0
  nghttp2_session_callbacks_set_send_callback(cbs, on_session_send);
314
0
  nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
315
0
  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
316
0
    cbs, tunnel_recv_callback);
317
0
  nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
318
0
  nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
319
320
  /* The nghttp2 session is not yet setup, do it */
321
0
  rc = h2_client_new(cf, cbs);
322
0
  if(rc) {
323
0
    failf(data, "Couldn't initialize nghttp2");
324
0
    goto out;
325
0
  }
326
327
0
  {
328
0
    nghttp2_settings_entry iv[3];
329
330
0
    iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
331
0
    iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
332
0
    iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
333
0
    iv[1].value = H2_TUNNEL_WINDOW_SIZE;
334
0
    iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
335
0
    iv[2].value = 0;
336
0
    rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, 3);
337
0
    if(rc) {
338
0
      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
339
0
            nghttp2_strerror(rc), rc);
340
0
      result = CURLE_HTTP2;
341
0
      goto out;
342
0
    }
343
0
  }
344
345
0
  rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
346
0
                                             HTTP2_HUGE_WINDOW_SIZE);
347
0
  if(rc) {
348
0
    failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
349
0
          nghttp2_strerror(rc), rc);
350
0
    result = CURLE_HTTP2;
351
0
    goto out;
352
0
  }
353
354
355
  /* all set, traffic will be send on connect */
356
0
  result = CURLE_OK;
357
358
0
out:
359
0
  if(cbs)
360
0
    nghttp2_session_callbacks_del(cbs);
361
0
  DEBUGF(LOG_CF(data, cf, "init proxy ctx -> %d", result));
362
0
  return result;
363
0
}
364
365
static CURLcode nw_out_flush(struct Curl_cfilter *cf,
366
                             struct Curl_easy *data)
367
0
{
368
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
369
0
  size_t buflen = Curl_bufq_len(&ctx->outbufq);
370
0
  ssize_t nwritten;
371
0
  CURLcode result;
372
373
0
  (void)data;
374
0
  if(!buflen)
375
0
    return CURLE_OK;
376
377
0
  DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen));
378
0
  nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result);
379
0
  if(nwritten < 0) {
380
0
    return result;
381
0
  }
382
0
  if((size_t)nwritten < buflen) {
383
0
    return CURLE_AGAIN;
384
0
  }
385
0
  return CURLE_OK;
386
0
}
387
388
/*
389
 * Processes pending input left in network input buffer.
390
 * This function returns 0 if it succeeds, or -1 and error code will
391
 * be assigned to *err.
392
 */
393
static int h2_process_pending_input(struct Curl_cfilter *cf,
394
                                    struct Curl_easy *data,
395
                                    CURLcode *err)
396
0
{
397
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
398
0
  const unsigned char *buf;
399
0
  size_t blen;
400
0
  ssize_t rv;
401
402
0
  while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) {
403
404
0
    rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen);
405
0
    DEBUGF(LOG_CF(data, cf,
406
0
                 "fed %zu bytes from nw to nghttp2 -> %zd", blen, rv));
407
0
    if(rv < 0) {
408
0
      failf(data,
409
0
            "process_pending_input: nghttp2_session_mem_recv() returned "
410
0
            "%zd:%s", rv, nghttp2_strerror((int)rv));
411
0
      *err = CURLE_RECV_ERROR;
412
0
      return -1;
413
0
    }
414
0
    Curl_bufq_skip(&ctx->inbufq, (size_t)rv);
415
0
    if(Curl_bufq_is_empty(&ctx->inbufq)) {
416
0
      DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed"));
417
0
      break;
418
0
    }
419
0
    else {
420
0
      DEBUGF(LOG_CF(data, cf, "process_pending_input: %zu bytes left "
421
0
                    "in connection buffer", Curl_bufq_len(&ctx->inbufq)));
422
0
    }
423
0
  }
424
425
0
  if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
426
    /* No more requests are allowed in the current session, so
427
       the connection may not be reused. This is set when a
428
       GOAWAY frame has been received or when the limit of stream
429
       identifiers has been reached. */
430
0
    connclose(cf->conn, "http/2: No new requests allowed");
431
0
  }
432
433
0
  return 0;
434
0
}
435
436
static CURLcode h2_progress_ingress(struct Curl_cfilter *cf,
437
                                    struct Curl_easy *data)
438
0
{
439
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
440
0
  CURLcode result = CURLE_OK;
441
0
  ssize_t nread;
442
443
  /* Process network input buffer fist */
444
0
  if(!Curl_bufq_is_empty(&ctx->inbufq)) {
445
0
    DEBUGF(LOG_CF(data, cf, "Process %zd bytes in connection buffer",
446
0
                  Curl_bufq_len(&ctx->inbufq)));
447
0
    if(h2_process_pending_input(cf, data, &result) < 0)
448
0
      return result;
449
0
  }
450
451
  /* Receive data from the "lower" filters, e.g. network until
452
   * it is time to stop or we have enough data for this stream */
453
0
  while(!ctx->conn_closed &&               /* not closed the connection */
454
0
        !ctx->tunnel.closed &&             /* nor the tunnel */
455
0
        Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */
456
0
        !Curl_bufq_is_full(&ctx->tunnel.recvbuf)) {
457
458
0
    nread = Curl_bufq_slurp(&ctx->inbufq, nw_in_reader, cf, &result);
459
0
    DEBUGF(LOG_CF(data, cf, "read %zd bytes nw data -> %zd, %d",
460
0
                  Curl_bufq_len(&ctx->inbufq), nread, result));
461
0
    if(nread < 0) {
462
0
      if(result != CURLE_AGAIN) {
463
0
        failf(data, "Failed receiving HTTP2 data");
464
0
        return result;
465
0
      }
466
0
      break;
467
0
    }
468
0
    else if(nread == 0) {
469
0
      ctx->conn_closed = TRUE;
470
0
      break;
471
0
    }
472
473
0
    if(h2_process_pending_input(cf, data, &result))
474
0
      return result;
475
0
  }
476
477
0
  if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
478
0
    connclose(cf->conn, "GOAWAY received");
479
0
  }
480
481
0
  return CURLE_OK;
482
0
}
483
484
/*
485
 * Check if there's been an update in the priority /
486
 * dependency settings and if so it submits a PRIORITY frame with the updated
487
 * info.
488
 * Flush any out data pending in the network buffer.
489
 */
490
static CURLcode h2_progress_egress(struct Curl_cfilter *cf,
491
                                  struct Curl_easy *data)
492
0
{
493
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
494
0
  int rv = 0;
495
496
0
  rv = nghttp2_session_send(ctx->h2);
497
0
  if(nghttp2_is_fatal(rv)) {
498
0
    DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d",
499
0
                  nghttp2_strerror(rv), rv));
500
0
    return CURLE_SEND_ERROR;
501
0
  }
502
0
  return nw_out_flush(cf, data);
503
0
}
504
505
static ssize_t on_session_send(nghttp2_session *h2,
506
                               const uint8_t *buf, size_t blen, int flags,
507
                               void *userp)
508
0
{
509
0
  struct Curl_cfilter *cf = userp;
510
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
511
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
512
0
  ssize_t nwritten;
513
0
  CURLcode result = CURLE_OK;
514
515
0
  (void)h2;
516
0
  (void)flags;
517
0
  DEBUGASSERT(data);
518
519
0
  nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
520
0
                                  nw_out_writer, cf, &result);
521
0
  if(nwritten < 0) {
522
0
    if(result == CURLE_AGAIN) {
523
0
      return NGHTTP2_ERR_WOULDBLOCK;
524
0
    }
525
0
    failf(data, "Failed sending HTTP2 data");
526
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
527
0
  }
528
529
0
  if(!nwritten)
530
0
    return NGHTTP2_ERR_WOULDBLOCK;
531
532
0
  return nwritten;
533
0
}
534
535
static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
536
                         void *userp)
537
0
{
538
0
  struct Curl_cfilter *cf = userp;
539
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
540
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
541
0
  int32_t stream_id = frame->hd.stream_id;
542
543
0
  (void)session;
544
0
  DEBUGASSERT(data);
545
0
  if(!stream_id) {
546
    /* stream ID zero is for connection-oriented stuff */
547
0
    DEBUGASSERT(data);
548
0
    switch(frame->hd.type) {
549
0
    case NGHTTP2_SETTINGS:
550
      /* we do not do anything with this for now */
551
0
      break;
552
0
    case NGHTTP2_GOAWAY:
553
0
      infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
554
0
                  frame->goaway.error_code, frame->goaway.last_stream_id);
555
0
      ctx->goaway = TRUE;
556
0
      break;
557
0
    case NGHTTP2_WINDOW_UPDATE:
558
0
      DEBUGF(LOG_CF(data, cf, "recv frame WINDOW_UPDATE"));
559
0
      break;
560
0
    default:
561
0
      DEBUGF(LOG_CF(data, cf, "recv frame %x on 0", frame->hd.type));
562
0
    }
563
0
    return 0;
564
0
  }
565
566
0
  if(stream_id != ctx->tunnel.stream_id) {
567
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] rcvd FRAME not for tunnel",
568
0
                  stream_id));
569
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
570
0
  }
571
572
0
  switch(frame->hd.type) {
573
0
  case NGHTTP2_DATA:
574
    /* If body started on this stream, then receiving DATA is illegal. */
575
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame DATA", stream_id));
576
0
    break;
577
0
  case NGHTTP2_HEADERS:
578
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame HEADERS", stream_id));
579
580
    /* nghttp2 guarantees that :status is received, and we store it to
581
       stream->status_code. Fuzzing has proven this can still be reached
582
       without status code having been set. */
583
0
    if(!ctx->tunnel.resp)
584
0
      return NGHTTP2_ERR_CALLBACK_FAILURE;
585
    /* Only final status code signals the end of header */
586
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] got http status: %d",
587
0
           stream_id, ctx->tunnel.resp->status));
588
0
    if(!ctx->tunnel.has_final_response) {
589
0
      if(ctx->tunnel.resp->status / 100 != 1) {
590
0
        ctx->tunnel.has_final_response = TRUE;
591
0
      }
592
0
    }
593
0
    break;
594
0
  case NGHTTP2_PUSH_PROMISE:
595
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id));
596
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
597
0
  case NGHTTP2_RST_STREAM:
598
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv RST", stream_id));
599
0
    ctx->tunnel.reset = TRUE;
600
0
    break;
601
0
  case NGHTTP2_WINDOW_UPDATE:
602
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv WINDOW_UPDATE", stream_id));
603
0
    if((data->req.keepon & KEEP_SEND_HOLD) &&
604
0
       (data->req.keepon & KEEP_SEND)) {
605
0
      data->req.keepon &= ~KEEP_SEND_HOLD;
606
0
      Curl_expire(data, 0, EXPIRE_RUN_NOW);
607
0
      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] unpausing after win update",
608
0
             stream_id));
609
0
    }
610
0
    break;
611
0
  default:
612
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv frame %x",
613
0
                  stream_id, frame->hd.type));
614
0
    break;
615
0
  }
616
0
  return 0;
617
0
}
618
619
static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
620
                     const uint8_t *name, size_t namelen,
621
                     const uint8_t *value, size_t valuelen,
622
                     uint8_t flags,
623
                     void *userp)
624
0
{
625
0
  struct Curl_cfilter *cf = userp;
626
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
627
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
628
0
  int32_t stream_id = frame->hd.stream_id;
629
0
  CURLcode result;
630
631
0
  (void)flags;
632
0
  (void)data;
633
0
  (void)session;
634
0
  DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
635
0
  if(stream_id != ctx->tunnel.stream_id) {
636
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] header for non-tunnel stream: "
637
0
                  "%.*s: %.*s", stream_id,
638
0
                  (int)namelen, name,
639
0
                  (int)valuelen, value));
640
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
641
0
  }
642
643
0
  if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
644
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
645
646
0
  if(ctx->tunnel.has_final_response) {
647
    /* we do not do anything with trailers for tunnel streams */
648
0
    return 0;
649
0
  }
650
651
0
  if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
652
0
     memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
653
0
    int http_status;
654
0
    struct http_resp *resp;
655
656
    /* status: always comes first, we might get more than one response,
657
     * link the previous ones for keepers */
658
0
    result = Curl_http_decode_status(&http_status,
659
0
                                    (const char *)value, valuelen);
660
0
    if(result)
661
0
      return NGHTTP2_ERR_CALLBACK_FAILURE;
662
0
    result = Curl_http_resp_make(&resp, http_status, NULL);
663
0
    if(result)
664
0
      return NGHTTP2_ERR_CALLBACK_FAILURE;
665
0
    resp->prev = ctx->tunnel.resp;
666
0
    ctx->tunnel.resp = resp;
667
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] status: HTTP/2 %03d",
668
0
                  stream_id, ctx->tunnel.resp->status));
669
0
    return 0;
670
0
  }
671
672
0
  if(!ctx->tunnel.resp)
673
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
674
675
0
  result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
676
0
                           (const char *)name, namelen,
677
0
                           (const char *)value, valuelen);
678
0
  if(result)
679
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
680
681
0
  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] header: %.*s: %.*s",
682
0
                stream_id,
683
0
                (int)namelen, name,
684
0
                (int)valuelen, value));
685
686
0
  return 0; /* 0 is successful */
687
0
}
688
689
static ssize_t tunnel_send_callback(nghttp2_session *session,
690
                                    int32_t stream_id,
691
                                    uint8_t *buf, size_t length,
692
                                    uint32_t *data_flags,
693
                                    nghttp2_data_source *source,
694
                                    void *userp)
695
0
{
696
0
  struct Curl_cfilter *cf = userp;
697
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
698
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
699
0
  struct tunnel_stream *ts;
700
0
  CURLcode result;
701
0
  ssize_t nread;
702
703
0
  (void)source;
704
0
  (void)data;
705
0
  (void)ctx;
706
707
0
  if(!stream_id)
708
0
    return NGHTTP2_ERR_INVALID_ARGUMENT;
709
710
0
  ts = nghttp2_session_get_stream_user_data(session, stream_id);
711
0
  if(!ts)
712
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
713
0
  DEBUGASSERT(ts == &ctx->tunnel);
714
715
0
  nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result);
716
0
  if(nread < 0) {
717
0
    if(result != CURLE_AGAIN)
718
0
      return NGHTTP2_ERR_CALLBACK_FAILURE;
719
0
    return NGHTTP2_ERR_DEFERRED;
720
0
  }
721
0
  if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
722
0
    *data_flags = NGHTTP2_DATA_FLAG_EOF;
723
724
0
  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] tunnel_send_callback -> %zd",
725
0
                ts->stream_id, nread));
726
0
  return nread;
727
0
}
728
729
static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
730
                                int32_t stream_id,
731
                                const uint8_t *mem, size_t len, void *userp)
732
0
{
733
0
  struct Curl_cfilter *cf = userp;
734
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
735
0
  ssize_t nwritten;
736
0
  CURLcode result;
737
738
0
  (void)flags;
739
0
  (void)session;
740
0
  DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
741
742
0
  if(stream_id != ctx->tunnel.stream_id)
743
0
    return NGHTTP2_ERR_CALLBACK_FAILURE;
744
745
0
  nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result);
746
0
  if(nwritten < 0) {
747
0
    if(result != CURLE_AGAIN)
748
0
      return NGHTTP2_ERR_CALLBACK_FAILURE;
749
0
    nwritten = 0;
750
0
  }
751
0
  DEBUGASSERT((size_t)nwritten == len);
752
0
  return 0;
753
0
}
754
755
static int on_stream_close(nghttp2_session *session, int32_t stream_id,
756
                           uint32_t error_code, void *userp)
757
0
{
758
0
  struct Curl_cfilter *cf = userp;
759
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
760
0
  struct Curl_easy *data = CF_DATA_CURRENT(cf);
761
762
0
  (void)session;
763
0
  (void)data;
764
765
0
  if(stream_id != ctx->tunnel.stream_id)
766
0
    return 0;
767
768
0
  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] on_stream_close, %s (err %d)",
769
0
                stream_id, nghttp2_http2_strerror(error_code), error_code));
770
0
  ctx->tunnel.closed = TRUE;
771
0
  ctx->tunnel.error = error_code;
772
773
0
  return 0;
774
0
}
775
776
static CURLcode h2_submit(int32_t *pstream_id,
777
                          struct Curl_cfilter *cf,
778
                          struct Curl_easy *data,
779
                          nghttp2_session *h2,
780
                          struct httpreq *req,
781
                          const nghttp2_priority_spec *pri_spec,
782
                          void *stream_user_data,
783
                          nghttp2_data_source_read_callback read_callback,
784
                          void *read_ctx)
785
0
{
786
0
  struct dynhds h2_headers;
787
0
  nghttp2_nv *nva = NULL;
788
0
  unsigned int i;
789
0
  int32_t stream_id = -1;
790
0
  size_t nheader;
791
0
  CURLcode result;
792
793
0
  (void)cf;
794
0
  Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
795
0
  result = Curl_http_req_to_h2(&h2_headers, req, data);
796
0
  if(result)
797
0
    goto out;
798
799
0
  nheader = Curl_dynhds_count(&h2_headers);
800
0
  nva = malloc(sizeof(nghttp2_nv) * nheader);
801
0
  if(!nva) {
802
0
    result = CURLE_OUT_OF_MEMORY;
803
0
    goto out;
804
0
  }
805
806
0
  for(i = 0; i < nheader; ++i) {
807
0
    struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
808
0
    nva[i].name = (unsigned char *)e->name;
809
0
    nva[i].namelen = e->namelen;
810
0
    nva[i].value = (unsigned char *)e->value;
811
0
    nva[i].valuelen = e->valuelen;
812
0
    nva[i].flags = NGHTTP2_NV_FLAG_NONE;
813
0
  }
814
815
0
  if(read_callback) {
816
0
    nghttp2_data_provider data_prd;
817
818
0
    data_prd.read_callback = read_callback;
819
0
    data_prd.source.ptr = read_ctx;
820
0
    stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
821
0
                                       &data_prd, stream_user_data);
822
0
  }
823
0
  else {
824
0
    stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
825
0
                                       NULL, stream_user_data);
826
0
  }
827
828
0
  if(stream_id < 0) {
829
0
    failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
830
0
          nghttp2_strerror(stream_id), stream_id);
831
0
    result = CURLE_SEND_ERROR;
832
0
    goto out;
833
0
  }
834
0
  result = CURLE_OK;
835
836
0
out:
837
0
  free(nva);
838
0
  Curl_dynhds_free(&h2_headers);
839
0
  *pstream_id = stream_id;
840
0
  return result;
841
0
}
842
843
static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
844
                               struct Curl_easy *data,
845
                               struct tunnel_stream *ts)
846
0
{
847
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
848
0
  CURLcode result;
849
0
  struct httpreq *req = NULL;
850
851
0
  infof(data, "Establish HTTP/2 proxy tunnel to %s", ts->authority);
852
853
0
  result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
854
0
                              NULL, 0, ts->authority, strlen(ts->authority),
855
0
                              NULL, 0);
856
0
  if(result)
857
0
    goto out;
858
859
  /* Setup the proxy-authorization header, if any */
860
0
  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
861
0
                                 req->authority, TRUE);
862
0
  if(result)
863
0
    goto out;
864
865
0
  if(data->state.aptr.proxyuserpwd) {
866
0
    result = Curl_dynhds_h1_cadd_line(&req->headers,
867
0
                                      data->state.aptr.proxyuserpwd);
868
0
    if(result)
869
0
      goto out;
870
0
  }
871
872
0
  if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent"))
873
0
     && data->set.str[STRING_USERAGENT]) {
874
0
    result = Curl_dynhds_cadd(&req->headers, "User-Agent",
875
0
                              data->set.str[STRING_USERAGENT]);
876
0
    if(result)
877
0
      goto out;
878
0
  }
879
880
0
  result = Curl_dynhds_add_custom(data, TRUE, &req->headers);
881
0
  if(result)
882
0
    goto out;
883
884
0
  result = h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
885
0
                     NULL, ts, tunnel_send_callback, cf);
886
0
  if(result) {
887
0
    DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u",
888
0
                  nghttp2_strerror(ts->stream_id), ts->stream_id));
889
0
  }
890
891
0
out:
892
0
  if(req)
893
0
    Curl_http_req_free(req);
894
0
  if(result)
895
0
    failf(data, "Failed sending CONNECT to proxy");
896
0
  return result;
897
0
}
898
899
static CURLcode inspect_response(struct Curl_cfilter *cf,
900
                                 struct Curl_easy *data,
901
                                 struct tunnel_stream *ts)
902
0
{
903
0
  CURLcode result = CURLE_OK;
904
0
  struct dynhds_entry *auth_reply = NULL;
905
0
  (void)cf;
906
907
0
  DEBUGASSERT(ts->resp);
908
0
  if(ts->resp->status/100 == 2) {
909
0
    infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
910
0
    tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data);
911
0
    return CURLE_OK;
912
0
  }
913
914
0
  if(ts->resp->status == 401) {
915
0
    auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
916
0
  }
917
0
  else if(ts->resp->status == 407) {
918
0
    auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
919
0
  }
920
921
0
  if(auth_reply) {
922
0
    DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'",
923
0
                  auth_reply->value));
924
0
    result = Curl_http_input_auth(data, ts->resp->status == 407,
925
0
                                  auth_reply->value);
926
0
    if(result)
927
0
      return result;
928
0
    if(data->req.newurl) {
929
      /* Inidicator that we should try again */
930
0
      Curl_safefree(data->req.newurl);
931
0
      tunnel_go_state(cf, ts, TUNNEL_INIT, data);
932
0
      return CURLE_OK;
933
0
    }
934
0
  }
935
936
  /* Seems to have failed */
937
0
  return CURLE_RECV_ERROR;
938
0
}
939
940
static CURLcode CONNECT(struct Curl_cfilter *cf,
941
                        struct Curl_easy *data,
942
                        struct tunnel_stream *ts)
943
0
{
944
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
945
0
  CURLcode result = CURLE_OK;
946
947
0
  DEBUGASSERT(ts);
948
0
  DEBUGASSERT(ts->authority);
949
0
  do {
950
0
    switch(ts->state) {
951
0
    case TUNNEL_INIT:
952
      /* Prepare the CONNECT request and make a first attempt to send. */
953
0
      DEBUGF(LOG_CF(data, cf, "CONNECT start for %s", ts->authority));
954
0
      result = submit_CONNECT(cf, data, ts);
955
0
      if(result)
956
0
        goto out;
957
0
      tunnel_go_state(cf, ts, TUNNEL_CONNECT, data);
958
      /* FALLTHROUGH */
959
960
0
    case TUNNEL_CONNECT:
961
      /* see that the request is completely sent */
962
0
      result = h2_progress_ingress(cf, data);
963
0
      if(!result)
964
0
        result = h2_progress_egress(cf, data);
965
0
      if(result) {
966
0
        tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
967
0
        break;
968
0
      }
969
970
0
      if(ts->has_final_response) {
971
0
        tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data);
972
0
      }
973
0
      else {
974
0
        result = CURLE_OK;
975
0
        goto out;
976
0
      }
977
      /* FALLTHROUGH */
978
979
0
    case TUNNEL_RESPONSE:
980
0
      DEBUGASSERT(ts->has_final_response);
981
0
      result = inspect_response(cf, data, ts);
982
0
      if(result)
983
0
        goto out;
984
0
      break;
985
986
0
    case TUNNEL_ESTABLISHED:
987
0
      return CURLE_OK;
988
989
0
    case TUNNEL_FAILED:
990
0
      return CURLE_RECV_ERROR;
991
992
0
    default:
993
0
      break;
994
0
    }
995
996
0
  } while(ts->state == TUNNEL_INIT);
997
998
0
out:
999
0
  if(result || ctx->tunnel.closed)
1000
0
    tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
1001
0
  return result;
1002
0
}
1003
1004
static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
1005
                                    struct Curl_easy *data,
1006
                                    bool blocking, bool *done)
1007
0
{
1008
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1009
0
  CURLcode result = CURLE_OK;
1010
0
  struct cf_call_data save;
1011
0
  timediff_t check;
1012
0
  struct tunnel_stream *ts = &ctx->tunnel;
1013
1014
0
  if(cf->connected) {
1015
0
    *done = TRUE;
1016
0
    return CURLE_OK;
1017
0
  }
1018
1019
  /* Connect the lower filters first */
1020
0
  if(!cf->next->connected) {
1021
0
    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
1022
0
    if(result || !*done)
1023
0
      return result;
1024
0
  }
1025
1026
0
  *done = FALSE;
1027
1028
0
  CF_DATA_SAVE(save, cf, data);
1029
0
  if(!ctx->h2) {
1030
0
    result = cf_h2_proxy_ctx_init(cf, data);
1031
0
    if(result)
1032
0
      goto out;
1033
0
  }
1034
0
  DEBUGASSERT(ts->authority);
1035
1036
0
  check = Curl_timeleft(data, NULL, TRUE);
1037
0
  if(check <= 0) {
1038
0
    failf(data, "Proxy CONNECT aborted due to timeout");
1039
0
    result = CURLE_OPERATION_TIMEDOUT;
1040
0
    goto out;
1041
0
  }
1042
1043
  /* for the secondary socket (FTP), use the "connect to host"
1044
   * but ignore the "connect to port" (use the secondary port)
1045
   */
1046
0
  result = CONNECT(cf, data, ts);
1047
1048
0
out:
1049
0
  *done = (result == CURLE_OK) && (ts->state == TUNNEL_ESTABLISHED);
1050
0
  cf->connected = *done;
1051
0
  CF_DATA_RESTORE(cf, save);
1052
0
  return result;
1053
0
}
1054
1055
static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
1056
0
{
1057
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1058
1059
0
  if(ctx) {
1060
0
    struct cf_call_data save;
1061
1062
0
    CF_DATA_SAVE(save, cf, data);
1063
0
    cf_h2_proxy_ctx_clear(ctx);
1064
0
    CF_DATA_RESTORE(cf, save);
1065
0
  }
1066
0
}
1067
1068
static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
1069
                                struct Curl_easy *data)
1070
0
{
1071
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1072
1073
0
  (void)data;
1074
0
  if(ctx) {
1075
0
    cf_h2_proxy_ctx_free(ctx);
1076
0
    cf->ctx = NULL;
1077
0
  }
1078
0
}
1079
1080
static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
1081
                                     const struct Curl_easy *data)
1082
0
{
1083
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1084
0
  if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
1085
0
     (ctx && ctx->tunnel.state == TUNNEL_ESTABLISHED &&
1086
0
      !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
1087
0
    return TRUE;
1088
0
  return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
1089
0
}
1090
1091
static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf,
1092
                                        struct Curl_easy *data,
1093
                                        curl_socket_t *sock)
1094
0
{
1095
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1096
0
  int bitmap = GETSOCK_BLANK;
1097
0
  struct cf_call_data save;
1098
1099
0
  CF_DATA_SAVE(save, cf, data);
1100
0
  sock[0] = Curl_conn_cf_get_socket(cf, data);
1101
0
  bitmap |= GETSOCK_READSOCK(0);
1102
1103
  /* HTTP/2 layer wants to send data) AND there's a window to send data in */
1104
0
  if(nghttp2_session_want_write(ctx->h2) &&
1105
0
     nghttp2_session_get_remote_window_size(ctx->h2))
1106
0
    bitmap |= GETSOCK_WRITESOCK(0);
1107
1108
0
  CF_DATA_RESTORE(cf, save);
1109
0
  return bitmap;
1110
0
}
1111
1112
static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
1113
                                      struct Curl_easy *data,
1114
                                      CURLcode *err)
1115
0
{
1116
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1117
0
  ssize_t rv = 0;
1118
1119
0
  if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
1120
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new "
1121
0
                  "connection", ctx->tunnel.stream_id));
1122
0
    connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
1123
0
    *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
1124
0
    return -1;
1125
0
  }
1126
0
  else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
1127
0
    failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
1128
0
          ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
1129
0
          ctx->tunnel.error);
1130
0
    *err = CURLE_HTTP2_STREAM;
1131
0
    return -1;
1132
0
  }
1133
0
  else if(ctx->tunnel.reset) {
1134
0
    failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
1135
0
    *err = CURLE_RECV_ERROR;
1136
0
    return -1;
1137
0
  }
1138
1139
0
  *err = CURLE_OK;
1140
0
  rv = 0;
1141
0
  DEBUGF(LOG_CF(data, cf, "handle_tunnel_close -> %zd, %d", rv, *err));
1142
0
  return rv;
1143
0
}
1144
1145
static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
1146
                           char *buf, size_t len, CURLcode *err)
1147
0
{
1148
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1149
0
  ssize_t nread = -1;
1150
1151
0
  *err = CURLE_AGAIN;
1152
0
  if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1153
0
    nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
1154
0
                           (unsigned char *)buf, len, err);
1155
0
    if(nread < 0)
1156
0
      goto out;
1157
0
    DEBUGASSERT(nread > 0);
1158
0
  }
1159
1160
0
  if(nread < 0) {
1161
0
    if(ctx->tunnel.closed) {
1162
0
      nread = h2_handle_tunnel_close(cf, data, err);
1163
0
    }
1164
0
    else if(ctx->tunnel.reset ||
1165
0
            (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
1166
0
            (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
1167
0
      *err = CURLE_RECV_ERROR;
1168
0
      nread = -1;
1169
0
    }
1170
0
  }
1171
0
  else if(nread == 0) {
1172
0
    *err = CURLE_AGAIN;
1173
0
    nread = -1;
1174
0
  }
1175
1176
0
out:
1177
0
  DEBUGF(LOG_CF(data, cf, "tunnel_recv(len=%zu) -> %zd, %d",
1178
0
                len, nread, *err));
1179
0
  return nread;
1180
0
}
1181
1182
static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
1183
                                struct Curl_easy *data,
1184
                                char *buf, size_t len, CURLcode *err)
1185
0
{
1186
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1187
0
  ssize_t nread = -1;
1188
0
  struct cf_call_data save;
1189
0
  CURLcode result;
1190
1191
0
  if(ctx->tunnel.state != TUNNEL_ESTABLISHED) {
1192
0
    *err = CURLE_RECV_ERROR;
1193
0
    return -1;
1194
0
  }
1195
0
  CF_DATA_SAVE(save, cf, data);
1196
1197
0
  if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
1198
0
    *err = h2_progress_ingress(cf, data);
1199
0
    if(*err)
1200
0
      goto out;
1201
0
  }
1202
1203
0
  nread = tunnel_recv(cf, data, buf, len, err);
1204
1205
0
  if(nread > 0) {
1206
0
    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] increase window by %zd",
1207
0
                  ctx->tunnel.stream_id, nread));
1208
0
    nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
1209
0
  }
1210
1211
0
  result = h2_progress_egress(cf, data);
1212
0
  if(result) {
1213
0
    *err = result;
1214
0
    nread = -1;
1215
0
  }
1216
1217
0
out:
1218
0
  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_recv(len=%zu) -> %zd %d",
1219
0
                ctx->tunnel.stream_id, len, nread, *err));
1220
0
  CF_DATA_RESTORE(cf, save);
1221
0
  return nread;
1222
0
}
1223
1224
static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
1225
                                struct Curl_easy *data,
1226
                                const void *mem, size_t len, CURLcode *err)
1227
0
{
1228
0
  struct cf_h2_proxy_ctx *ctx = cf->ctx;
1229
0
  struct cf_call_data save;
1230
0
  ssize_t nwritten = -1;
1231
0
  const unsigned char *buf = mem;
1232
0
  size_t start_len = len;
1233
0
  int rv;
1234
1235
0
  if(ctx->tunnel.state != TUNNEL_ESTABLISHED) {
1236
0
    *err = CURLE_SEND_ERROR;
1237
0
    return -1;
1238
0
  }
1239
0
  CF_DATA_SAVE(save, cf, data);
1240
1241
0
  while(len) {
1242
0
    nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
1243
0
    if(nwritten <= 0) {
1244
0
      if(*err && *err != CURLE_AGAIN) {
1245
0
        DEBUGF(LOG_CF(data, cf, "error adding data to tunnel sendbuf: %d",
1246
0
               *err));
1247
0
        nwritten = -1;
1248
0
        goto out;
1249
0
      }
1250
      /* blocked */
1251
0
      nwritten = 0;
1252
0
    }
1253
0
    else {
1254
0
      DEBUGASSERT((size_t)nwritten <= len);
1255
0
      buf += (size_t)nwritten;
1256
0
      len -= (size_t)nwritten;
1257
0
    }
1258
1259
    /* resume the tunnel stream and let the h2 session send, which
1260
     * triggers reading from tunnel.sendbuf */
1261
0
    rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
1262
0
    if(nghttp2_is_fatal(rv)) {
1263
0
      *err = CURLE_SEND_ERROR;
1264
0
      nwritten = -1;
1265
0
      goto out;
1266
0
    }
1267
0
    *err = h2_progress_egress(cf, data);
1268
0
    if(*err) {
1269
0
      nwritten = -1;
1270
0
      goto out;
1271
0
    }
1272
1273
0
    if(!nwritten && Curl_bufq_is_full(&ctx->tunnel.sendbuf)) {
1274
0
      size_t rwin;
1275
      /* we could not add to the buffer and after session processing,
1276
       * it is still full. */
1277
0
      rwin = nghttp2_session_get_stream_remote_window_size(
1278
0
                                        ctx->h2, ctx->tunnel.stream_id);
1279
0
      DEBUGF(LOG_CF(data, cf, "cf_send: tunnel win %u/%zu",
1280
0
             nghttp2_session_get_remote_window_size(ctx->h2), rwin));
1281
0
      if(rwin == 0) {
1282
        /* We cannot upload more as the stream's remote window size
1283
         * is 0. We need to receive WIN_UPDATEs before we can continue.
1284
         */
1285
0
        data->req.keepon |= KEEP_SEND_HOLD;
1286
0
        DEBUGF(LOG_CF(data, cf, "pausing send as remote flow "
1287
0
               "window is exhausted"));
1288
0
      }
1289
0
      break;
1290
0
    }
1291
0
  }
1292
1293
0
  nwritten = start_len - len;
1294
0
  if(nwritten > 0) {
1295
0
    *err = CURLE_OK;
1296
0
  }
1297
0
  else if(ctx->tunnel.closed) {
1298
0
    nwritten = -1;
1299
0
    *err = CURLE_SEND_ERROR;
1300
0
  }
1301
0
  else {
1302
0
    nwritten = -1;
1303
0
    *err = CURLE_AGAIN;
1304
0
  }
1305
1306
0
out:
1307
0
  DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d ",
1308
0
         start_len, nwritten, *err));
1309
0
  CF_DATA_RESTORE(cf, save);
1310
0
  return nwritten;
1311
0
}
1312
1313
struct Curl_cftype Curl_cft_h2_proxy = {
1314
  "H2-PROXY",
1315
  CF_TYPE_IP_CONNECT,
1316
  CURL_LOG_DEFAULT,
1317
  cf_h2_proxy_destroy,
1318
  cf_h2_proxy_connect,
1319
  cf_h2_proxy_close,
1320
  Curl_cf_http_proxy_get_host,
1321
  cf_h2_proxy_get_select_socks,
1322
  cf_h2_proxy_data_pending,
1323
  cf_h2_proxy_send,
1324
  cf_h2_proxy_recv,
1325
  Curl_cf_def_cntrl,
1326
  Curl_cf_def_conn_is_alive,
1327
  Curl_cf_def_conn_keep_alive,
1328
  Curl_cf_def_query,
1329
};
1330
1331
CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
1332
                                       struct Curl_easy *data)
1333
0
{
1334
0
  struct Curl_cfilter *cf_h2_proxy = NULL;
1335
0
  struct cf_h2_proxy_ctx *ctx;
1336
0
  CURLcode result = CURLE_OUT_OF_MEMORY;
1337
1338
0
  (void)data;
1339
0
  ctx = calloc(sizeof(*ctx), 1);
1340
0
  if(!ctx)
1341
0
    goto out;
1342
1343
0
  result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
1344
0
  if(result)
1345
0
    goto out;
1346
1347
0
  Curl_conn_cf_insert_after(cf, cf_h2_proxy);
1348
0
  result = CURLE_OK;
1349
1350
0
out:
1351
0
  if(result)
1352
0
    cf_h2_proxy_ctx_free(ctx);
1353
0
  return result;
1354
0
}
1355
1356
#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */