Coverage Report

Created: 2026-10-15 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/curl/lib/http_proxy.c
Line
Count
Source
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9
 *
10
 * This software is licensed as described in the file COPYING, which
11
 * you should have received as part of this distribution. The terms
12
 * are also available at https://curl.se/docs/copyright.html.
13
 *
14
 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15
 * copies of the Software, and permit persons to whom the Software is
16
 * furnished to do so, under the terms of the COPYING file.
17
 *
18
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19
 * KIND, either express or implied.
20
 *
21
 * SPDX-License-Identifier: curl
22
 *
23
 ***************************************************************************/
24
#include "curl_setup.h"
25
26
#include "http_proxy.h"
27
28
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
29
30
#include "curl_trc.h"
31
#include "http.h"
32
#include "url.h"
33
#include "cfilters.h"
34
#include "cf-h1-proxy.h"
35
#include "cf-h2-proxy.h"
36
#include "connect.h"
37
#include "vauth/vauth.h"
38
#include "vquic/vquic.h"
39
#include "curlx/strparse.h"
40
41
static CURLcode dynhds_add_custom(struct Curl_easy *data,
42
                                  bool is_connect, int httpversion,
43
                                  bool is_udp, struct dynhds *hds)
44
1.48k
{
45
1.48k
  struct connectdata *conn = data->conn;
46
1.48k
  struct curl_slist *h[2];
47
1.48k
  struct curl_slist *headers;
48
1.48k
  int numlists = 1; /* by default */
49
1.48k
  int i;
50
51
1.48k
  enum Curl_proxy_use proxy;
52
53
1.48k
  if(is_connect && !is_udp)
54
1.48k
    proxy = HEADER_CONNECT;
55
0
  else if(is_connect && is_udp)
56
0
    proxy = HEADER_CONNECT_UDP;
57
0
  else
58
0
    proxy = conn->bits.origin_is_proxy ? HEADER_PROXY : HEADER_SERVER;
59
60
1.48k
  switch(proxy) {
61
0
  case HEADER_SERVER:
62
0
    h[0] = data->set.headers;
63
0
    break;
64
0
  case HEADER_PROXY:
65
0
    h[0] = data->set.headers;
66
0
    if(data->set.sep_headers) {
67
0
      h[1] = data->set.proxyheaders;
68
0
      numlists++;
69
0
    }
70
0
    break;
71
1.48k
  case HEADER_CONNECT:
72
1.48k
    if(data->set.sep_headers)
73
1.29k
      h[0] = data->set.proxyheaders;
74
188
    else
75
188
      h[0] = data->set.headers;
76
1.48k
    break;
77
0
  case HEADER_CONNECT_UDP:
78
0
    if(data->set.sep_headers)
79
0
      h[0] = data->set.proxyheaders;
80
0
    else
81
0
      h[0] = data->set.headers;
82
0
    break;
83
1.48k
  }
84
85
  /* loop through one or two lists */
86
2.97k
  for(i = 0; i < numlists; i++) {
87
7.06k
    for(headers = h[i]; headers; headers = headers->next) {
88
5.58k
      struct Curl_str name;
89
5.58k
      const char *value = NULL;
90
5.58k
      size_t valuelen = 0;
91
5.58k
      const char *ptr = headers->data;
92
93
      /* There are 2 quirks in place for custom headers:
94
       * 1. setting only 'name:' to suppress a header from being sent
95
       * 2. setting only 'name;' to send an empty (illegal) header
96
       */
97
5.58k
      if(!curlx_str_cspn(&ptr, &name, ";:")) {
98
4.80k
        if(!curlx_str_single(&ptr, ':')) {
99
1.87k
          curlx_str_passblanks(&ptr);
100
1.87k
          if(*ptr) {
101
1.53k
            value = ptr;
102
1.53k
            valuelen = strlen(value);
103
1.53k
          }
104
345
          else {
105
            /* quirk #1, suppress this header */
106
345
            continue;
107
345
          }
108
1.87k
        }
109
2.93k
        else if(!curlx_str_single(&ptr, ';')) {
110
2.39k
          curlx_str_passblanks(&ptr);
111
2.39k
          if(!*ptr) {
112
            /* quirk #2, send an empty header */
113
2.18k
            value = "";
114
2.18k
            valuelen = 0;
115
2.18k
          }
116
208
          else {
117
            /* this may be used for something else in the future,
118
             * ignore this for now */
119
208
            continue;
120
208
          }
121
2.39k
        }
122
536
        else
123
          /* neither : nor ; in provided header value. We ignore this
124
           * silently */
125
536
          continue;
126
4.80k
      }
127
775
      else
128
        /* no name, move on */
129
775
        continue;
130
131
3.71k
      DEBUGASSERT(curlx_strlen(&name) && value);
132
      /* trim surrounding whitespace so a padded field name (e.g.
133
         `Authorization :`) cannot slip past the Authorization/Cookie check */
134
3.71k
      curlx_str_trimblanks(&name);
135
3.71k
      if(!curlx_strlen(&name))
136
211
        continue;
137
3.50k
      if(data->state.http_host &&
138
         /* a Host: header was sent already, do not pass on any custom Host:
139
            header as that will produce *two* in the same request! */
140
0
         curlx_str_casecompare(&name, "Host"))
141
0
        ;
142
3.50k
      else if(data->state.httpreq == HTTPREQ_POST_FORM &&
143
              /* this header (extended by formdata.c) is sent later */
144
786
              curlx_str_casecompare(&name, "Content-Type"))
145
208
        ;
146
3.29k
      else if(data->state.httpreq == HTTPREQ_POST_MIME &&
147
              /* this header is sent later */
148
549
              curlx_str_casecompare(&name, "Content-Type"))
149
194
        ;
150
3.10k
      else if(data->req.authneg &&
151
              /* while doing auth neg, do not allow the custom length since
152
                 we will force length zero then */
153
0
              curlx_str_casecompare(&name, "Content-Length"))
154
0
        ;
155
3.10k
      else if((httpversion >= 20) &&
156
0
              curlx_str_casecompare(&name, "Transfer-Encoding"))
157
0
        ;
158
      /* HTTP/2 and HTTP/3 do not support chunked requests */
159
3.10k
      else if((curlx_str_casecompare(&name, "Authorization") ||
160
3.10k
               curlx_str_casecompare(&name, "Cookie")) &&
161
              /* be careful of sending this potentially sensitive header to
162
                 other hosts */
163
1.34k
              !Curl_auth_allowed_to_host(data))
164
0
        ;
165
3.10k
      else {
166
3.10k
        CURLcode result =
167
3.10k
          Curl_dynhds_add(hds, curlx_str(&name), curlx_strlen(&name),
168
3.10k
                          value, valuelen);
169
3.10k
        if(result)
170
1
          return result;
171
3.10k
      }
172
3.50k
    }
173
1.48k
  }
174
175
1.48k
  return CURLE_OK;
176
1.48k
}
177
178
struct cf_proxy_ctx {
179
  struct Curl_peer *peer; /* proxy */
180
  struct Curl_peer *tunnel_peer; /* tunnel destination */
181
  uint8_t proxytype;
182
  uint8_t tunnel_transport;
183
  BIT(sub_filter_installed);
184
};
185
186
static int proxy_http_ver_major(proxy_http_ver ver)
187
1.52k
{
188
1.52k
  switch(ver) {
189
1.52k
  case PROXY_HTTP_V1:
190
1.52k
    return 11;
191
0
  case PROXY_HTTP_V2:
192
0
    return 20;
193
0
  case PROXY_HTTP_V3:
194
0
    return 30;
195
1.52k
  }
196
0
  return 0;
197
1.52k
}
198
199
static CURLcode http_proxy_create_CONNECT(struct httpreq **preq,
200
                                          struct Curl_cfilter *cf,
201
                                          struct Curl_easy *data,
202
                                          struct Curl_peer *peer,
203
                                          struct Curl_peer *dest,
204
                                          proxy_http_ver ver)
