Coverage Report

Created: 2025-07-11 06:33

/src/PROJ/curl/lib/http_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
#include "http_proxy.h"
28
29
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_PROXY)
30
31
#include <curl/curl.h>
32
#include "sendf.h"
33
#include "http.h"
34
#include "url.h"
35
#include "select.h"
36
#include "progress.h"
37
#include "cfilters.h"
38
#include "cf-h1-proxy.h"
39
#include "cf-h2-proxy.h"
40
#include "connect.h"
41
#include "vtls/vtls.h"
42
#include "transfer.h"
43
#include "multiif.h"
44
#include "vauth/vauth.h"
45
#include "curlx/strparse.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
static bool hd_name_eq(const char *n1, size_t n1len,
53
                       const char *n2, size_t n2len)
54
0
{
55
0
  return (n1len == n2len) ? curl_strnequal(n1, n2, n1len) : FALSE;
56
0
}
57
58
static CURLcode dynhds_add_custom(struct Curl_easy *data,
59
                                  bool is_connect, int httpversion,
60
                                  struct dynhds *hds)
61
0
{
62
0
  struct connectdata *conn = data->conn;
63
0
  const char *ptr;
64
0
  struct curl_slist *h[2];
65
0
  struct curl_slist *headers;
66
0
  int numlists = 1; /* by default */
67
0
  int i;
68
69
0
  enum Curl_proxy_use proxy;
70
71
0
  if(is_connect)
72
0
    proxy = HEADER_CONNECT;
73
0
  else
74
0
    proxy = conn->bits.httpproxy && !conn->bits.tunnel_proxy ?
75
0
      HEADER_PROXY : HEADER_SERVER;
76
77
0
  switch(proxy) {
78
0
  case HEADER_SERVER:
79
0
    h[0] = data->set.headers;
80
0
    break;
81
0
  case HEADER_PROXY:
82
0
    h[0] = data->set.headers;
83
0
    if(data->set.sep_headers) {
84
0
      h[1] = data->set.proxyheaders;
85
0
      numlists++;
86
0
    }
87
0
    break;
88
0
  case HEADER_CONNECT:
89
0
    if(data->set.sep_headers)
90
0
      h[0] = data->set.proxyheaders;
91
0
    else
92
0
      h[0] = data->set.headers;
93
0
    break;
94
0
  }
95
96
  /* loop through one or two lists */
97
0
  for(i = 0; i < numlists; i++) {
98
0
    for(headers = h[i]; headers; headers = headers->next) {
99
0
      const char *name, *value;
100
0
      size_t namelen, valuelen;
101
102
      /* There are 2 quirks in place for custom headers:
103
       * 1. setting only 'name:' to suppress a header from being sent
104
       * 2. setting only 'name;' to send an empty (illegal) header
105
       */
106
0
      ptr = strchr(headers->data, ':');
107
0
      if(ptr) {
108
0
        name = headers->data;
109
0
        namelen = ptr - headers->data;
110
0
        ptr++; /* pass the colon */
111
0
        curlx_str_passblanks(&ptr);
112
0
        if(*ptr) {
113
0
          value = ptr;
114
0
          valuelen = strlen(value);
115
0
        }
116
0
        else {
117
          /* quirk #1, suppress this header */
118
0
          continue;
119
0
        }
120
0
      }
121
0
      else {
122
0
        ptr = strchr(headers->data, ';');
123
124
0
        if(!ptr) {
125
          /* neither : nor ; in provided header value. We seem
126
           * to ignore this silently */
127
0
          continue;
128
0
        }
129
130
0
        name = headers->data;
131
0
        namelen = ptr - headers->data;
132
0
        ptr++; /* pass the semicolon */
133
0
        curlx_str_passblanks(&ptr);
134
0
        if(!*ptr) {
135
          /* quirk #2, send an empty header */
136
0
          value = "";
137
0
          valuelen = 0;
138
0
        }
139
0
        else {
140
          /* this may be used for something else in the future,
141
           * ignore this for now */
142
0
          continue;
143
0
        }
144
0
      }
145
146
0
      DEBUGASSERT(name && value);
147
0
      if(data->state.aptr.host &&
148
         /* a Host: header was sent already, do not pass on any custom Host:
149
            header as that will produce *two* in the same request! */
150
0
         hd_name_eq(name, namelen, STRCONST("Host:")))
151
0
        ;
152
0
      else if(data->state.httpreq == HTTPREQ_POST_FORM &&
153
              /* this header (extended by formdata.c) is sent later */
154
0
              hd_name_eq(name, namelen, STRCONST("Content-Type:")))
155
0
        ;
156
0
      else if(data->state.httpreq == HTTPREQ_POST_MIME &&
157
              /* this header is sent later */
158
0
              hd_name_eq(name, namelen, STRCONST("Content-Type:")))
159
0
        ;
160
0
      else if(data->req.authneg &&
161
              /* while doing auth neg, do not allow the custom length since
162
                 we will force length zero then */
163
0
              hd_name_eq(name, namelen, STRCONST("Content-Length:")))
164
0
        ;
165
0
      else if(data->state.aptr.te &&
166
              /* when asking for Transfer-Encoding, do not pass on a custom
167
                 Connection: */
168
0
              hd_name_eq(name, namelen, STRCONST("Connection:")))
169
0
        ;
170
0
      else if((httpversion >= 20) &&
171
0
              hd_name_eq(name, namelen, STRCONST("Transfer-Encoding:")))
172
        /* HTTP/2 and HTTP/3 do not support chunked requests */
173
0
        ;
174
0
      else if((hd_name_eq(name, namelen, STRCONST("Authorization:")) ||
175
0
               hd_name_eq(name, namelen, STRCONST("Cookie:"))) &&
176
              /* be careful of sending this potentially sensitive header to
177
                 other hosts */
178
0
              !Curl_auth_allowed_to_host(data))
179
0
        ;
180
0
      else {
181
0
        CURLcode result;
182
183
0
        result = Curl_dynhds_add(hds, name, namelen, value, valuelen);
184
0
        if(result)
185
0
          return result;
186
0
      }
187
0
    }
188
0
  }
189
190
0
  return CURLE_OK;
191
0
}
192
193
CURLcode Curl_http_proxy_get_destination(struct Curl_cfilter *cf,
194
                                         const char **phostname,
195
                                         int *pport, bool *pipv6_ip)
