Coverage Report

Created: 2026-01-09 07:14

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