205
1.52k
{
206
1.52k
  char *authority = NULL;
207
1.52k
  const char *ua;
208
1.52k
  int httpversion = proxy_http_ver_major(ver);
209
1.52k
  CURLcode result;
210
1.52k
  struct httpreq *req = NULL;
211
212
1.52k
  (void)peer;
213
1.52k
  authority = curl_maprintf("%s%s%s:%u",
214
1.52k
                            dest->ipv6 ? "[" : "",
215
1.52k
                            dest->hostname,
216
1.52k
                            dest->ipv6 ? "]" : "",
217
1.52k
                            dest->port);
218
1.52k
  if(!authority) {
219
0
    result = CURLE_OUT_OF_MEMORY;
220
0
    goto out;
221
0
  }
222
223
1.52k
  result = Curl_http_req_make(&req, STRCONST("CONNECT"),
224
1.52k
                              NULL, 0, authority, strlen(authority),
225
1.52k
                              NULL, 0);
226
1.52k
  if(result)
227
0
    goto out;
228
229
  /* Setup the proxy-authorization header, if any */
230
1.52k
  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
231
1.52k
                                 req->authority, NULL, TRUE);
232
1.52k
  if(result)
233
38
    goto out;
234
235
  /* If user is not overriding Host: header, we add for HTTP/1.x */
