Coverage Report

Created: 2025-10-10 06:31

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