Coverage Report

Created: 2022-11-30 06:20

/src/curl/lib/http_proxy.c
Line
Count
Source (jump to first uncovered line)
1
/***************************************************************************
2
 *                                  _   _ ____  _
3
 *  Project                     ___| | | |  _ \| |
4
 *                             / __| | | | |_) | |
5
 *                            | (__| |_| |  _ <| |___
6
 *                             \___|\___/|_| \_\_____|
7
 *
8
 * Copyright (C) 1998 - 2022, 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_PROXY) && !defined(CURL_DISABLE_HTTP)
30
31
#include <curl/curl.h>
32
#ifdef USE_HYPER
33
#include <hyper.h>
34
#endif
35
#include "sendf.h"
36
#include "http.h"
37
#include "url.h"
38
#include "select.h"
39
#include "progress.h"
40
#include "cfilters.h"
41
#include "connect.h"
42
#include "curlx.h"
43
#include "vtls/vtls.h"
44
#include "transfer.h"
45
#include "multiif.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
typedef enum {
53
    TUNNEL_INIT,     /* init/default/no tunnel state */
54
    TUNNEL_CONNECT,  /* CONNECT request is being send */
55
    TUNNEL_RECEIVE,  /* CONNECT answer is being received */
56
    TUNNEL_RESPONSE, /* CONNECT response received completely */
57
    TUNNEL_ESTABLISHED,
58
    TUNNEL_FAILED
59
} tunnel_state;
60
61
/* struct for HTTP CONNECT tunneling */
62
struct tunnel_state {
63
  int sockindex;
64
  const char *hostname;
65
  int remote_port;
66
  struct HTTP http_proxy;
67
  struct HTTP *prot_save;
68
  struct dynbuf rcvbuf;
69
  struct dynbuf req;
70
  size_t nsend;
71
  size_t headerlines;
72
  enum keeponval {
73
    KEEPON_DONE,
74
    KEEPON_CONNECT,
75
    KEEPON_IGNORE
76
  } keepon;
77
  curl_off_t cl; /* size of content to read and ignore */
78
  tunnel_state tunnel_state;
79
  BIT(chunked_encoding);
80
  BIT(close_connection);
81
};
82
83
84
static bool tunnel_is_established(struct tunnel_state *ts)
85
0
{
86
0
  return ts && (ts->tunnel_state == TUNNEL_ESTABLISHED);
87
0
}
88
89
static bool tunnel_is_failed(struct tunnel_state *ts)
90
0
{
91
0
  return ts && (ts->tunnel_state == TUNNEL_FAILED);
92
0
}
93
94
static CURLcode tunnel_reinit(struct tunnel_state *ts,
95
                              struct connectdata *conn,
96
                              struct Curl_easy *data)
97
0
{
98
0
  (void)data;
99
0
  DEBUGASSERT(ts);
100
0
  Curl_dyn_reset(&ts->rcvbuf);
101
0
  Curl_dyn_reset(&ts->req);
102
0
  ts->tunnel_state = TUNNEL_INIT;
103
0
  ts->keepon = KEEPON_CONNECT;
104
0
  ts->cl = 0;
105
0
  ts->close_connection = FALSE;
106
107
0
  if(conn->bits.conn_to_host)
108
0
    ts->hostname = conn->conn_to_host.name;
109
0
  else if(ts->sockindex == SECONDARYSOCKET)
110
0
    ts->hostname = conn->secondaryhostname;
111
0
  else
112
0
    ts->hostname = conn->host.name;
113
114
0
  if(ts->sockindex == SECONDARYSOCKET)
115
0
    ts->remote_port = conn->secondary_port;
116
0
  else if(conn->bits.conn_to_port)
117
0
    ts->remote_port = conn->conn_to_port;
118
0
  else
119
0
    ts->remote_port = conn->remote_port;
120
121
0
  return CURLE_OK;
122
0
}
123
124
static CURLcode tunnel_init(struct tunnel_state **pts,
125
                            struct Curl_easy *data,
126
                            struct connectdata *conn,
127
                            int sockindex)
128
0
{
129
0
  struct tunnel_state *ts;
130
0
  CURLcode result;
131
132
0
  if(conn->handler->flags & PROTOPT_NOTCPPROXY) {
133
0
    failf(data, "%s cannot be done over CONNECT", conn->handler->scheme);
134
0
    return CURLE_UNSUPPORTED_PROTOCOL;
135
0
  }
136
137
  /* we might need the upload buffer for streaming a partial request */
138
0
  result = Curl_get_upload_buffer(data);
139
0
  if(result)
140
0
    return result;
141
142
0
  ts = calloc(1, sizeof(*ts));
143
0
  if(!ts)
144
0
    return CURLE_OUT_OF_MEMORY;
145
146
0
  ts->sockindex = sockindex;
147
0
  infof(data, "allocate connect buffer");
148
149
0
  Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
150
0
  Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
151
152
  /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
153
   * member conn->proto.http; we want [protocol] through HTTP and we have
154
   * to change the member temporarily for connecting to the HTTP
155
   * proxy. After Curl_proxyCONNECT we have to set back the member to the
156
   * original pointer
157
   *
158
   * This function might be called several times in the multi interface case
159
   * if the proxy's CONNECT response is not instant.
160
   */
161
0
  ts->prot_save = data->req.p.http;
162
0
  data->req.p.http = &ts->http_proxy;
163
0
  *pts =  ts;
164
0
  connkeep(conn, "HTTP proxy CONNECT");
165
0
  return tunnel_reinit(ts, conn, data);
166
0
}
167
168
static void tunnel_go_state(struct Curl_cfilter *cf,
169
                            struct tunnel_state *ts,
170
                            tunnel_state new_state,
171
                            struct Curl_easy *data)