236
1.48k
  if(ver == PROXY_HTTP_V1 &&
237
1.48k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
238
1.48k
    result = Curl_dynhds_cadd(&req->headers, "Host", authority);
239
1.48k
    if(result)
240
0
      goto out;
241
1.48k
  }
242
243
1.48k
  if(data->req.hd_proxy_auth) {
244
131
    result = Curl_dynhds_h1_cadd_line(&req->headers,
245
131
                                      data->req.hd_proxy_auth);
246
131
    if(result)
247
0
      goto out;
248
131
  }
249
250
1.48k
  ua = CURL_EASY_STR(data, STRING_USERAGENT);
251
1.48k
  if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
252
1.48k
     ua && *ua) {
253
20
    result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua);
254
20
    if(result)
255
2
      goto out;
256
20
  }
257
258
1.48k
  if(ver == PROXY_HTTP_V1 &&
259
1.48k
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
260
1.48k
    result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
261
1.48k
    if(result)
262
1
      goto out;
263
1.48k
  }
264
265
1.48k
  result = dynhds_add_custom(data, TRUE, httpversion,
266
1.48k
                             FALSE, &req->headers);
267
268
1.52k
out:
269
1.52k
  if(result && req) {
270
42
    Curl_http_req_free(req);
271
42
    req = NULL;
272
42
  }
273
1.52k
  curlx_free(authority);
274
1.52k
  *preq = req;
275
1.52k
  return result;
276
1.48k
}
277
278
static CURLcode http_proxy_create_CONNECTUDP(struct httpreq **preq,
279
                                             struct Curl_cfilter *cf,
280
                                             struct Curl_easy *data,
281
                                             struct Curl_peer *peer,
282
                                             struct Curl_peer *dest,
283
                                             proxy_http_ver ver)
284
0
{
285
0
  const char *proxy_scheme = "http", *ua;
286
0
  int httpversion = proxy_http_ver_major(ver);
287
0
  char *authority = NULL;
288
0
  char *path = NULL;
289
0
  char *encoded_host = NULL;
290
0
  struct httpreq *req = NULL;
291
0
  CURLcode result;
292
293
0
  if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS ||
294
0
     cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS2 ||
295
0
     cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS3)
296
0
    proxy_scheme = "https";
297
298
0
  authority = curl_maprintf("%s%s%s:%d",
299
0
                            peer->ipv6 ? "[" : "",
300
0
                            peer->hostname,
301
0
                            peer->ipv6 ? "]" : "",
302
0
                            peer->port);
303
0
  if(!authority) {
304
0
    result = CURLE_OUT_OF_MEMORY;
305
0
    goto out;
306
0
  }