196
0
{
197
0
  DEBUGASSERT(cf);
198
0
  DEBUGASSERT(cf->conn);
199
200
0
  if(cf->conn->bits.conn_to_host)
201
0
    *phostname = cf->conn->conn_to_host.name;
202
0
  else if(cf->sockindex == SECONDARYSOCKET)
203
0
    *phostname = cf->conn->secondaryhostname;
204
0
  else
205
0
    *phostname = cf->conn->host.name;
206
207
0
  if(cf->sockindex == SECONDARYSOCKET)
208
0
    *pport = cf->conn->secondary_port;
209
0
  else if(cf->conn->bits.conn_to_port)
210
0
    *pport = cf->conn->conn_to_port;
211
0
  else
212
0
    *pport = cf->conn->remote_port;
213
214
0
  if(*phostname != cf->conn->host.name)
215
0
    *pipv6_ip = (strchr(*phostname, ':') != NULL);
216
0
  else
217
0
    *pipv6_ip = cf->conn->bits.ipv6_ip;
218
219
0
  return CURLE_OK;
220
0
}
221
222
struct cf_proxy_ctx {
223
  /* the protocol specific sub-filter we install during connect */
224
  struct Curl_cfilter *cf_protocol;
225
  int httpversion; /* HTTP version used to CONNECT */
226
};
227
228
CURLcode Curl_http_proxy_create_CONNECT(struct httpreq **preq,
229
                                        struct Curl_cfilter *cf,
230
                                        struct Curl_easy *data,
231
                                        int http_version_major)
232
0
{
233
0
  struct cf_proxy_ctx *ctx = cf->ctx;
234
0
  const char *hostname = NULL;
235
0
  char *authority = NULL;
236
0
  int port;
237
0
  bool ipv6_ip;
238
0
  CURLcode result;
239
0
  struct httpreq *req = NULL;
240
241
0
  result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
242
0
  if(result)
243
0
    goto out;
244
245
0
  authority = aprintf("%s%s%s:%d", ipv6_ip ? "[" : "", hostname,
246
0
                      ipv6_ip ?"]" : "", port);
247
0
  if(!authority) {
248
0
    result = CURLE_OUT_OF_MEMORY;
249
0
    goto out;
250
0
  }
251
252
0
  result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
253
0
                              NULL, 0, authority, strlen(authority),
254
0
                              NULL, 0);
