Coverage Report

Created: 2025-12-05 06:38

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