307
308
0
  if(dest->ipv6) {
309
    /* RFC 9298: colons in IPv6 addresses MUST be percent-encoded
310
     * in the URI template (e.g. "2001:db8::1" -> "2001%3Adb8%3A%3A1") */
311
0
    const char *s = dest->hostname;
312
0
    char *d;
313
0
    size_t hlen = strlen(s);
314
0
    encoded_host = curlx_malloc(hlen * 3 + 1);
315
0
    if(!encoded_host) {
316
0
      result = CURLE_OUT_OF_MEMORY;
317
0
      goto out;
318
0
    }
319
0
    d = encoded_host;
320
0
    while(*s) {
321
0
      if(*s == ':') {
322
0
        *d++ = '%';
323
0
        *d++ = '3';
324
0
        *d++ = 'A';
325
0
      }
326
0
      else
327
0
        *d++ = *s;
328
0
      s++;
329
0
    }
330
0
    *d = '\0';
331
0
    path = curl_maprintf("/.well-known/masque/udp/%s/%u/",
332
0
                         encoded_host, (unsigned int)dest->port);
333
0
  }
334
0
  else {
335
0
    path = curl_maprintf("/.well-known/masque/udp/%s/%u/",
336
0
                         dest->hostname, (unsigned int)dest->port);
337
0
  }
338
339
0
  if(!path) {
340
0
    result = CURLE_OUT_OF_MEMORY;
341
0
    goto out;
342
0
  }
343
344
0
  if(ver == PROXY_HTTP_V1) {
345
0
    result = Curl_http_req_make(&req, "GET", CURL_CSTRLEN("GET"),
346
0
                                proxy_scheme, strlen(proxy_scheme),
347
0
                                authority, strlen(authority),
348
0
                                path, strlen(path));
349
0
    if(result)
350
0
      goto out;
351
0
  }
352
0
  else if(ver == PROXY_HTTP_V2 || ver == PROXY_HTTP_V3) {
353
0
    result = Curl_http_req_make(&req, STRCONST("CONNECT"),
354
0
                                proxy_scheme, strlen(proxy_scheme),
355
0
                                authority, strlen(authority),
356
0
                                path, strlen(path));
357
0
    if(result)
358
0
      goto out;
359
0
  }
360
0
  else {
361
0
    result = CURLE_FAILED_INIT;
362
0
    goto out;
363
0
  }
364
365
  /* Setup the proxy-authorization header, if any */
366
0
  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
367
0
                                 req->authority, NULL, TRUE);
368
0
  if(result)
369
0
    goto out;
370
371
  /* If user is not overriding Host: header, we add for HTTP/1.x */
372
0
  if(ver == PROXY_HTTP_V1 &&
373
0
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
374
0
    result = Curl_dynhds_cadd(&req->headers, "Host", authority);
375
0
    if(result)
376
0
      goto out;
377
0
  }
378
379
0
  if(data->req.hd_proxy_auth) {
380
0
    result = Curl_dynhds_h1_cadd_line(&req->headers,
381
0
                                      data->req.hd_proxy_auth);
382
0
    if(result)
383
0
      goto out;
384
0
  }
385
386
0
  ua = CURL_EASY_STR(data, STRING_USERAGENT);
387
0
  if(ver == PROXY_HTTP_V1 &&
388
0
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
389
0
     ua && *ua) {
390
0
    result = Curl_dynhds_cadd(&req->headers, "User-Agent", ua);
391
0
    if(result)
392
0
      goto out;
393
0
  }
394
395
0
  if(ver == PROXY_HTTP_V1 &&
396
0
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
397
0
    result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
398
0
    if(result)
399
0
      goto out;
400
0
  }
401
402
0
  if(ver == PROXY_HTTP_V1) {
403
0
    result = Curl_dynhds_cadd(&req->headers, "Connection", "Upgrade");
404
0
    if(result)
405
0
      goto out;
406
407
0
    result = Curl_dynhds_cadd(&req->headers, "Upgrade", "connect-udp");
408
0
    if(result)
409
0
      goto out;
410
411
0
    result = Curl_dynhds_cadd(&req->headers, "Capsule-Protocol", "?1");
412
0
    if(result)
413
0
      goto out;
414
0
  }