255
0
  if(result)
256
0
    goto out;
257
258
  /* Setup the proxy-authorization header, if any */
259
0
  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
260
0
                                 req->authority, TRUE);
261
0
  if(result)
262
0
    goto out;
263
264
  /* If user is not overriding Host: header, we add for HTTP/1.x */
265
0
  if(http_version_major == 1 &&
266
0
     !Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
267
0
    result = Curl_dynhds_cadd(&req->headers, "Host", authority);
268
0
    if(result)
269
0
      goto out;
270
0
  }
271
272
0
  if(data->state.aptr.proxyuserpwd) {
273
0
    result = Curl_dynhds_h1_cadd_line(&req->headers,
274
0
                                      data->state.aptr.proxyuserpwd);
275
0
    if(result)
276
0
      goto out;
277
0
  }
278
279
0
  if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent")) &&
280
0
     data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) {
281
0
    result = Curl_dynhds_cadd(&req->headers, "User-Agent",
282
0
                              data->set.str[STRING_USERAGENT]);
283
0
    if(result)
284
0
      goto out;
285
0
  }
286
287
0
  if(http_version_major == 1 &&
288
0
    !Curl_checkProxyheaders(data, cf->conn, STRCONST("Proxy-Connection"))) {
289
0
    result = Curl_dynhds_cadd(&req->headers, "Proxy-Connection", "Keep-Alive");
290
0
    if(result)
291
0
      goto out;
292
0
  }
293
294
0
  result = dynhds_add_custom(data, TRUE, ctx->httpversion, &req->headers);
295
296
0
out:
297
0
  if(result && req) {
298
0
    Curl_http_req_free(req);
299
0
    req = NULL;
300
0
  }
301
0
  free(authority);
302
0
  *preq = req;
303
0
  return result;
304
0
}
305
306
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
307
                                      struct Curl_easy *data,
308
                                      bool *done)
309
0
{
310
0
  struct cf_proxy_ctx *ctx = cf->ctx;
311
0
  CURLcode result;
312
313
0
  if(cf->connected) {
314
0
    *done = TRUE;
315
0
    return CURLE_OK;
316
0
  }
317
318
0
  CURL_TRC_CF(data, cf, "connect");
319
0
connect_sub:
320
0
  result = cf->next->cft->do_connect(cf->next, data, done);
321
0
  if(result || !*done)
322
0
    return result;
323
324
0
  *done = FALSE;
325
0
  if(!ctx->cf_protocol) {
326
0
    struct Curl_cfilter *cf_protocol = NULL;
327
0
    int httpversion = 0;
328
0
    int alpn = Curl_conn_cf_is_ssl(cf->next) ?
329
0
      cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
330
331
    /* First time call after the subchain connected */
332
0
    switch(alpn) {
333
0
    case CURL_HTTP_VERSION_NONE:
334
0
    case CURL_HTTP_VERSION_1_0:
335
0
    case CURL_HTTP_VERSION_1_1:
336
0
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1");
337
0
      infof(data, "CONNECT tunnel: HTTP/1.%d negotiated",
338
0
            (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1);
339
0
      result = Curl_cf_h1_proxy_insert_after(cf, data);
340
0
      if(result)
341
0
        goto out;
342
0
      cf_protocol = cf->next;
343
0
      httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11;
344
0
      break;
345
#ifdef USE_NGHTTP2
346
    case CURL_HTTP_VERSION_2:
347
      CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
348
      infof(data, "CONNECT tunnel: HTTP/2 negotiated");
349
      result = Curl_cf_h2_proxy_insert_after(cf, data);
350
      if(result)
351
        goto out;
352
      cf_protocol = cf->next;
353
      httpversion = 20;
354
      break;
355
#endif
356
0
    default:
357
0
      infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn);
358
0
      result = CURLE_COULDNT_CONNECT;
359
0
      goto out;
360
0
    }
361
362
0
    ctx->cf_protocol = cf_protocol;
363
0
    ctx->httpversion = httpversion;
364
    /* after we installed the filter "below" us, we call connect
365
     * on out sub-chain again.
366
     */
367
0
    goto connect_sub;
368
0
  }
369
0
  else {
370
    /* subchain connected and we had already installed the protocol filter.
371
     * This means the protocol tunnel is established, we are done.
372
     */
373
0
    DEBUGASSERT(ctx->cf_protocol);
374
0
    result = CURLE_OK;
375
0
  }
376
377
0
out:
378
0
  if(!result) {
379
0
    cf->connected = TRUE;
380
0
    *done = TRUE;
381
0
  }
382
0
  return result;
383
0
}
384
385
CURLcode Curl_cf_http_proxy_query(struct Curl_cfilter *cf,
386
                                  struct Curl_easy *data,
387
                                  int query, int *pres1, void *pres2)