172
0
{
173
0
  if(ts->tunnel_state == new_state)
174
0
    return;
175
  /* leaving this one */
176
0
  switch(ts->tunnel_state) {
177
0
  case TUNNEL_CONNECT:
178
0
    data->req.ignorebody = FALSE;
179
0
    break;
180
0
  default:
181
0
    break;
182
0
  }
183
  /* entering this one */
184
0
  switch(new_state) {
185
0
  case TUNNEL_INIT:
186
0
    tunnel_reinit(ts, cf->conn, data);
187
0
    break;
188
189
0
  case TUNNEL_CONNECT:
190
0
    ts->tunnel_state = TUNNEL_CONNECT;
191
0
    ts->keepon = KEEPON_CONNECT;
192
0
    Curl_dyn_reset(&ts->rcvbuf);
193
0
    break;
194
195
0
  case TUNNEL_RECEIVE:
196
0
    ts->tunnel_state = TUNNEL_RECEIVE;
197
0
    break;
198
199
0
  case TUNNEL_RESPONSE:
200
0
    ts->tunnel_state = TUNNEL_RESPONSE;
201
0
    break;
202
203
0
  case TUNNEL_ESTABLISHED:
204
0
    infof(data, "CONNECT phase completed");
205
0
    data->state.authproxy.done = TRUE;
206
0
    data->state.authproxy.multipass = FALSE;
207
    /* FALLTHROUGH */
208
0
  case TUNNEL_FAILED:
209
0
    ts->tunnel_state = new_state;
210
0
    Curl_dyn_reset(&ts->rcvbuf);
211
0
    Curl_dyn_reset(&ts->req);
212
    /* restore the protocol pointer */
213
0
    data->req.p.http = ts->prot_save;
214
0
    data->info.httpcode = 0; /* clear it as it might've been used for the
215
                                proxy */
216
    /* If a proxy-authorization header was used for the proxy, then we should
217
       make sure that it isn't accidentally used for the document request
218
       after we've connected. So let's free and clear it here. */
219
0
    Curl_safefree(data->state.aptr.proxyuserpwd);
220
0
    data->state.aptr.proxyuserpwd = NULL;
221
#ifdef USE_HYPER
222
    data->state.hconnect = FALSE;
223
#endif
224
0
    break;
225
0
  }
226
0
}
227
228
static void tunnel_free(struct Curl_cfilter *cf,
229
                        struct Curl_easy *data)
230
0
{
231
0
  struct tunnel_state *ts = cf->ctx;
232
0
  if(ts) {
233
0
    tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
234
0
    Curl_dyn_free(&ts->rcvbuf);
235
0
    Curl_dyn_free(&ts->req);
236
0
    free(ts);
237
0
    cf->ctx = NULL;
238
0
  }
239
0
}
240
241
static CURLcode CONNECT_host(struct Curl_easy *data,
242
                             struct connectdata *conn,
243
                             const char *hostname,
244
                             int remote_port,
245
                             char **connecthostp,
246
                             char **hostp)
247
0
{
248
0
  char *hostheader; /* for CONNECT */
249
0
  char *host = NULL; /* Host: */
250
0
  bool ipv6_ip = conn->bits.ipv6_ip;
251
252
  /* the hostname may be different */
253
0
  if(hostname != conn->host.name)
254
0
    ipv6_ip = (strchr(hostname, ':') != NULL);
255
0
  hostheader = /* host:port with IPv6 support */
256
0
    aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
257
0
            remote_port);
258
0
  if(!hostheader)
259
0
    return CURLE_OUT_OF_MEMORY;
260
261
0
  if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) {
262
0
    host = aprintf("Host: %s\r\n", hostheader);
263
0
    if(!host) {
264
0
      free(hostheader);
265
0
      return CURLE_OUT_OF_MEMORY;
266
0
    }
267
0
  }
268
0
  *connecthostp = hostheader;
269
0
  *hostp = host;
270
0
  return CURLE_OK;
271
0
}
272
273
#ifndef USE_HYPER
274
static CURLcode start_CONNECT(struct Curl_easy *data,
275
                              struct connectdata *conn,
276
                              struct tunnel_state *ts)
277
0
{
278
0
  char *hostheader = NULL;
279
0
  char *host = NULL;
280
0
  const char *httpv;
281
0
  CURLcode result;
282
283
0
  infof(data, "Establish HTTP proxy tunnel to %s:%d",
284
0
        ts->hostname, ts->remote_port);
285
286
    /* This only happens if we've looped here due to authentication
287
       reasons, and we don't really use the newly cloned URL here
288
       then. Just free() it. */
289
0
  Curl_safefree(data->req.newurl);
290
291
0
  result = CONNECT_host(data, conn,
292
0
                        ts->hostname, ts->remote_port,
293
0
                        &hostheader, &host);
294
0
  if(result)
295
0
    goto out;
296
297
  /* Setup the proxy-authorization header, if any */
298
0
  result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
299
0
                                 hostheader, TRUE);
300
0
  if(result)
301
0
    goto out;
302
303
0
  httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1";
304
305
0
  result =
306
0
      Curl_dyn_addf(&ts->req,
307
0
                    "CONNECT %s HTTP/%s\r\n"
308
0
                    "%s"  /* Host: */
309
0
                    "%s", /* Proxy-Authorization */
310
0
                    hostheader,
311
0
                    httpv,
312
0
                    host?host:"",
313
0
                    data->state.aptr.proxyuserpwd?
314
0
                    data->state.aptr.proxyuserpwd:"");
315
0
  if(result)
316
0
    goto out;
317
318
0
  if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent"))
319
0
     && data->set.str[STRING_USERAGENT])
320
0
    result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n",
321
0
                           data->set.str[STRING_USERAGENT]);
322
0
  if(result)
323
0
    goto out;
324
325
0
  if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection")))
326
0
    result = Curl_dyn_addn(&ts->req,
327
0
                           STRCONST("Proxy-Connection: Keep-Alive\r\n"));
328
0
  if(result)
329
0
    goto out;