415
0
  else {
416
0
    result = Curl_dynhds_cadd(&req->headers, ":Protocol", "connect-udp");
417
0
    if(result)
418
0
      goto out;
419
420
0
    if(ver >= PROXY_HTTP_V2) {
421
0
      result = Curl_dynhds_cadd(&req->headers, "Capsule-Protocol", "?1");
422
0
      if(result)
423
0
        goto out;
424
0
    }
425
0
  }
426
427
0
  result = dynhds_add_custom(data, TRUE, httpversion,
428
0
                             TRUE, &req->headers);
429
430
0
out:
431
0
  if(result && req) {
432
0
    Curl_http_req_free(req);
433
0
    req = NULL;
434
0
  }
435
0
  curlx_free(authority);
436
0
  curlx_free(path);
437
0
  curlx_free(encoded_host);
438
0
  *preq = req;
439
0
  return result;
440
0
}
441
442
CURLcode Curl_http_proxy_create_tunnel_request(
443
    struct httpreq **preq, struct Curl_cfilter *cf,
444
    struct Curl_easy *data, struct Curl_peer *peer, struct Curl_peer *dest,
445
    proxy_http_ver ver, bool udp_tunnel)
446
1.52k
{
447
1.52k
  CURLcode result = udp_tunnel ?
448
0
    http_proxy_create_CONNECTUDP(preq, cf, data, peer, dest, ver) :
449
1.52k
    http_proxy_create_CONNECT(preq, cf, data, peer, dest, ver);
450
1.52k
  if(result)
451
42
    return result;
452
453
1.48k
  infof(data, "Establishing %s proxy %stunnel to %s:%u",
454
1.48k
        (ver == PROXY_HTTP_V2) ? "HTTP/2" :
455
1.48k
        (ver == PROXY_HTTP_V3) ? "HTTP/3" : "HTTP",
456
1.48k
        udp_tunnel ? "UDP " : "",
457
1.48k
        dest->user_hostname, dest->port);
458
1.48k
  return CURLE_OK;
459
1.52k
}
460
461
CURLcode Curl_http_proxy_inspect_tunnel_response(
462
    struct Curl_cfilter *cf, struct Curl_easy *data,
463
    struct http_resp *resp, bool udp_tunnel,
464
    proxy_inspect_result *presult)