388
0
{
389
0
  switch(query) {
390
0
  case CF_QUERY_HOST_PORT:
391
0
    *pres1 = (int)cf->conn->http_proxy.port;
392
0
    *((const char **)pres2) = cf->conn->http_proxy.host.name;
393
0
    return CURLE_OK;
394
0
  default:
395
0
    break;
396
0
  }
397
0
  return cf->next ?
398
0
    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
399
0
    CURLE_UNKNOWN_OPTION;
400
0
}
401
402
static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
403
                                  struct Curl_easy *data)
404
0
{
405
0
  struct cf_proxy_ctx *ctx = cf->ctx;
406
407
0
  (void)data;
408
0
  CURL_TRC_CF(data, cf, "destroy");
409
0
  free(ctx);
410
0
}
411
412
static void http_proxy_cf_close(struct Curl_cfilter *cf,
413
                                struct Curl_easy *data)
414
0
{
415
0
  struct cf_proxy_ctx *ctx = cf->ctx;
416
417
0
  CURL_TRC_CF(data, cf, "close");
418
0
  cf->connected = FALSE;
419
0
  if(ctx->cf_protocol) {
420
0
    struct Curl_cfilter *f;
421
    /* if someone already removed it, we assume he also
422
     * took care of destroying it. */
423
0
    for(f = cf->next; f; f = f->next) {
424
0
      if(f == ctx->cf_protocol) {
425
        /* still in our sub-chain */
426
0
        Curl_conn_cf_discard_sub(cf, ctx->cf_protocol, data, FALSE);
427
0
        break;
428
0
      }
429
0
    }
430
0
    ctx->cf_protocol = NULL;
431
0
  }
432
0
  if(cf->next)
433
0
    cf->next->cft->do_close(cf->next, data);
434
0
}
435
436
437
struct Curl_cftype Curl_cft_http_proxy = {
438
  "HTTP-PROXY",
439
  CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
440
  0,
441
  http_proxy_cf_destroy,
442
  http_proxy_cf_connect,
443
  http_proxy_cf_close,
444
  Curl_cf_def_shutdown,
445
  Curl_cf_def_adjust_pollset,
446
  Curl_cf_def_data_pending,
447
  Curl_cf_def_send,
448
  Curl_cf_def_recv,
449
  Curl_cf_def_cntrl,
450
  Curl_cf_def_conn_is_alive,
451
  Curl_cf_def_conn_keep_alive,
452
  Curl_cf_http_proxy_query,
453
};
454
455
CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
456
                                         struct Curl_easy *data)
457
0
{
458
0
  struct Curl_cfilter *cf;
459
0
  struct cf_proxy_ctx *ctx = NULL;
460
0
  CURLcode result;
461
462
0
  (void)data;
463
0
  ctx = calloc(1, sizeof(*ctx));
464
0
  if(!ctx) {
465
0
    result = CURLE_OUT_OF_MEMORY;
466
0
    goto out;
467
0
  }
468
0
  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, ctx);
469
0
  if(result)
470
0
    goto out;
471
0
  ctx = NULL;
472
0
  Curl_conn_cf_insert_after(cf_at, cf);
473
474
0
out:
475
0
  free(ctx);
476
0
  return result;
477
0
}
478
479
#endif /* ! CURL_DISABLE_HTTP && !CURL_DISABLE_PROXY */