330
331
0
  result = Curl_add_custom_headers(data, TRUE, &ts->req);
332
0
  if(result)
333
0
    goto out;
334
335
  /* CRLF terminate the request */
336
0
  result = Curl_dyn_addn(&ts->req, STRCONST("\r\n"));
337
0
  if(result)
338
0
    goto out;
339
340
  /* Send the connect request to the proxy */
341
0
  result = Curl_buffer_send(&ts->req, data, &data->info.request_size, 0,
342
0
                            ts->sockindex);
343
0
  ts->headerlines = 0;
344
345
0
out:
346
0
  if(result)
347
0
    failf(data, "Failed sending CONNECT to proxy");
348
0
  free(host);
349
0
  free(hostheader);
350
0
  return result;
351
0
}
352
353
static CURLcode send_CONNECT(struct Curl_easy *data,
354
                             struct connectdata *conn,
355
                             struct tunnel_state *ts,
356
                             bool *done)
357
0
{
358
0
  struct SingleRequest *k = &data->req;
359
0
  struct HTTP *http = data->req.p.http;
360
0
  CURLcode result = CURLE_OK;
361
362
0
  if(http->sending != HTTPSEND_REQUEST)
363
0
    goto out;
364
365
0
  if(!ts->nsend) {
366
0
    size_t fillcount;
367
0
    k->upload_fromhere = data->state.ulbuf;
368
0
    result = Curl_fillreadbuffer(data, data->set.upload_buffer_size,
369
0
                                 &fillcount);
370
0
    if(result)
371
0
      goto out;
372
0
    ts->nsend = fillcount;
373
0
  }
374
0
  if(ts->nsend) {
375
0
    ssize_t bytes_written;
376
    /* write to socket (send away data) */
377
0
    result = Curl_write(data,
378
0
                        conn->writesockfd,  /* socket to send to */
379
0
                        k->upload_fromhere, /* buffer pointer */
380
0
                        ts->nsend,           /* buffer size */
381
0
                        &bytes_written);    /* actually sent */
382
0
    if(result)
383
0
      goto out;
384
    /* send to debug callback! */
385
0
    Curl_debug(data, CURLINFO_HEADER_OUT,
386
0
               k->upload_fromhere, bytes_written);
387
388
0
    ts->nsend -= bytes_written;
389
0
    k->upload_fromhere += bytes_written;
390
0
  }
391
0
  if(!ts->nsend)
392
0
    http->sending = HTTPSEND_NADA;
393
394
0
out:
395
0
  if(result)
396
0
    failf(data, "Failed sending CONNECT to proxy");
397
0
  *done = (http->sending != HTTPSEND_REQUEST);
398
0
  return result;
399
0
}
400
401
static CURLcode on_resp_header(struct Curl_easy *data,
402
                               struct tunnel_state *ts,
403
                               const char *header)
404
0
{
405
0
  CURLcode result = CURLE_OK;
406
0
  struct SingleRequest *k = &data->req;
407
0
  int subversion = 0;
408
409
0
  if((checkprefix("WWW-Authenticate:", header) &&
410
0
      (401 == k->httpcode)) ||
411
0
     (checkprefix("Proxy-authenticate:", header) &&
412
0
      (407 == k->httpcode))) {
413
414
0
    bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
415
0
    char *auth = Curl_copy_header_value(header);
416
0
    if(!auth)
417
0
      return CURLE_OUT_OF_MEMORY;
418
419
0
    DEBUGF(infof(data, "CONNECT: fwd auth header '%s'",
420
0
           header));
421
0
    result = Curl_http_input_auth(data, proxy, auth);
422
423
0
    free(auth);
424
425
0
    if(result)
426
0
      return result;
427
0
  }
428
0
  else if(checkprefix("Content-Length:", header)) {
429
0
    if(k->httpcode/100 == 2) {
430
      /* A client MUST ignore any Content-Length or Transfer-Encoding
431
         header fields received in a successful response to CONNECT.
432
         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
433
0
      infof(data, "Ignoring Content-Length in CONNECT %03d response",
434
0
            k->httpcode);
435
0
    }
436
0
    else {
437
0
      (void)curlx_strtoofft(header + strlen("Content-Length:"),
438
0
                            NULL, 10, &ts->cl);
439
0
    }
440
0
  }
441
0
  else if(Curl_compareheader(header,
442
0
                             STRCONST("Connection:"), STRCONST("close")))
443
0
    ts->close_connection = TRUE;
444
0
  else if(checkprefix("Transfer-Encoding:", header)) {
445
0
    if(k->httpcode/100 == 2) {
446
      /* A client MUST ignore any Content-Length or Transfer-Encoding
447
         header fields received in a successful response to CONNECT.
448
         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
449
0
      infof(data, "Ignoring Transfer-Encoding in "
450
0
            "CONNECT %03d response", k->httpcode);
451
0
    }
452
0
    else if(Curl_compareheader(header,
453
0
                               STRCONST("Transfer-Encoding:"),
454
0
                               STRCONST("chunked"))) {
455
0
      infof(data, "CONNECT responded chunked");
456
0
      ts->chunked_encoding = TRUE;
457
      /* init our chunky engine */
458
0
      Curl_httpchunk_init(data);
459
0
    }
460
0
  }
461
0
  else if(Curl_compareheader(header,
462
0
                             STRCONST("Proxy-Connection:"),
463
0
                             STRCONST("close")))
464
0
    ts->close_connection = TRUE;
465
0
  else if(2 == sscanf(header, "HTTP/1.%d %d",
466
0
                      &subversion,
467
0
                      &k->httpcode)) {
468
    /* store the HTTP code from the proxy */
469
0
    data->info.httpproxycode = k->httpcode;
470
0
  }
471
0
  return result;
472
0
}
473
474
static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
475
                                  struct connectdata *conn,
476
                                  struct tunnel_state *ts,
477
                                  bool *done)