465
0
{
466
0
  struct dynhds_entry *capsule_protocol = NULL;
467
0
  struct dynhds_entry *auth_reply = NULL;
468
0
  size_t i, header_count;
469
0
  CURLcode result = CURLE_OK;
470
471
0
  DEBUGASSERT(resp);
472
473
0
  header_count = Curl_dynhds_count(&resp->headers);
474
0
  infof(data, "CONNECT%s Response Status %d",
475
0
        udp_tunnel ? "-UDP" : "", resp->status);
476
0
  infof(data, "Response Headers (%zu total):", header_count);
477
0
  for(i = 0; i < header_count; i++) {
478
0
    struct dynhds_entry *entry = Curl_dynhds_getn(&resp->headers, i);
479
0
    if(entry)
480
0
      infof(data, "  %s: %s", entry->name, entry->value);
481
0
  }
482
483
0
  if(resp->status == 407) {
484
0
    auth_reply = Curl_dynhds_cget(&resp->headers, "Proxy-Authenticate");
485
0
  }
486
487
0
  if(auth_reply) {
488
0
    CURL_TRC_CF(data, cf, "[0] CONNECT%s: fwd auth header '%s'",
489
0
                udp_tunnel ? "-UDP" : "", auth_reply->value);
490
0
    result = Curl_http_input_auth(data, resp->status == 407,
491
0
                                  auth_reply->value);
492
0
    if(result)
493
0
      return result;
494
0
    if(data->req.newurl) {
495
0
      curlx_safefree(data->req.newurl);
496
0
      *presult = PROXY_INSPECT_AUTH_RETRY;
497
0
      return CURLE_OK;
498
0
    }
499
0
  }
500
501
0
  if(udp_tunnel) {
502
0
    if(resp->status / 100 == 2) {
503
0
      capsule_protocol = Curl_dynhds_cget(&resp->headers,
504
0
                                           "capsule-protocol");
505
0
      if(capsule_protocol) {
506
0
        if(!strncmp(capsule_protocol->value, "?1", 2) &&
507
0
           !capsule_protocol->value[2]) {
508
0
          infof(data, "CONNECT-UDP tunnel established, response %d",
509
0
                resp->status);
510
0
          *presult = PROXY_INSPECT_OK;
511
0
          return CURLE_OK;
512
0
        }
513
0
        failf(data, "Failed to establish CONNECT-UDP tunnel, response %d, "
514
0
              "unsupported capsule-protocol value '%s'",
515
0
              resp->status, capsule_protocol->value);
516
0
        *presult = PROXY_INSPECT_FAILED;
517
0
        return CURLE_COULDNT_CONNECT;
518
0
      }
519
0
      else {
520
        /* NOTE proxies may not set capsule protocol in the headers */
521
0
        infof(data, "CONNECT-UDP tunnel established, response %d "
522
0
                    "but no capsule-protocol header found", resp->status);
523
0
        *presult = PROXY_INSPECT_OK;
524
0
        return CURLE_OK;
525
0
      }
526
0
    }
527
0
    else {
528
0
      failf(data, "Failed to establish CONNECT-UDP tunnel, "
529
0
                  "response %d", resp->status);
530
0
      *presult = PROXY_INSPECT_FAILED;
531
0
      return CURLE_COULDNT_CONNECT;
532
0
    }
533
0
  }
534
535
0
  if(resp->status / 100 == 2) {
536
0
    infof(data, "CONNECT tunnel established, response %d", resp->status);
537
0
    *presult = PROXY_INSPECT_OK;
538
0
    return CURLE_OK;
539
0
  }
540
541
0
  *presult = PROXY_INSPECT_FAILED;
542
0
  return CURLE_COULDNT_CONNECT;
543
0
}
544
545
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
546
                                      struct Curl_easy *data,
547
                                      bool *done)
548
1.64k
{
549
1.64k
  struct cf_proxy_ctx *ctx = cf->ctx;
550
1.64k
  CURLcode result;
551
1.64k
  bool udp_tunnel = TRNSPRT_IS_DGRAM(ctx->tunnel_transport);
552
553
1.64k
  if(cf->connected) {
554
3
    *done = TRUE;
555
3
    return CURLE_OK;
556
3
  }
557
558
1.64k
  CURL_TRC_CF(data, cf, "CONNECT%s", udp_tunnel ? "-UDP" : "");
559
3.16k
connect_sub:
560
  /* in case of h3_proxy, cf->next will be NULL initially */
561
3.16k
  if(cf->next) {
562
3.16k
    result = cf->next->cft->do_connect(cf->next, data, done);
563
3.16k
    if(result || !*done)
564
1.60k
      return result;
565
3.16k
  }
566
567
1.55k
  *done = FALSE;
568
1.55k
  if(!ctx->sub_filter_installed) {
569
1.52k
    const char *alpn = NULL;
570
571
    /* in case of h3_proxy, cf->next will be NULL initially */
572
1.52k
    if(cf->next) {
573
1.52k
      alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
574
1.52k
    }
575
576
1.52k
    if(alpn)
577
0
      infof(data, "CONNECT%s: '%s' negotiated",
578
1.52k
            udp_tunnel ? "-UDP" : "", alpn);
579
1.52k
    else if(!alpn) {
580
      /* No ALPN, proxytype rules. Fake ALPN */
581
1.52k
      infof(data, "CONNECT%s: no ALPN negotiated", udp_tunnel ? "-UDP" : "");
582
1.52k
      switch(ctx->proxytype) {
583
1
      case CURLPROXY_HTTP_1_0:
584
1
        alpn = "http/1.0";
585
1
        break;
586
0
      case CURLPROXY_HTTPS2:
587
0
        alpn = "h2";
588
0
        break;
589
0
      case CURLPROXY_HTTPS3:
590
0
        alpn = "h3";
591
0
        break;
592
1.52k
      default:
593
1.52k
        alpn = "http/1.1";
594
1.52k
        break;
595
1.52k
      }
596
1.52k
    }
597
598
1.52k
    if(!strcmp(alpn, "http/1.0")) {
599
1
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0");
600
1
      result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->peer,
601
1
                                             ctx->tunnel_peer, 10, udp_tunnel);
602
1
      if(result)
603
0
        goto out;
604
1
    }
605
1.52k
    else if(!strcmp(alpn, "http/1.1")) {
606
1.52k
      int httpversion = (ctx->proxytype == CURLPROXY_HTTP_1_0) ? 10 : 11;
607
1.52k
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.%d",
608
1.52k
                  httpversion % 10);
609
1.52k
      result = Curl_cf_h1_proxy_insert_after(cf, data, ctx->peer,
610
1.52k
                                             ctx->tunnel_peer,
611
1.52k
                                             httpversion, udp_tunnel);
612
1.52k
      if(result)
613
0
        goto out;
614
1.52k
    }
