Coverage Report

Created: 2025-06-09 08:44

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