478
0
{
479
0
  CURLcode result = CURLE_OK;
480
0
  struct SingleRequest *k = &data->req;
481
0
  curl_socket_t tunnelsocket = conn->sock[ts->sockindex];
482
0
  char *linep;
483
0
  size_t perline;
484
0
  int error;
485
486
0
#define SELECT_OK      0
487
0
#define SELECT_ERROR   1
488
489
0
  error = SELECT_OK;
490
0
  *done = FALSE;
491
492
0
  if(!Curl_conn_data_pending(data, ts->sockindex))
493
0
    return CURLE_OK;
494
495
0
  while(ts->keepon) {
496
0
    ssize_t gotbytes;
497
0
    char byte;
498
499
    /* Read one byte at a time to avoid a race condition. Wait at most one
500
       second before looping to ensure continuous pgrsUpdates. */
501
0
    result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes);
502
0
    if(result == CURLE_AGAIN)
503
      /* socket buffer drained, return */
504
0
      return CURLE_OK;
505
506
0
    if(Curl_pgrsUpdate(data))
507
0
      return CURLE_ABORTED_BY_CALLBACK;
508
509
0
    if(result) {
510
0
      ts->keepon = KEEPON_DONE;
511
0
      break;
512
0
    }
513
514
0
    if(gotbytes <= 0) {
515
0
      if(data->set.proxyauth && data->state.authproxy.avail &&
516
0
         data->state.aptr.proxyuserpwd) {
517
        /* proxy auth was requested and there was proxy auth available,
518
           then deem this as "mere" proxy disconnect */
519
0
        ts->close_connection = TRUE;
520
0
        infof(data, "Proxy CONNECT connection closed");
521
0
      }
522
0
      else {
523
0
        error = SELECT_ERROR;
524
0
        failf(data, "Proxy CONNECT aborted");
525
0
      }
526
0
      ts->keepon = KEEPON_DONE;
527
0
      break;
528
0
    }
529
530
0
    if(ts->keepon == KEEPON_IGNORE) {
531
      /* This means we are currently ignoring a response-body */
532
533
0
      if(ts->cl) {
534
        /* A Content-Length based body: simply count down the counter
535
           and make sure to break out of the loop when we're done! */
536
0
        ts->cl--;
537
0
        if(ts->cl <= 0) {
538
0
          ts->keepon = KEEPON_DONE;
539
0
          break;
540
0
        }
541
0
      }
542
0
      else {
543
        /* chunked-encoded body, so we need to do the chunked dance
544
           properly to know when the end of the body is reached */
545
0
        CHUNKcode r;
546
0
        CURLcode extra;
547
0
        ssize_t tookcareof = 0;
548
549
        /* now parse the chunked piece of data so that we can
550
           properly tell when the stream ends */
551
0
        r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra);
552
0
        if(r == CHUNKE_STOP) {
553
          /* we're done reading chunks! */
554
0
          infof(data, "chunk reading DONE");
555
0
          ts->keepon = KEEPON_DONE;
556
0
        }
557
0
      }
558
0
      continue;
559
0
    }
560
561
0
    if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
562
0
      failf(data, "CONNECT response too large");
563
0
      return CURLE_RECV_ERROR;
564
0
    }
565
566
    /* if this is not the end of a header line then continue */
567
0
    if(byte != 0x0a)
568
0
      continue;
569
570
0
    ts->headerlines++;
571
0
    linep = Curl_dyn_ptr(&ts->rcvbuf);
572
0
    perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
573
574
    /* output debug if that is requested */
575
0
    Curl_debug(data, CURLINFO_HEADER_IN, linep, perline);
576
577
0
    if(!data->set.suppress_connect_headers) {
578
      /* send the header to the callback */
579
0
      int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
580
0
        (data->set.include_header ? CLIENTWRITE_BODY : 0) |
581
0
        (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
582
583
0
      result = Curl_client_write(data, writetype, linep, perline);
584
0
      if(result)
585
0
        return result;
586
0
    }
587
588
0
    data->info.header_size += (long)perline;
589
590
    /* Newlines are CRLF, so the CR is ignored as the line isn't
591
       really terminated until the LF comes. Treat a following CR
592
       as end-of-headers as well.*/
593
594
0
    if(('\r' == linep[0]) ||
595
0
       ('\n' == linep[0])) {
596
      /* end of response-headers from the proxy */
597
598
0
      if((407 == k->httpcode) && !data->state.authproblem) {
599
        /* If we get a 407 response code with content length
600
           when we have no auth problem, we must ignore the
601
           whole response-body */
602
0
        ts->keepon = KEEPON_IGNORE;
603
604
0
        if(ts->cl) {
605
0
          infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
606
0
                " bytes of response-body", ts->cl);
607
0
        }
608
0
        else if(ts->chunked_encoding) {
609
0
          CHUNKcode r;
610
0
          CURLcode extra;
611
612
0
          infof(data, "Ignore chunked response-body");
613
614
          /* We set ignorebody true here since the chunked decoder
615
             function will acknowledge that. Pay attention so that this is
616
             cleared again when this function returns! */
617
0
          k->ignorebody = TRUE;
618
619
0
          if(linep[1] == '\n')
620
            /* this can only be a LF if the letter at index 0 was a CR */
621
0
            linep++;
622
623
          /* now parse the chunked piece of data so that we can properly
624
             tell when the stream ends */
625
0
          r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes,
626
0
                                  &extra);
627
0
          if(r == CHUNKE_STOP) {
628
            /* we're done reading chunks! */
629
0
            infof(data, "chunk reading DONE");
630
0
            ts->keepon = KEEPON_DONE;
631
0
          }
632
0
        }
633
0
        else {
634
          /* without content-length or chunked encoding, we
635
             can't keep the connection alive since the close is
636
             the end signal so we bail out at once instead */
637
0
          DEBUGF(infof(data, "CONNECT: no content-length or chunked"));
638
0
          ts->keepon = KEEPON_DONE;
639
0
        }
640
0
      }