615
0
#ifdef USE_NGHTTP2
616
0
    else if(!strcmp(alpn, "h2")) {
617
0
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
618
0
      result = Curl_cf_h2_proxy_insert_after(cf, data, ctx->peer,
619
0
                                             ctx->tunnel_peer, udp_tunnel);
620
0
      if(result)
621
0
        goto out;
622
0
    }
623
0
#endif /* USE_NGHTTP2 */
624
#if defined(USE_PROXY_HTTP3) && defined(USE_NGHTTP3) && \
625
  defined(USE_NGTCP2) && defined(USE_OPENSSL)
626
    else if(!strcmp(alpn, "h3")) {
627
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/3");
628
      result = Curl_cf_h3_proxy_insert_after(cf, data, ctx->peer, ctx->peer,
629
                                             ctx->tunnel_peer,
630
                                             ctx->tunnel_transport);
631
      if(result)
632
        goto out;
633
    }
634
#endif /* USE_PROXY_HTTP3 && USE_NGHTTP3 && USE_NGTCP2 && USE_OPENSSL */
635
0
    else {
636
0
      failf(data, "CONNECT%s: negotiated ALPN '%s' not supported",
637
0
            udp_tunnel ? "-UDP" : "", alpn);
638
0
      result = CURLE_COULDNT_CONNECT;
639
0
      goto out;
640
0
    }
641
642
1.52k
    ctx->sub_filter_installed = TRUE;
643
    /* after we installed the filter "below" us, we call connect
644
     * on out sub-chain again.
645
     */
646
1.52k
    goto connect_sub;
647
1.52k
  }
648
32
  else {
649
    /* subchain connected and we had already installed the protocol filter.
650
     * This means the protocol tunnel is established, we are done. */
651
32
    DEBUGASSERT(ctx->sub_filter_installed);
652
32
    result = CURLE_OK;
653
32
  }
654
655
32
out:
656
32
  if(!result) {
657
32
    cf->connected = TRUE;
658
32
    *done = TRUE;
659
32
  }
660
32
  return result;
661
1.55k
}
662
663
static CURLcode cf_http_proxy_query(struct Curl_cfilter *cf,
664
                                    struct Curl_easy *data,
665
                                    int query, int *pres1, void *pres2)
