xref: /curl/lib/cf-h1-proxy.c (revision cd2b4520)
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 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
28 
29 #include <curl/curl.h>
30 #ifdef USE_HYPER
31 #include <hyper.h>
32 #endif
33 #include "urldata.h"
34 #include "dynbuf.h"
35 #include "sendf.h"
36 #include "http.h"
37 #include "http1.h"
38 #include "http_proxy.h"
39 #include "url.h"
40 #include "select.h"
41 #include "progress.h"
42 #include "cfilters.h"
43 #include "cf-h1-proxy.h"
44 #include "connect.h"
45 #include "curl_trc.h"
46 #include "curlx.h"
47 #include "vtls/vtls.h"
48 #include "transfer.h"
49 #include "multiif.h"
50 
51 /* The last 3 #include files should be in this order */
52 #include "curl_printf.h"
53 #include "curl_memory.h"
54 #include "memdebug.h"
55 
56 
57 typedef enum {
58     H1_TUNNEL_INIT,     /* init/default/no tunnel state */
59     H1_TUNNEL_CONNECT,  /* CONNECT request is being send */
60     H1_TUNNEL_RECEIVE,  /* CONNECT answer is being received */
61     H1_TUNNEL_RESPONSE, /* CONNECT response received completely */
62     H1_TUNNEL_ESTABLISHED,
63     H1_TUNNEL_FAILED
64 } h1_tunnel_state;
65 
66 /* struct for HTTP CONNECT tunneling */
67 struct h1_tunnel_state {
68   struct dynbuf rcvbuf;
69   struct dynbuf request_data;
70   size_t nsent;
71   size_t headerlines;
72   struct Curl_chunker ch;
73   enum keeponval {
74     KEEPON_DONE,
75     KEEPON_CONNECT,
76     KEEPON_IGNORE
77   } keepon;
78   curl_off_t cl; /* size of content to read and ignore */
79   h1_tunnel_state tunnel_state;
80   BIT(chunked_encoding);
81   BIT(close_connection);
82 };
83 
84 
tunnel_is_established(struct h1_tunnel_state * ts)85 static bool tunnel_is_established(struct h1_tunnel_state *ts)
86 {
87   return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
88 }
89 
tunnel_is_failed(struct h1_tunnel_state * ts)90 static bool tunnel_is_failed(struct h1_tunnel_state *ts)
91 {
92   return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
93 }
94 
tunnel_reinit(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts)95 static CURLcode tunnel_reinit(struct Curl_cfilter *cf,
96                               struct Curl_easy *data,
97                               struct h1_tunnel_state *ts)
98 {
99   (void)data;
100   (void)cf;
101   DEBUGASSERT(ts);
102   Curl_dyn_reset(&ts->rcvbuf);
103   Curl_dyn_reset(&ts->request_data);
104   ts->tunnel_state = H1_TUNNEL_INIT;
105   ts->keepon = KEEPON_CONNECT;
106   ts->cl = 0;
107   ts->close_connection = FALSE;
108   return CURLE_OK;
109 }
110 
tunnel_init(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state ** pts)111 static CURLcode tunnel_init(struct Curl_cfilter *cf,
112                             struct Curl_easy *data,
113                             struct h1_tunnel_state **pts)
114 {
115   struct h1_tunnel_state *ts;
116 
117   if(cf->conn->handler->flags & PROTOPT_NOTCPPROXY) {
118     failf(data, "%s cannot be done over CONNECT", cf->conn->handler->scheme);
119     return CURLE_UNSUPPORTED_PROTOCOL;
120   }
121 
122   ts = calloc(1, sizeof(*ts));
123   if(!ts)
124     return CURLE_OUT_OF_MEMORY;
125 
126   infof(data, "allocate connect buffer");
127 
128   Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
129   Curl_dyn_init(&ts->request_data, DYN_HTTP_REQUEST);
130   Curl_httpchunk_init(data, &ts->ch, TRUE);
131 
132   *pts =  ts;
133   connkeep(cf->conn, "HTTP proxy CONNECT");
134   return tunnel_reinit(cf, data, ts);
135 }
136 
h1_tunnel_go_state(struct Curl_cfilter * cf,struct h1_tunnel_state * ts,h1_tunnel_state new_state,struct Curl_easy * data)137 static void h1_tunnel_go_state(struct Curl_cfilter *cf,
138                                struct h1_tunnel_state *ts,
139                                h1_tunnel_state new_state,
140                                struct Curl_easy *data)
141 {
142   if(ts->tunnel_state == new_state)
143     return;
144   /* entering this one */
145   switch(new_state) {
146   case H1_TUNNEL_INIT:
147     CURL_TRC_CF(data, cf, "new tunnel state 'init'");
148     tunnel_reinit(cf, data, ts);
149     break;
150 
151   case H1_TUNNEL_CONNECT:
152     CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
153     ts->tunnel_state = H1_TUNNEL_CONNECT;
154     ts->keepon = KEEPON_CONNECT;
155     Curl_dyn_reset(&ts->rcvbuf);
156     break;
157 
158   case H1_TUNNEL_RECEIVE:
159     CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
160     ts->tunnel_state = H1_TUNNEL_RECEIVE;
161     break;
162 
163   case H1_TUNNEL_RESPONSE:
164     CURL_TRC_CF(data, cf, "new tunnel state 'response'");
165     ts->tunnel_state = H1_TUNNEL_RESPONSE;
166     break;
167 
168   case H1_TUNNEL_ESTABLISHED:
169     CURL_TRC_CF(data, cf, "new tunnel state 'established'");
170     infof(data, "CONNECT phase completed");
171     data->state.authproxy.done = TRUE;
172     data->state.authproxy.multipass = FALSE;
173     FALLTHROUGH();
174   case H1_TUNNEL_FAILED:
175     if(new_state == H1_TUNNEL_FAILED)
176       CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
177     ts->tunnel_state = new_state;
178     Curl_dyn_reset(&ts->rcvbuf);
179     Curl_dyn_reset(&ts->request_data);
180     /* restore the protocol pointer */
181     data->info.httpcode = 0; /* clear it as it might've been used for the
182                                 proxy */
183     /* If a proxy-authorization header was used for the proxy, then we should
184        make sure that it is not accidentally used for the document request
185        after we have connected. So let's free and clear it here. */
186     Curl_safefree(data->state.aptr.proxyuserpwd);
187 #ifdef USE_HYPER
188     data->state.hconnect = FALSE;
189 #endif
190     break;
191   }
192 }
193 
tunnel_free(struct Curl_cfilter * cf,struct Curl_easy * data)194 static void tunnel_free(struct Curl_cfilter *cf,
195                         struct Curl_easy *data)
196 {
197   if(cf) {
198     struct h1_tunnel_state *ts = cf->ctx;
199     if(ts) {
200       h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
201       Curl_dyn_free(&ts->rcvbuf);
202       Curl_dyn_free(&ts->request_data);
203       Curl_httpchunk_free(data, &ts->ch);
204       free(ts);
205       cf->ctx = NULL;
206     }
207   }
208 }
209 
tunnel_want_send(struct h1_tunnel_state * ts)210 static bool tunnel_want_send(struct h1_tunnel_state *ts)
211 {
212   return (ts->tunnel_state == H1_TUNNEL_CONNECT);
213 }
214 
215 #ifndef USE_HYPER
start_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts)216 static CURLcode start_CONNECT(struct Curl_cfilter *cf,
217                               struct Curl_easy *data,
218                               struct h1_tunnel_state *ts)
219 {
220   struct httpreq *req = NULL;
221   int http_minor;
222   CURLcode result;
223 
224     /* This only happens if we have looped here due to authentication
225        reasons, and we do not really use the newly cloned URL here
226        then. Just free() it. */
227   Curl_safefree(data->req.newurl);
228 
229   result = Curl_http_proxy_create_CONNECT(&req, cf, data, 1);
230   if(result)
231     goto out;
232 
233   infof(data, "Establish HTTP proxy tunnel to %s", req->authority);
234 
235   Curl_dyn_reset(&ts->request_data);
236   ts->nsent = 0;
237   ts->headerlines = 0;
238   http_minor = (cf->conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? 0 : 1;
239 
240   result = Curl_h1_req_write_head(req, http_minor, &ts->request_data);
241   if(!result)
242     result = Curl_creader_set_null(data);
243 
244 out:
245   if(result)
246     failf(data, "Failed sending CONNECT to proxy");
247   if(req)
248     Curl_http_req_free(req);
249   return result;
250 }
251 
send_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts,bool * done)252 static CURLcode send_CONNECT(struct Curl_cfilter *cf,
253                              struct Curl_easy *data,
254                              struct h1_tunnel_state *ts,
255                              bool *done)
256 {
257   char *buf = Curl_dyn_ptr(&ts->request_data);
258   size_t request_len = Curl_dyn_len(&ts->request_data);
259   size_t blen = request_len;
260   CURLcode result = CURLE_OK;
261   ssize_t nwritten;
262 
263   if(blen <= ts->nsent)
264     goto out;  /* we are done */
265 
266   blen -= ts->nsent;
267   buf += ts->nsent;
268 
269   nwritten = cf->next->cft->do_send(cf->next, data, buf, blen, FALSE, &result);
270   if(nwritten < 0) {
271     if(result == CURLE_AGAIN) {
272       result = CURLE_OK;
273     }
274     goto out;
275   }
276 
277   DEBUGASSERT(blen >= (size_t)nwritten);
278   ts->nsent += (size_t)nwritten;
279   Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)nwritten);
280 
281 out:
282   if(result)
283     failf(data, "Failed sending CONNECT to proxy");
284   *done = (!result && (ts->nsent >= request_len));
285   return result;
286 }
287 
on_resp_header(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts,const char * header)288 static CURLcode on_resp_header(struct Curl_cfilter *cf,
289                                struct Curl_easy *data,
290                                struct h1_tunnel_state *ts,
291                                const char *header)
292 {
293   CURLcode result = CURLE_OK;
294   struct SingleRequest *k = &data->req;
295   (void)cf;
296 
297   if((checkprefix("WWW-Authenticate:", header) &&
298       (401 == k->httpcode)) ||
299      (checkprefix("Proxy-authenticate:", header) &&
300       (407 == k->httpcode))) {
301 
302     bool proxy = (k->httpcode == 407);
303     char *auth = Curl_copy_header_value(header);
304     if(!auth)
305       return CURLE_OUT_OF_MEMORY;
306 
307     CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
308     result = Curl_http_input_auth(data, proxy, auth);
309 
310     free(auth);
311 
312     if(result)
313       return result;
314   }
315   else if(checkprefix("Content-Length:", header)) {
316     if(k->httpcode/100 == 2) {
317       /* A client MUST ignore any Content-Length or Transfer-Encoding
318          header fields received in a successful response to CONNECT.
319          "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
320       infof(data, "Ignoring Content-Length in CONNECT %03d response",
321             k->httpcode);
322     }
323     else {
324       (void)curlx_strtoofft(header + strlen("Content-Length:"),
325                             NULL, 10, &ts->cl);
326     }
327   }
328   else if(Curl_compareheader(header,
329                              STRCONST("Connection:"), STRCONST("close")))
330     ts->close_connection = TRUE;
331   else if(checkprefix("Transfer-Encoding:", header)) {
332     if(k->httpcode/100 == 2) {
333       /* A client MUST ignore any Content-Length or Transfer-Encoding
334          header fields received in a successful response to CONNECT.
335          "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
336       infof(data, "Ignoring Transfer-Encoding in "
337             "CONNECT %03d response", k->httpcode);
338     }
339     else if(Curl_compareheader(header,
340                                STRCONST("Transfer-Encoding:"),
341                                STRCONST("chunked"))) {
342       infof(data, "CONNECT responded chunked");
343       ts->chunked_encoding = TRUE;
344       /* reset our chunky engine */
345       Curl_httpchunk_reset(data, &ts->ch, TRUE);
346     }
347   }
348   else if(Curl_compareheader(header,
349                              STRCONST("Proxy-Connection:"),
350                              STRCONST("close")))
351     ts->close_connection = TRUE;
352   else if(!strncmp(header, "HTTP/1.", 7) &&
353           ((header[7] == '0') || (header[7] == '1')) &&
354           (header[8] == ' ') &&
355           ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
356           !ISDIGIT(header[12])) {
357     /* store the HTTP code from the proxy */
358     data->info.httpproxycode =  k->httpcode = (header[9] - '0') * 100 +
359       (header[10] - '0') * 10 + (header[11] - '0');
360   }
361   return result;
362 }
363 
recv_CONNECT_resp(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts,bool * done)364 static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
365                                   struct Curl_easy *data,
366                                   struct h1_tunnel_state *ts,
367                                   bool *done)
368 {
369   CURLcode result = CURLE_OK;
370   struct SingleRequest *k = &data->req;
371   char *linep;
372   size_t line_len;
373   int error, writetype;
374 
375 #define SELECT_OK      0
376 #define SELECT_ERROR   1
377 
378   error = SELECT_OK;
379   *done = FALSE;
380 
381   if(!Curl_conn_data_pending(data, cf->sockindex))
382     return CURLE_OK;
383 
384   while(ts->keepon) {
385     ssize_t nread;
386     char byte;
387 
388     /* Read one byte at a time to avoid a race condition. Wait at most one
389        second before looping to ensure continuous pgrsUpdates. */
390     result = Curl_conn_recv(data, cf->sockindex, &byte, 1, &nread);
391     if(result == CURLE_AGAIN)
392       /* socket buffer drained, return */
393       return CURLE_OK;
394 
395     if(Curl_pgrsUpdate(data))
396       return CURLE_ABORTED_BY_CALLBACK;
397 
398     if(result) {
399       ts->keepon = KEEPON_DONE;
400       break;
401     }
402 
403     if(nread <= 0) {
404       if(data->set.proxyauth && data->state.authproxy.avail &&
405          data->state.aptr.proxyuserpwd) {
406         /* proxy auth was requested and there was proxy auth available,
407            then deem this as "mere" proxy disconnect */
408         ts->close_connection = TRUE;
409         infof(data, "Proxy CONNECT connection closed");
410       }
411       else {
412         error = SELECT_ERROR;
413         failf(data, "Proxy CONNECT aborted");
414       }
415       ts->keepon = KEEPON_DONE;
416       break;
417     }
418 
419     if(ts->keepon == KEEPON_IGNORE) {
420       /* This means we are currently ignoring a response-body */
421 
422       if(ts->cl) {
423         /* A Content-Length based body: simply count down the counter
424            and make sure to break out of the loop when we are done! */
425         ts->cl--;
426         if(ts->cl <= 0) {
427           ts->keepon = KEEPON_DONE;
428           break;
429         }
430       }
431       else if(ts->chunked_encoding) {
432         /* chunked-encoded body, so we need to do the chunked dance
433            properly to know when the end of the body is reached */
434         size_t consumed = 0;
435 
436         /* now parse the chunked piece of data so that we can
437            properly tell when the stream ends */
438         result = Curl_httpchunk_read(data, &ts->ch, &byte, 1, &consumed);
439         if(result)
440           return result;
441         if(Curl_httpchunk_is_done(data, &ts->ch)) {
442           /* we are done reading chunks! */
443           infof(data, "chunk reading DONE");
444           ts->keepon = KEEPON_DONE;
445         }
446       }
447       continue;
448     }
449 
450     if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
451       failf(data, "CONNECT response too large");
452       return CURLE_RECV_ERROR;
453     }
454 
455     /* if this is not the end of a header line then continue */
456     if(byte != 0x0a)
457       continue;
458 
459     ts->headerlines++;
460     linep = Curl_dyn_ptr(&ts->rcvbuf);
461     line_len = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
462 
463     /* output debug if that is requested */
464     Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
465 
466     /* send the header to the callback */
467     writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
468       (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
469     result = Curl_client_write(data, writetype, linep, line_len);
470     if(result)
471       return result;
472 
473     result = Curl_bump_headersize(data, line_len, TRUE);
474     if(result)
475       return result;
476 
477     /* Newlines are CRLF, so the CR is ignored as the line is not
478        really terminated until the LF comes. Treat a following CR
479        as end-of-headers as well.*/
480 
481     if(('\r' == linep[0]) ||
482        ('\n' == linep[0])) {
483       /* end of response-headers from the proxy */
484 
485       if((407 == k->httpcode) && !data->state.authproblem) {
486         /* If we get a 407 response code with content length
487            when we have no auth problem, we must ignore the
488            whole response-body */
489         ts->keepon = KEEPON_IGNORE;
490 
491         if(ts->cl) {
492           infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl);
493         }
494         else if(ts->chunked_encoding) {
495           infof(data, "Ignore chunked response-body");
496         }
497         else {
498           /* without content-length or chunked encoding, we
499              cannot keep the connection alive since the close is
500              the end signal so we bail out at once instead */
501           CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
502           ts->keepon = KEEPON_DONE;
503         }
504       }
505       else {
506         ts->keepon = KEEPON_DONE;
507       }
508 
509       DEBUGASSERT(ts->keepon == KEEPON_IGNORE
510                   || ts->keepon == KEEPON_DONE);
511       continue;
512     }
513 
514     result = on_resp_header(cf, data, ts, linep);
515     if(result)
516       return result;
517 
518     Curl_dyn_reset(&ts->rcvbuf);
519   } /* while there is buffer left and loop is requested */
520 
521   if(error)
522     result = CURLE_RECV_ERROR;
523   *done = (ts->keepon == KEEPON_DONE);
524   if(!result && *done && data->info.httpproxycode/100 != 2) {
525     /* Deal with the possibly already received authenticate
526        headers. 'newurl' is set to a new URL if we must loop. */
527     result = Curl_http_auth_act(data);
528   }
529   return result;
530 }
531 
532 #else /* USE_HYPER */
533 
CONNECT_host(struct Curl_cfilter * cf,struct Curl_easy * data,char ** pauthority,char ** phost_header)534 static CURLcode CONNECT_host(struct Curl_cfilter *cf,
535                              struct Curl_easy *data,
536                              char **pauthority,
537                              char **phost_header)
538 {
539   const char *hostname;
540   int port;
541   bool ipv6_ip;
542   CURLcode result;
543   char *authority; /* for CONNECT, the destination host + port */
544   char *host_header = NULL; /* Host: authority */
545 
546   result = Curl_http_proxy_get_destination(cf, &hostname, &port, &ipv6_ip);
547   if(result)
548     return result;
549 
550   authority = aprintf("%s%s%s:%d", ipv6_ip ? "[":"", hostname,
551                       ipv6_ip ? "]" : "", port);
552   if(!authority)
553     return CURLE_OUT_OF_MEMORY;
554 
555   /* If user is not overriding the Host header later */
556   if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("Host"))) {
557     host_header = aprintf("Host: %s\r\n", authority);
558     if(!host_header) {
559       free(authority);
560       return CURLE_OUT_OF_MEMORY;
561     }
562   }
563   *pauthority = authority;
564   *phost_header = host_header;
565   return CURLE_OK;
566 }
567 
568 /* The Hyper version of CONNECT */
start_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts)569 static CURLcode start_CONNECT(struct Curl_cfilter *cf,
570                               struct Curl_easy *data,
571                               struct h1_tunnel_state *ts)
572 {
573   struct connectdata *conn = cf->conn;
574   struct hyptransfer *h = &data->hyp;
575   curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
576   hyper_io *io = NULL;
577   hyper_request *req = NULL;
578   hyper_headers *headers = NULL;
579   hyper_clientconn_options *options = NULL;
580   hyper_task *handshake = NULL;
581   hyper_task *task = NULL; /* for the handshake */
582   hyper_clientconn *client = NULL;
583   hyper_task *sendtask = NULL; /* for the send */
584   char *authority = NULL; /* for CONNECT */
585   char *host_header = NULL; /* Host: */
586   CURLcode result = CURLE_OUT_OF_MEMORY;
587   (void)ts;
588 
589   io = hyper_io_new();
590   if(!io) {
591     failf(data, "Couldn't create hyper IO");
592     result = CURLE_OUT_OF_MEMORY;
593     goto error;
594   }
595   /* tell Hyper how to read/write network data */
596   h->io_ctx.data = data;
597   h->io_ctx.sockindex = cf->sockindex;
598   hyper_io_set_userdata(io, &h->io_ctx);
599   hyper_io_set_read(io, Curl_hyper_recv);
600   hyper_io_set_write(io, Curl_hyper_send);
601   conn->sockfd = tunnelsocket;
602 
603   data->state.hconnect = TRUE;
604 
605   /* create an executor to poll futures */
606   if(!h->exec) {
607     h->exec = hyper_executor_new();
608     if(!h->exec) {
609       failf(data, "Couldn't create hyper executor");
610       result = CURLE_OUT_OF_MEMORY;
611       goto error;
612     }
613   }
614 
615   options = hyper_clientconn_options_new();
616   if(!options) {
617     failf(data, "Couldn't create hyper client options");
618     result = CURLE_OUT_OF_MEMORY;
619     goto error;
620   }
621   hyper_clientconn_options_set_preserve_header_case(options, 1);
622   hyper_clientconn_options_set_preserve_header_order(options, 1);
623 
624   hyper_clientconn_options_exec(options, h->exec);
625 
626   /* "Both the `io` and the `options` are consumed in this function
627      call" */
628   handshake = hyper_clientconn_handshake(io, options);
629   if(!handshake) {
630     failf(data, "Couldn't create hyper client handshake");
631     result = CURLE_OUT_OF_MEMORY;
632     goto error;
633   }
634   io = NULL;
635   options = NULL;
636 
637   if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
638     failf(data, "Couldn't hyper_executor_push the handshake");
639     result = CURLE_OUT_OF_MEMORY;
640     goto error;
641   }
642   handshake = NULL; /* ownership passed on */
643 
644   task = hyper_executor_poll(h->exec);
645   if(!task) {
646     failf(data, "Couldn't hyper_executor_poll the handshake");
647     result = CURLE_OUT_OF_MEMORY;
648     goto error;
649   }
650 
651   client = hyper_task_value(task);
652   hyper_task_free(task);
653 
654   req = hyper_request_new();
655   if(!req) {
656     failf(data, "Couldn't hyper_request_new");
657     result = CURLE_OUT_OF_MEMORY;
658     goto error;
659   }
660   if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
661                               strlen("CONNECT"))) {
662     failf(data, "error setting method");
663     result = CURLE_OUT_OF_MEMORY;
664     goto error;
665   }
666 
667     /* This only happens if we have looped here due to authentication
668        reasons, and we do not really use the newly cloned URL here
669        then. Just free() it. */
670   Curl_safefree(data->req.newurl);
671 
672   result = CONNECT_host(cf, data, &authority, &host_header);
673   if(result)
674     goto error;
675 
676   infof(data, "Establish HTTP proxy tunnel to %s", authority);
677 
678   if(hyper_request_set_uri(req, (uint8_t *)authority,
679                            strlen(authority))) {
680     failf(data, "error setting path");
681     result = CURLE_OUT_OF_MEMORY;
682     goto error;
683   }
684   if(data->set.verbose) {
685     char *se = aprintf("CONNECT %s HTTP/1.1\r\n", authority);
686     if(!se) {
687       result = CURLE_OUT_OF_MEMORY;
688       goto error;
689     }
690     Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
691     free(se);
692   }
693   /* Setup the proxy-authorization header, if any */
694   result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
695                                  authority, TRUE);
696   if(result)
697     goto error;
698   Curl_safefree(authority);
699 
700   /* default is 1.1 */
701   if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
702      (HYPERE_OK != hyper_request_set_version(req,
703                                              HYPER_HTTP_VERSION_1_0))) {
704     failf(data, "error setting HTTP version");
705     result = CURLE_OUT_OF_MEMORY;
706     goto error;
707   }
708 
709   headers = hyper_request_headers(req);
710   if(!headers) {
711     failf(data, "hyper_request_headers");
712     result = CURLE_OUT_OF_MEMORY;
713     goto error;
714   }
715   if(host_header) {
716     result = Curl_hyper_header(data, headers, host_header);
717     if(result)
718       goto error;
719     Curl_safefree(host_header);
720   }
721 
722   if(data->state.aptr.proxyuserpwd) {
723     result = Curl_hyper_header(data, headers,
724                                data->state.aptr.proxyuserpwd);
725     if(result)
726       goto error;
727   }
728 
729   if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
730      data->set.str[STRING_USERAGENT] && *data->set.str[STRING_USERAGENT]) {
731     struct dynbuf ua;
732     Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
733     result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
734                            data->set.str[STRING_USERAGENT]);
735     if(result)
736       goto error;
737     result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
738     if(result)
739       goto error;
740     Curl_dyn_free(&ua);
741   }
742 
743   if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
744     result = Curl_hyper_header(data, headers,
745                                "Proxy-Connection: Keep-Alive");
746     if(result)
747       goto error;
748   }
749 
750   result = Curl_add_custom_headers(data, TRUE, headers);
751   if(result)
752     goto error;
753 
754   result = Curl_creader_set_null(data);
755   if(result)
756     goto error;
757 
758   sendtask = hyper_clientconn_send(client, req);
759   if(!sendtask) {
760     failf(data, "hyper_clientconn_send");
761     result = CURLE_OUT_OF_MEMORY;
762     goto error;
763   }
764   req = NULL;
765 
766   if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
767     failf(data, "Couldn't hyper_executor_push the send");
768     result = CURLE_OUT_OF_MEMORY;
769     goto error;
770   }
771   sendtask = NULL; /* ownership passed on */
772 
773   hyper_clientconn_free(client);
774   client = NULL;
775 
776 error:
777   free(host_header);
778   free(authority);
779   if(io)
780     hyper_io_free(io);
781   if(options)
782     hyper_clientconn_options_free(options);
783   if(handshake)
784     hyper_task_free(handshake);
785   if(client)
786     hyper_clientconn_free(client);
787   if(req)
788     hyper_request_free(req);
789 
790   return result;
791 }
792 
send_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts,bool * done)793 static CURLcode send_CONNECT(struct Curl_cfilter *cf,
794                              struct Curl_easy *data,
795                              struct h1_tunnel_state *ts,
796                              bool *done)
797 {
798   struct hyptransfer *h = &data->hyp;
799   struct connectdata *conn = cf->conn;
800   hyper_task *task = NULL;
801   hyper_error *hypererr = NULL;
802   CURLcode result = CURLE_OK;
803 
804   (void)ts;
805   (void)conn;
806   do {
807     task = hyper_executor_poll(h->exec);
808     if(task) {
809       bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
810       if(error)
811         hypererr = hyper_task_value(task);
812       hyper_task_free(task);
813       if(error) {
814         /* this could probably use a better error code? */
815         result = CURLE_OUT_OF_MEMORY;
816         goto error;
817       }
818     }
819   } while(task);
820 error:
821   *done = (result == CURLE_OK);
822   if(hypererr) {
823     uint8_t errbuf[256];
824     size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
825     failf(data, "Hyper: %.*s", (int)errlen, errbuf);
826     hyper_error_free(hypererr);
827   }
828   return result;
829 }
830 
recv_CONNECT_resp(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts,bool * done)831 static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
832                                   struct Curl_easy *data,
833                                   struct h1_tunnel_state *ts,
834                                   bool *done)
835 {
836   struct hyptransfer *h = &data->hyp;
837   CURLcode result;
838   int didwhat;
839 
840   (void)ts;
841   result = Curl_hyper_stream(data, cf->conn, &didwhat,
842                              CURL_CSELECT_IN | CURL_CSELECT_OUT);
843   *done = data->req.done;
844   if(result || !*done)
845     return result;
846   if(h->exec) {
847     hyper_executor_free(h->exec);
848     h->exec = NULL;
849   }
850   if(h->read_waker) {
851     hyper_waker_free(h->read_waker);
852     h->read_waker = NULL;
853   }
854   if(h->write_waker) {
855     hyper_waker_free(h->write_waker);
856     h->write_waker = NULL;
857   }
858   return result;
859 }
860 
861 #endif /* USE_HYPER */
862 
H1_CONNECT(struct Curl_cfilter * cf,struct Curl_easy * data,struct h1_tunnel_state * ts)863 static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
864                            struct Curl_easy *data,
865                            struct h1_tunnel_state *ts)
866 {
867   struct connectdata *conn = cf->conn;
868   CURLcode result;
869   bool done;
870 
871   if(tunnel_is_established(ts))
872     return CURLE_OK;
873   if(tunnel_is_failed(ts))
874     return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
875 
876   do {
877     timediff_t check;
878 
879     check = Curl_timeleft(data, NULL, TRUE);
880     if(check <= 0) {
881       failf(data, "Proxy CONNECT aborted due to timeout");
882       result = CURLE_OPERATION_TIMEDOUT;
883       goto out;
884     }
885 
886     switch(ts->tunnel_state) {
887     case H1_TUNNEL_INIT:
888       /* Prepare the CONNECT request and make a first attempt to send. */
889       CURL_TRC_CF(data, cf, "CONNECT start");
890       result = start_CONNECT(cf, data, ts);
891       if(result)
892         goto out;
893       h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
894       FALLTHROUGH();
895 
896     case H1_TUNNEL_CONNECT:
897       /* see that the request is completely sent */
898       CURL_TRC_CF(data, cf, "CONNECT send");
899       result = send_CONNECT(cf, data, ts, &done);
900       if(result || !done)
901         goto out;
902       h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
903       FALLTHROUGH();
904 
905     case H1_TUNNEL_RECEIVE:
906       /* read what is there */
907       CURL_TRC_CF(data, cf, "CONNECT receive");
908       result = recv_CONNECT_resp(cf, data, ts, &done);
909       if(Curl_pgrsUpdate(data)) {
910         result = CURLE_ABORTED_BY_CALLBACK;
911         goto out;
912       }
913       /* error or not complete yet. return for more multi-multi */
914       if(result || !done)
915         goto out;
916       /* got it */
917       h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
918       FALLTHROUGH();
919 
920     case H1_TUNNEL_RESPONSE:
921       CURL_TRC_CF(data, cf, "CONNECT response");
922       if(data->req.newurl) {
923         /* not the "final" response, we need to do a follow up request.
924          * If the other side indicated a connection close, or if someone
925          * else told us to close this connection, do so now.
926          */
927         Curl_req_soft_reset(&data->req, data);
928         if(ts->close_connection || conn->bits.close) {
929           /* Close this filter and the sub-chain, re-connect the
930            * sub-chain and continue. Closing this filter will
931            * reset our tunnel state. To avoid recursion, we return
932            * and expect to be called again.
933            */
934           CURL_TRC_CF(data, cf, "CONNECT need to close+open");
935           infof(data, "Connect me again please");
936           Curl_conn_cf_close(cf, data);
937           connkeep(conn, "HTTP proxy CONNECT");
938           result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
939           goto out;
940         }
941         else {
942           /* staying on this connection, reset state */
943           h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
944         }
945       }
946       break;
947 
948     default:
949       break;
950     }
951 
952   } while(data->req.newurl);
953 
954   DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
955   if(data->info.httpproxycode/100 != 2) {
956     /* a non-2xx response and we have no next URL to try. */
957     Curl_safefree(data->req.newurl);
958     /* failure, close this connection to avoid reuse */
959     streamclose(conn, "proxy CONNECT failure");
960     h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
961     failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
962     return CURLE_RECV_ERROR;
963   }
964   /* 2xx response, SUCCESS! */
965   h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
966   infof(data, "CONNECT tunnel established, response %d",
967         data->info.httpproxycode);
968   result = CURLE_OK;
969 
970 out:
971   if(result)
972     h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
973   return result;
974 }
975 
cf_h1_proxy_connect(struct Curl_cfilter * cf,struct Curl_easy * data,bool blocking,bool * done)976 static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
977                                     struct Curl_easy *data,
978                                     bool blocking, bool *done)
979 {
980   CURLcode result;
981   struct h1_tunnel_state *ts = cf->ctx;
982 
983   if(cf->connected) {
984     *done = TRUE;
985     return CURLE_OK;
986   }
987 
988   CURL_TRC_CF(data, cf, "connect");
989   result = cf->next->cft->do_connect(cf->next, data, blocking, done);
990   if(result || !*done)
991     return result;
992 
993   *done = FALSE;
994   if(!ts) {
995     result = tunnel_init(cf, data, &ts);
996     if(result)
997       return result;
998     cf->ctx = ts;
999   }
1000 
1001   /* TODO: can we do blocking? */
1002   /* We want "seamless" operations through HTTP proxy tunnel */
1003 
1004   result = H1_CONNECT(cf, data, ts);
1005   if(result)
1006     goto out;
1007   Curl_safefree(data->state.aptr.proxyuserpwd);
1008 
1009 out:
1010   *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
1011   if(*done) {
1012     cf->connected = TRUE;
1013     /* The real request will follow the CONNECT, reset request partially */
1014     Curl_req_soft_reset(&data->req, data);
1015     Curl_client_reset(data);
1016     Curl_pgrsSetUploadCounter(data, 0);
1017     Curl_pgrsSetDownloadCounter(data, 0);
1018 
1019     tunnel_free(cf, data);
1020   }
1021   return result;
1022 }
1023 
cf_h1_proxy_adjust_pollset(struct Curl_cfilter * cf,struct Curl_easy * data,struct easy_pollset * ps)1024 static void cf_h1_proxy_adjust_pollset(struct Curl_cfilter *cf,
1025                                         struct Curl_easy *data,
1026                                         struct easy_pollset *ps)
1027 {
1028   struct h1_tunnel_state *ts = cf->ctx;
1029 
1030   if(!cf->connected) {
1031     /* If we are not connected, but the filter "below" is
1032      * and not waiting on something, we are tunneling. */
1033     curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
1034     if(ts) {
1035       /* when we have sent a CONNECT to a proxy, we should rather either
1036          wait for the socket to become readable to be able to get the
1037          response headers or if we are still sending the request, wait
1038          for write. */
1039       if(tunnel_want_send(ts))
1040         Curl_pollset_set_out_only(data, ps, sock);
1041       else
1042         Curl_pollset_set_in_only(data, ps, sock);
1043     }
1044     else
1045       Curl_pollset_set_out_only(data, ps, sock);
1046   }
1047 }
1048 
cf_h1_proxy_destroy(struct Curl_cfilter * cf,struct Curl_easy * data)1049 static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
1050                                 struct Curl_easy *data)
1051 {
1052   CURL_TRC_CF(data, cf, "destroy");
1053   tunnel_free(cf, data);
1054 }
1055 
cf_h1_proxy_close(struct Curl_cfilter * cf,struct Curl_easy * data)1056 static void cf_h1_proxy_close(struct Curl_cfilter *cf,
1057                               struct Curl_easy *data)
1058 {
1059   CURL_TRC_CF(data, cf, "close");
1060   if(cf) {
1061     cf->connected = FALSE;
1062     if(cf->ctx) {
1063       h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
1064     }
1065     if(cf->next)
1066       cf->next->cft->do_close(cf->next, data);
1067   }
1068 }
1069 
1070 
1071 struct Curl_cftype Curl_cft_h1_proxy = {
1072   "H1-PROXY",
1073   CF_TYPE_IP_CONNECT|CF_TYPE_PROXY,
1074   0,
1075   cf_h1_proxy_destroy,
1076   cf_h1_proxy_connect,
1077   cf_h1_proxy_close,
1078   Curl_cf_def_shutdown,
1079   Curl_cf_http_proxy_get_host,
1080   cf_h1_proxy_adjust_pollset,
1081   Curl_cf_def_data_pending,
1082   Curl_cf_def_send,
1083   Curl_cf_def_recv,
1084   Curl_cf_def_cntrl,
1085   Curl_cf_def_conn_is_alive,
1086   Curl_cf_def_conn_keep_alive,
1087   Curl_cf_def_query,
1088 };
1089 
Curl_cf_h1_proxy_insert_after(struct Curl_cfilter * cf_at,struct Curl_easy * data)1090 CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
1091                                        struct Curl_easy *data)
1092 {
1093   struct Curl_cfilter *cf;
1094   CURLcode result;
1095 
1096   (void)data;
1097   result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
1098   if(!result)
1099     Curl_conn_cf_insert_after(cf_at, cf);
1100   return result;
1101 }
1102 
1103 #endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */
1104