641
0
      else {
642
0
        ts->keepon = KEEPON_DONE;
643
0
      }
644
645
0
      DEBUGASSERT(ts->keepon == KEEPON_IGNORE
646
0
                  || ts->keepon == KEEPON_DONE);
647
0
      continue;
648
0
    }
649
650
0
    result = on_resp_header(data, ts, linep);
651
0
    if(result)
652
0
      return result;
653
654
0
    Curl_dyn_reset(&ts->rcvbuf);
655
0
  } /* while there's buffer left and loop is requested */
656
657
0
  if(error)
658
0
    result = CURLE_RECV_ERROR;
659
0
  *done = (ts->keepon == KEEPON_DONE);
660
0
  if(!result && *done && data->info.httpproxycode/100 != 2) {
661
    /* Deal with the possibly already received authenticate
662
       headers. 'newurl' is set to a new URL if we must loop. */
663
0
    result = Curl_http_auth_act(data);
664
0
  }
665
0
  return result;
666
0
}
667
668
#else /* USE_HYPER */
669
/* The Hyper version of CONNECT */
670
static CURLcode start_CONNECT(struct Curl_easy *data,
671
                              struct connectdata *conn,
672
                              struct tunnel_state *ts)
673
{
674
  struct hyptransfer *h = &data->hyp;
675
  curl_socket_t tunnelsocket = conn->sock[ts->sockindex];
676
  hyper_io *io = NULL;
677
  hyper_request *req = NULL;
678
  hyper_headers *headers = NULL;
679
  hyper_clientconn_options *options = NULL;
680
  hyper_task *handshake = NULL;
681
  hyper_task *task = NULL; /* for the handshake */
682
  hyper_clientconn *client = NULL;
683
  hyper_task *sendtask = NULL; /* for the send */
684
  char *hostheader = NULL; /* for CONNECT */
685
  char *host = NULL; /* Host: */
686
  CURLcode result = CURLE_OUT_OF_MEMORY;
687
688
  io = hyper_io_new();
689
  if(!io) {
690
    failf(data, "Couldn't create hyper IO");
691
    result = CURLE_OUT_OF_MEMORY;
692
    goto error;
693
  }
694
  /* tell Hyper how to read/write network data */
695
  hyper_io_set_userdata(io, data);
696
  hyper_io_set_read(io, Curl_hyper_recv);
697
  hyper_io_set_write(io, Curl_hyper_send);
698
  conn->sockfd = tunnelsocket;
699
700
  data->state.hconnect = TRUE;
701
702
  /* create an executor to poll futures */
703
  if(!h->exec) {
704
    h->exec = hyper_executor_new();
705
    if(!h->exec) {
706
      failf(data, "Couldn't create hyper executor");
707
      result = CURLE_OUT_OF_MEMORY;
708
      goto error;
709
    }
710
  }
711
712
  options = hyper_clientconn_options_new();
713
  hyper_clientconn_options_set_preserve_header_case(options, 1);
714
  hyper_clientconn_options_set_preserve_header_order(options, 1);
715
716
  if(!options) {
717
    failf(data, "Couldn't create hyper client options");
718
    result = CURLE_OUT_OF_MEMORY;
719
    goto error;
720
  }
721
722
  hyper_clientconn_options_exec(options, h->exec);
723
724
  /* "Both the `io` and the `options` are consumed in this function
725
     call" */
726
  handshake = hyper_clientconn_handshake(io, options);
727
  if(!handshake) {
728
    failf(data, "Couldn't create hyper client handshake");
729
    result = CURLE_OUT_OF_MEMORY;
730
    goto error;
731
  }
732
  io = NULL;
733
  options = NULL;
734
735
  if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
736
    failf(data, "Couldn't hyper_executor_push the handshake");
737
    result = CURLE_OUT_OF_MEMORY;
738
    goto error;
739
  }
740
  handshake = NULL; /* ownership passed on */
741
742
  task = hyper_executor_poll(h->exec);
743
  if(!task) {
744
    failf(data, "Couldn't hyper_executor_poll the handshake");
745
    result = CURLE_OUT_OF_MEMORY;
746
    goto error;
747
  }
748
749
  client = hyper_task_value(task);
750
  hyper_task_free(task);
751
  req = hyper_request_new();
752
  if(!req) {
753
    failf(data, "Couldn't hyper_request_new");
754
    result = CURLE_OUT_OF_MEMORY;
755
    goto error;
756
  }
757
  if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
758
                              strlen("CONNECT"))) {
759
    failf(data, "error setting method");
760
    result = CURLE_OUT_OF_MEMORY;
761
    goto error;
762
  }
763
764
  infof(data, "Establish HTTP proxy tunnel to %s:%d",
765
        ts->hostname, ts->remote_port);
766
767
    /* This only happens if we've looped here due to authentication
768
       reasons, and we don't really use the newly cloned URL here
769
       then. Just free() it. */
770
  Curl_safefree(data->req.newurl);
771
772
  result = CONNECT_host(data, conn, ts->hostname, ts->remote_port,
773
                        &hostheader, &host);
774
  if(result)
775
    goto error;
776
777
  if(hyper_request_set_uri(req, (uint8_t *)hostheader,
778
                           strlen(hostheader))) {
779
    failf(data, "error setting path");
780
    result = CURLE_OUT_OF_MEMORY;
781
    goto error;
782
  }
783
  if(data->set.verbose) {
784
    char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader);
785
    if(!se) {
786
      result = CURLE_OUT_OF_MEMORY;
787
      goto error;
788
    }
789
    Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
790
    free(se);
791
  }
792
  /* Setup the proxy-authorization header, if any */
793
  result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
794
                                 hostheader, TRUE);
795
  if(result)
796
    goto error;
797
  Curl_safefree(hostheader);
798
799
  /* default is 1.1 */
800
  if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
801
     (HYPERE_OK != hyper_request_set_version(req,
802
                                             HYPER_HTTP_VERSION_1_0))) {
803
    failf(data, "error setting HTTP version");
804
    result = CURLE_OUT_OF_MEMORY;
805
    goto error;
806
  }
807
808
  headers = hyper_request_headers(req);
809
  if(!headers) {
810
    failf(data, "hyper_request_headers");
811
    result = CURLE_OUT_OF_MEMORY;
812
    goto error;
813
  }
814
  if(host) {
815
    result = Curl_hyper_header(data, headers, host);
816
    if(result)
817
      goto error;
818
    Curl_safefree(host);
819
  }
820
821
  if(data->state.aptr.proxyuserpwd) {
822
    result = Curl_hyper_header(data, headers,
823
                               data->state.aptr.proxyuserpwd);
824
    if(result)
825
      goto error;
826
  }
827
828
  if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
829
     data->set.str[STRING_USERAGENT]) {
830
    struct dynbuf ua;
831
    Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
832
    result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
833
                           data->set.str[STRING_USERAGENT]);
834
    if(result)
835
      goto error;
836
    result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
837
    if(result)
838
      goto error;
839
    Curl_dyn_free(&ua);
840
  }
841
842
  if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
843
    result = Curl_hyper_header(data, headers,
844
                               "Proxy-Connection: Keep-Alive");
845
    if(result)
846
      goto error;
847
  }
848
849
  result = Curl_add_custom_headers(data, TRUE, headers);
850
  if(result)
851
    goto error;
852
853
  sendtask = hyper_clientconn_send(client, req);
854
  if(!sendtask) {
855
    failf(data, "hyper_clientconn_send");
856
    result = CURLE_OUT_OF_MEMORY;
857
    goto error;
858
  }
859
860
  if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
861
    failf(data, "Couldn't hyper_executor_push the send");
862
    result = CURLE_OUT_OF_MEMORY;
863
    goto error;
864
  }
865
866
error:
867
  free(host);
868
  free(hostheader);
869
  if(io)
870
    hyper_io_free(io);
871
  if(options)
872
    hyper_clientconn_options_free(options);
873
  if(handshake)
874
    hyper_task_free(handshake);
875
  if(client)
876
    hyper_clientconn_free(client);
877
  return result;
878
}
879
880
static CURLcode send_CONNECT(struct Curl_easy *data,
881
                             struct connectdata *conn,
882
                             struct tunnel_state *ts,
883
                             bool *done)
884
{
885
  struct hyptransfer *h = &data->hyp;
886
  hyper_task *task = NULL;
887
  hyper_error *hypererr = NULL;
888
  CURLcode result = CURLE_OK;
889
890
  (void)ts;
891
  (void)conn;
892
  do {
893
    task = hyper_executor_poll(h->exec);
894
    if(task) {
895
      bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
896
      if(error)
897
        hypererr = hyper_task_value(task);
898
      hyper_task_free(task);
899
      if(error) {
900
        /* this could probably use a better error code? */
901
        result = CURLE_OUT_OF_MEMORY;
902
        goto error;
903
      }
904
    }
905
  } while(task);
906
error:
907
  *done = (result == CURLE_OK);
908
  if(hypererr) {
909
    uint8_t errbuf[256];
910
    size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
911
    failf(data, "Hyper: %.*s", (int)errlen, errbuf);
912
    hyper_error_free(hypererr);
913
  }
914
  return result;
915
}
916
917
static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
918
                                  struct connectdata *conn,
919
                                  struct tunnel_state *ts,
920
                                  bool *done)
921
{
922
  struct hyptransfer *h = &data->hyp;
923
  CURLcode result;
924
  int didwhat;
925
926
  (void)ts;
927
  *done = FALSE;
928
  result = Curl_hyper_stream(data, conn, &didwhat, done,
929
                             CURL_CSELECT_IN | CURL_CSELECT_OUT);
930
  if(result || !*done)
931
    return result;
932
  if(h->exec) {
933
    hyper_executor_free(h->exec);
934
    h->exec = NULL;
935
  }
936
  if(h->read_waker) {
937
    hyper_waker_free(h->read_waker);
938
    h->read_waker = NULL;
939
  }
940
  if(h->write_waker) {
941
    hyper_waker_free(h->write_waker);
942
    h->write_waker = NULL;
943
  }
944
  return result;
945
}
946
947
#endif /* USE_HYPER */
948
949
static CURLcode CONNECT(struct Curl_cfilter *cf,
950
                        struct Curl_easy *data,
951
                        struct tunnel_state *ts)
952
0
{
953
0
  struct connectdata *conn = cf->conn;
954
0
  CURLcode result;
955
0
  bool done;
956
957
0
  if(tunnel_is_established(ts))
958
0
    return CURLE_OK;
959
0
  if(tunnel_is_failed(ts))
960
0
    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
961
962
0
  do {
963
0
    timediff_t check;
964
965
0
    check = Curl_timeleft(data, NULL, TRUE);
966
0
    if(check <= 0) {
967
0
      failf(data, "Proxy CONNECT aborted due to timeout");
968
0
      result = CURLE_OPERATION_TIMEDOUT;
969
0
      goto out;
970
0
    }
971
972
0
    switch(ts->tunnel_state) {
973
0
    case TUNNEL_INIT:
974
      /* Prepare the CONNECT request and make a first attempt to send. */
975
0
      result = start_CONNECT(data, cf->conn, ts);
976
0
      if(result)
977
0
        goto out;
978
0
      tunnel_go_state(cf, ts, TUNNEL_CONNECT, data);
979
      /* FALLTHROUGH */
980
981
0
    case TUNNEL_CONNECT:
982
      /* see that the request is completely sent */
983
0
      result = send_CONNECT(data, cf->conn, ts, &done);
984
0
      if(result || !done)
985
0
        goto out;
986
0
      tunnel_go_state(cf, ts, TUNNEL_RECEIVE, data);
987
      /* FALLTHROUGH */
988
989
0
    case TUNNEL_RECEIVE:
990
      /* read what is there */
991
0
      result = recv_CONNECT_resp(data, cf->conn, ts, &done);
992
0
      if(Curl_pgrsUpdate(data)) {
993
0
        result = CURLE_ABORTED_BY_CALLBACK;
994
0
        goto out;
995
0
      }
996
      /* error or not complete yet. return for more multi-multi */
997
0
      if(result || !done)
998
0
        goto out;
999
      /* got it */
1000
0
      tunnel_go_state(cf, ts, TUNNEL_RESPONSE, data);
1001
      /* FALLTHROUGH */
1002
1003
0
    case TUNNEL_RESPONSE:
1004
0
      if(data->req.newurl) {
1005
        /* not the "final" response, we need to do a follow up request.
1006
         * If the other side indicated a connection close, or if someone
1007
         * else told us to close this connection, do so now. */
1008
0
        if(ts->close_connection || conn->bits.close) {
1009
          /* Close the filter chain and trigger connect, non-blocking
1010
           * again, so the process is ongoing. This will
1011
           * a) the close resets our tunnel state
1012
           * b) the connect makes sure that there will be a socket
1013
           *    to select on again.
1014
           * We return and expect to be called again. */
1015
0
          infof(data, "Connect me again please");
1016
0
          Curl_conn_close(data, cf->sockindex);
1017
0
          result = cf->next->cft->connect(cf->next, data, FALSE, &done);
1018
0
          goto out;
1019
0
        }
1020
        /* staying on this connection, reset state */
1021
0
        tunnel_go_state(cf, ts, TUNNEL_INIT, data);
1022
0
      }
1023
0
      break;
1024
1025
0
    default:
1026
0
      break;
1027
0
    }
1028
1029
0
  } while(data->req.newurl);
1030
1031
0
  DEBUGASSERT(ts->tunnel_state == TUNNEL_RESPONSE);
1032
0
  if(data->info.httpproxycode/100 != 2) {
1033
    /* a non-2xx response and we have no next url to try. */
1034
0
    free(data->req.newurl);
1035
0
    data->req.newurl = NULL;
1036
    /* failure, close this connection to avoid re-use */
1037
0
    streamclose(conn, "proxy CONNECT failure");
1038
0
    tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
1039
0
    failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
1040
0
    return CURLE_RECV_ERROR;
1041
0
  }
1042
  /* 2xx response, SUCCESS! */
1043
0
  tunnel_go_state(cf, ts, TUNNEL_ESTABLISHED, data);
1044
0
  infof(data, "CONNECT tunnel established, response %d",
1045
0
        data->info.httpproxycode);
1046
0
  result = CURLE_OK;
1047
1048
0
out:
1049
0
  if(result)
1050
0
    tunnel_go_state(cf, ts, TUNNEL_FAILED, data);
1051
0
  return result;
1052
0
}
1053
1054
static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
1055
                                      struct Curl_easy *data,
1056
                                      bool blocking, bool *done)
1057
0
{
1058
0
  CURLcode result;
1059
0
  struct tunnel_state *ts = cf->ctx;
1060
1061
0
  if(cf->connected) {
1062
0
    *done = TRUE;
1063
0
    return CURLE_OK;
1064
0
  }
1065
1066
0
  result = cf->next->cft->connect(cf->next, data, blocking, done);
1067
0
  if(result || !*done)
1068
0
    return result;
1069
1070
  /* TODO: can we do blocking? */
1071
  /* We want "seamless" operations through HTTP proxy tunnel */
1072
1073
  /* for the secondary socket (FTP), use the "connect to host"
1074
   * but ignore the "connect to port" (use the secondary port)
1075
   */
1076
0
  *done = FALSE;
1077
0
  if(!ts) {
1078
0
    result = tunnel_init(&ts, data, cf->conn, cf->sockindex);
1079
0
    if(result)
1080
0
      return result;
1081
0
    cf->ctx = ts;
1082
0
  }
1083
1084
0
  result = CONNECT(cf, data, ts);
1085
0
  if(result)
1086
0
    goto out;
1087
0
  Curl_safefree(data->state.aptr.proxyuserpwd);
1088
1089
0
out:
1090
0
  *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
1091
0
  if (*done) {
1092
0
    cf->connected = TRUE;
1093
0
    tunnel_free(cf, data);
1094
0
  }
1095
0
  return result;
1096
0
}
1097
1098
static void http_proxy_cf_get_host(struct Curl_cfilter *cf,
1099
                                   struct Curl_easy *data,
1100
                                   const char **phost,
1101
                                   const char **pdisplay_host,
1102
                                   int *pport)
1103
0
{
1104
0
  (void)data;
1105
0
  if(!cf->connected) {
1106
0
    *phost = cf->conn->http_proxy.host.name;
1107
0
    *pdisplay_host = cf->conn->http_proxy.host.dispname;
1108
0
    *pport = (int)cf->conn->http_proxy.port;
1109
0
  }
1110
0
  else {
1111
0
    cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
1112
0
  }
1113
0
}
1114
1115
static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf,
1116
                                          struct Curl_easy *data,
1117
                                          curl_socket_t *socks)
1118
0
{
1119
0
  struct tunnel_state *ts = cf->ctx;
1120
0
  struct connectdata *conn = cf->conn;
1121
0
  int fds;
1122
1123
0
  DEBUGASSERT(conn);
1124
0
  fds = cf->next->cft->get_select_socks(cf->next, data, socks);
1125
0
  if(!fds && cf->next->connected && !cf->connected) {
1126
    /* If we are not connected, but the filter "below" is
1127
     * and not waiting on something, we are tunneling. */
1128
0
    socks[0] = conn->sock[cf->sockindex];
1129
0
    if(ts) {
1130
      /* when we've sent a CONNECT to a proxy, we should rather either
1131
         wait for the socket to become readable to be able to get the
1132
         response headers or if we're still sending the request, wait
1133
         for write. */
1134
0
      if(ts->http_proxy.sending == HTTPSEND_REQUEST)
1135
0
        return GETSOCK_WRITESOCK(0);
1136
0
      return GETSOCK_READSOCK(0);
1137
0
    }
1138
0
    return GETSOCK_WRITESOCK(0);
1139
0
  }
1140
0
  return fds;
1141
0
}
1142
1143
static void http_proxy_cf_detach_data(struct Curl_cfilter *cf,
1144
                                      struct Curl_easy *data)
1145
0
{
1146
0
  if(cf->ctx) {
1147
0
    tunnel_free(cf, data);
1148
0
  }
1149
0
}
1150
1151
static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
1152
                                  struct Curl_easy *data)
1153
0
{
1154
0
  http_proxy_cf_detach_data(cf, data);
1155
0
}
1156
1157
static void http_proxy_cf_close(struct Curl_cfilter *cf,
1158
                                struct Curl_easy *data)
1159
0
{
1160
0
  DEBUGASSERT(cf->next);
1161
0
  cf->connected = FALSE;
1162
0
  cf->next->cft->close(cf->next, data);
1163
0
  if(cf->ctx) {
1164
0
    tunnel_go_state(cf, cf->ctx, TUNNEL_INIT, data);
1165
0
  }
1166
0
}
1167
1168
1169
static const struct Curl_cftype cft_http_proxy = {
1170
  "HTTP-PROXY",
1171
  CF_TYPE_IP_CONNECT,
1172
  http_proxy_cf_destroy,
1173
  Curl_cf_def_setup,
1174
  http_proxy_cf_connect,
1175
  http_proxy_cf_close,
1176
  http_proxy_cf_get_host,
1177
  http_proxy_cf_get_select_socks,
1178
  Curl_cf_def_data_pending,
1179
  Curl_cf_def_send,
1180
  Curl_cf_def_recv,
1181
  Curl_cf_def_attach_data,
1182
  http_proxy_cf_detach_data,
1183
};
1184
1185
CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
1186
                                  struct connectdata *conn,
1187
                                  int sockindex)
1188
0
{
1189
0
  struct Curl_cfilter *cf;
1190
0
  CURLcode result;
1191
1192
0
  result = Curl_cf_create(&cf, &cft_http_proxy, NULL);
1193
0
  if(!result)
1194
0
    Curl_conn_cf_add(data, conn, sockindex, cf);
1195
0
  return result;
1196
0
}
1197
1198
1199
static CURLcode send_haproxy_header(struct Curl_cfilter*cf,
1200
                                    struct Curl_easy *data)
1201
0
{
1202
0
  struct dynbuf req;
1203
0
  CURLcode result;
1204
0
  const char *tcp_version;
1205
0
  Curl_dyn_init(&req, DYN_HAXPROXY);
1206
1207
0
#ifdef USE_UNIX_SOCKETS
1208
0
  if(cf->conn->unix_domain_socket)
1209
    /* the buffer is large enough to hold this! */
1210
0
    result = Curl_dyn_addn(&req, STRCONST("PROXY UNKNOWN\r\n"));
1211
0
  else {
1212
0
#endif /* USE_UNIX_SOCKETS */
1213
  /* Emit the correct prefix for IPv6 */
1214
0
  tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4";
1215
1216
0
  result = Curl_dyn_addf(&req, "PROXY %s %s %s %i %i\r\n",
1217
0
                         tcp_version,
1218
0
                         data->info.conn_local_ip,
1219
0
                         data->info.conn_primary_ip,
1220
0
                         data->info.conn_local_port,
1221
0
                         data->info.conn_primary_port);
1222
1223
0
#ifdef USE_UNIX_SOCKETS
1224
0
  }
1225
0
#endif /* USE_UNIX_SOCKETS */
1226
1227
0
  if(!result)
1228
0
    result = Curl_buffer_send(&req, data, &data->info.request_size,
1229
0
                              0, FIRSTSOCKET);
1230
0
  return result;
1231
0
}
1232
1233
static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf,
1234
                                   struct Curl_easy *data,
1235
                                   bool blocking, bool *done)
1236
0
{
1237
0
  CURLcode result;
1238
1239
0
  if(cf->connected) {
1240
0
    *done = TRUE;
1241
0
    return CURLE_OK;
1242
0
  }
1243
1244
0
  result = cf->next->cft->connect(cf->next, data, blocking, done);
1245
0
  if(result || !*done)
1246
0
    return result;
1247
1248
0
  result = send_haproxy_header(cf, data);
1249
0
  *done = (!result);
1250
0
  cf->connected = *done;
1251
0
  return result;
1252
0
}
1253
1254
static const struct Curl_cftype cft_haproxy = {
1255
  "HAPROXY",
1256
  0,
1257
  Curl_cf_def_destroy_this,
1258
  Curl_cf_def_setup,
1259
  haproxy_cf_connect,
1260
  Curl_cf_def_close,
1261
  Curl_cf_def_get_host,
1262
  Curl_cf_def_get_select_socks,
1263
  Curl_cf_def_data_pending,
1264
  Curl_cf_def_send,
1265
  Curl_cf_def_recv,
1266
  Curl_cf_def_attach_data,
1267
  Curl_cf_def_detach_data,
1268
};
1269
1270
CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
1271
                               struct connectdata *conn,
1272
                               int sockindex)
1273
0
{
1274
0
  struct Curl_cfilter *cf;
1275
0
  CURLcode result;
1276
1277
0
  result = Curl_cf_create(&cf, &cft_haproxy, NULL);
1278
0
  if(!result)
1279
0
    Curl_conn_cf_add(data, conn, sockindex, cf);
1280
0
  return result;
1281
0
}
1282
1283
#endif /* !CURL_DISABLE_PROXY &6 ! CURL_DISABLE_HTTP */