666
291
{
667
291
  struct cf_proxy_ctx *ctx = cf->ctx;
668
291
  switch(query) {
669
0
  case CF_QUERY_HOST_PORT:
670
0
    *pres1 = (int)ctx->tunnel_peer->port;
671
0
    *((const char **)pres2) = ctx->tunnel_peer->hostname;
672
0
    return CURLE_OK;
673
0
  case CF_QUERY_ALPN_NEGOTIATED: {
674
0
    const char **palpn = pres2;
675
0
    DEBUGASSERT(palpn);
676
0
    *palpn = NULL;
677
0
    return CURLE_OK;
678
0
  }
679
291
  default:
680
291
    break;
681
291
  }
682
291
  return cf->next ?
683
291
    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
684
291
    CURLE_UNKNOWN_OPTION;
685
291
}
686
687
static void cf_https_proxy_ctx_free(struct cf_proxy_ctx *ctx)
688
3.05k
{
689
3.05k
  if(ctx) {
690
1.52k
    Curl_peer_unlink(&ctx->peer);
691
1.52k
    Curl_peer_unlink(&ctx->tunnel_peer);
692
1.52k
    curlx_free(ctx);
693
1.52k
  }
694
3.05k
}
695
696
static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
697
                                  struct Curl_easy *data)
698
1.52k
{
699
1.52k
  struct cf_proxy_ctx *ctx = cf->ctx;
700
1.52k
  if(ctx) {
701
1.52k
    CURL_TRC_CF(data, cf, "destroy");
702
1.52k
    cf_https_proxy_ctx_free(ctx);
703
1.52k
  }
704
1.52k
}
705
706
struct Curl_cftype Curl_cft_http_proxy = {
707
  "HTTP-PROXY",
708
  CF_TYPE_IP_CONNECT | CF_TYPE_PROXY | CF_TYPE_SETUP,
709
  0,
710
  http_proxy_cf_destroy,
711
  http_proxy_cf_connect,
712
  Curl_cf_def_shutdown,
713
  Curl_cf_def_adjust_pollset,
714
  Curl_cf_def_data_pending,
715
  Curl_cf_def_send,
716
  Curl_cf_def_recv,
717
  Curl_cf_def_cntrl,
718
  Curl_cf_def_conn_is_alive,
719
  Curl_cf_def_conn_keep_alive,
720
  cf_http_proxy_query,
721
};
722
723
CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
724
                                         struct Curl_easy *data,
725
                                         struct Curl_peer *peer,
726
                                         struct Curl_peer *tunnel_peer,
727
                                         uint8_t tunnel_transport,
728
                                         uint8_t proxytype)
729
1.52k
{
730
1.52k
  struct Curl_cfilter *cf;
731
1.52k
  struct cf_proxy_ctx *ctx = NULL;
732
1.52k
  CURLcode result;
733
734
1.52k
  (void)data;
735
1.52k
  if(!peer || !tunnel_peer)
736
0
    return CURLE_FAILED_INIT;
737
738
1.52k
  ctx = curlx_calloc(1, sizeof(*ctx));
739
1.52k
  if(!ctx) {
740
0
    result = CURLE_OUT_OF_MEMORY;
741
0
    goto out;
742
0
  }
743
1.52k
  Curl_peer_link(&ctx->peer, peer);
744
1.52k
  Curl_peer_link(&ctx->tunnel_peer, tunnel_peer);
745
1.52k
  ctx->proxytype = proxytype;
746
1.52k
  ctx->tunnel_transport = tunnel_transport;
747
748
1.52k
  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx);
749
1.52k
  if(result)
750
0
    goto out;
751
1.52k
  ctx = NULL;
752
1.52k
  Curl_conn_cf_insert_after(cf_at, cf);
753
754
1.52k
out:
755
1.52k
  cf_https_proxy_ctx_free(ctx);
756
1.52k
  return result;
757
1.52k
}
758
759
uint8_t Curl_http_proxy_transport(uint8_t proxytype)
760
1.57k
{
761
1.57k
  switch(proxytype) {
762
0
  case CURLPROXY_HTTPS3:
763
0
    return TRNSPRT_QUIC;
764
1.57k
  default:
765
1.57k
    return TRNSPRT_TCP;
766
1.57k
  }
767
1.57k
}
768
769
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */