Imported Upstream version 7.50.2
[platform/upstream/curl.git] / lib / http_proxy.c
1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2015, 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.haxx.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  ***************************************************************************/
22
23 #include "curl_setup.h"
24
25 #if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
26
27 #include "urldata.h"
28 #include <curl/curl.h>
29 #include "http_proxy.h"
30 #include "sendf.h"
31 #include "http.h"
32 #include "url.h"
33 #include "select.h"
34 #include "rawstr.h"
35 #include "progress.h"
36 #include "non-ascii.h"
37 #include "connect.h"
38 #include "curlx.h"
39
40 /* The last 3 #include files should be in this order */
41 #include "curl_printf.h"
42 #include "curl_memory.h"
43 #include "memdebug.h"
44
45 CURLcode Curl_proxy_connect(struct connectdata *conn)
46 {
47   if(conn->bits.tunnel_proxy && conn->bits.httpproxy) {
48 #ifndef CURL_DISABLE_PROXY
49     /* for [protocol] tunneled through HTTP proxy */
50     struct HTTP http_proxy;
51     void *prot_save;
52     const char *hostname;
53     int remote_port;
54     CURLcode result;
55
56     /* BLOCKING */
57     /* We want "seamless" operations through HTTP proxy tunnel */
58
59     /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
60      * member conn->proto.http; we want [protocol] through HTTP and we have
61      * to change the member temporarily for connecting to the HTTP
62      * proxy. After Curl_proxyCONNECT we have to set back the member to the
63      * original pointer
64      *
65      * This function might be called several times in the multi interface case
66      * if the proxy's CONNTECT response is not instant.
67      */
68     prot_save = conn->data->req.protop;
69     memset(&http_proxy, 0, sizeof(http_proxy));
70     conn->data->req.protop = &http_proxy;
71     connkeep(conn, "HTTP proxy CONNECT");
72     if(conn->bits.conn_to_host)
73       hostname = conn->conn_to_host.name;
74     else
75       hostname = conn->host.name;
76     if(conn->bits.conn_to_port)
77       remote_port = conn->conn_to_port;
78     else
79       remote_port = conn->remote_port;
80     result = Curl_proxyCONNECT(conn, FIRSTSOCKET, hostname,
81                                remote_port, FALSE);
82     conn->data->req.protop = prot_save;
83     if(CURLE_OK != result)
84       return result;
85     Curl_safefree(conn->allocptr.proxyuserpwd);
86 #else
87     return CURLE_NOT_BUILT_IN;
88 #endif
89   }
90   /* no HTTP tunnel proxy, just return */
91   return CURLE_OK;
92 }
93
94 /*
95  * Curl_proxyCONNECT() requires that we're connected to a HTTP proxy. This
96  * function will issue the necessary commands to get a seamless tunnel through
97  * this proxy. After that, the socket can be used just as a normal socket.
98  *
99  * 'blocking' set to TRUE means that this function will do the entire CONNECT
100  * + response in a blocking fashion. Should be avoided!
101  */
102
103 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
104                            int sockindex,
105                            const char *hostname,
106                            int remote_port,
107                            bool blocking)
108 {
109   int subversion=0;
110   struct Curl_easy *data=conn->data;
111   struct SingleRequest *k = &data->req;
112   CURLcode result;
113   curl_socket_t tunnelsocket = conn->sock[sockindex];
114   curl_off_t cl=0;
115   bool closeConnection = FALSE;
116   bool chunked_encoding = FALSE;
117   long check;
118
119 #define SELECT_OK      0
120 #define SELECT_ERROR   1
121 #define SELECT_TIMEOUT 2
122   int error = SELECT_OK;
123
124   if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
125     return CURLE_OK; /* CONNECT is already completed */
126
127   conn->bits.proxy_connect_closed = FALSE;
128
129   do {
130     if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
131       /* BEGIN CONNECT PHASE */
132       char *host_port;
133       Curl_send_buffer *req_buffer;
134
135       infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
136             hostname, remote_port);
137
138         /* This only happens if we've looped here due to authentication
139            reasons, and we don't really use the newly cloned URL here
140            then. Just free() it. */
141       free(data->req.newurl);
142       data->req.newurl = NULL;
143
144       /* initialize a dynamic send-buffer */
145       req_buffer = Curl_add_buffer_init();
146
147       if(!req_buffer)
148         return CURLE_OUT_OF_MEMORY;
149
150       host_port = aprintf("%s:%hu", hostname, remote_port);
151       if(!host_port) {
152         Curl_add_buffer_free(req_buffer);
153         return CURLE_OUT_OF_MEMORY;
154       }
155
156       /* Setup the proxy-authorization header, if any */
157       result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
158
159       free(host_port);
160
161       if(!result) {
162         char *host=(char *)"";
163         const char *proxyconn="";
164         const char *useragent="";
165         const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
166           "1.0" : "1.1";
167         bool ipv6_ip = conn->bits.ipv6_ip;
168         char *hostheader;
169
170         /* the hostname may be different */
171         if(hostname != conn->host.name)
172           ipv6_ip = (strchr(hostname, ':') != NULL);
173         hostheader= /* host:port with IPv6 support */
174           aprintf("%s%s%s:%hu", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
175                   remote_port);
176         if(!hostheader) {
177           Curl_add_buffer_free(req_buffer);
178           return CURLE_OUT_OF_MEMORY;
179         }
180
181         if(!Curl_checkProxyheaders(conn, "Host:")) {
182           host = aprintf("Host: %s\r\n", hostheader);
183           if(!host) {
184             free(hostheader);
185             Curl_add_buffer_free(req_buffer);
186             return CURLE_OUT_OF_MEMORY;
187           }
188         }
189         if(!Curl_checkProxyheaders(conn, "Proxy-Connection:"))
190           proxyconn = "Proxy-Connection: Keep-Alive\r\n";
191
192         if(!Curl_checkProxyheaders(conn, "User-Agent:") &&
193            data->set.str[STRING_USERAGENT])
194           useragent = conn->allocptr.uagent;
195
196         result =
197           Curl_add_bufferf(req_buffer,
198                            "CONNECT %s HTTP/%s\r\n"
199                            "%s"  /* Host: */
200                            "%s"  /* Proxy-Authorization */
201                            "%s"  /* User-Agent */
202                            "%s", /* Proxy-Connection */
203                            hostheader,
204                            http,
205                            host,
206                            conn->allocptr.proxyuserpwd?
207                            conn->allocptr.proxyuserpwd:"",
208                            useragent,
209                            proxyconn);
210
211         if(host && *host)
212           free(host);
213         free(hostheader);
214
215         if(!result)
216           result = Curl_add_custom_headers(conn, TRUE, req_buffer);
217
218         if(!result)
219           /* CRLF terminate the request */
220           result = Curl_add_bufferf(req_buffer, "\r\n");
221
222         if(!result) {
223           /* Send the connect request to the proxy */
224           /* BLOCKING */
225           result =
226             Curl_add_buffer_send(req_buffer, conn,
227                                  &data->info.request_size, 0, sockindex);
228         }
229         req_buffer = NULL;
230         if(result)
231           failf(data, "Failed sending CONNECT to proxy");
232       }
233
234       Curl_add_buffer_free(req_buffer);
235       if(result)
236         return result;
237
238       conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
239     } /* END CONNECT PHASE */
240
241     check = Curl_timeleft(data, NULL, TRUE);
242     if(check <= 0) {
243       failf(data, "Proxy CONNECT aborted due to timeout");
244       return CURLE_RECV_ERROR;
245     }
246
247     if(!blocking) {
248       if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
249         /* return so we'll be called again polling-style */
250         return CURLE_OK;
251       else {
252         DEBUGF(infof(data,
253                "Read response immediately from proxy CONNECT\n"));
254       }
255     }
256
257     /* at this point, the tunnel_connecting phase is over. */
258
259     { /* READING RESPONSE PHASE */
260       size_t nread;   /* total size read */
261       int perline; /* count bytes per line */
262       int keepon=TRUE;
263       ssize_t gotbytes;
264       char *ptr;
265       char *line_start;
266
267       ptr=data->state.buffer;
268       line_start = ptr;
269
270       nread=0;
271       perline=0;
272
273       while((nread<BUFSIZE) && (keepon && !error)) {
274
275         check = Curl_timeleft(data, NULL, TRUE);
276         if(check <= 0) {
277           failf(data, "Proxy CONNECT aborted due to timeout");
278           error = SELECT_TIMEOUT; /* already too little time */
279           break;
280         }
281
282         /* loop every second at least, less if the timeout is near */
283         switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
284                                   check<1000L?check:1000)) {
285         case -1: /* select() error, stop reading */
286           error = SELECT_ERROR;
287           failf(data, "Proxy CONNECT aborted due to select/poll error");
288           break;
289         case 0: /* timeout */
290           break;
291         default:
292           DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
293           result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
294                              &gotbytes);
295           if(result==CURLE_AGAIN)
296             continue; /* go loop yourself */
297           else if(result)
298             keepon = FALSE;
299           else if(gotbytes <= 0) {
300             keepon = FALSE;
301             if(data->set.proxyauth && data->state.authproxy.avail) {
302               /* proxy auth was requested and there was proxy auth available,
303                  then deem this as "mere" proxy disconnect */
304               conn->bits.proxy_connect_closed = TRUE;
305               infof(data, "Proxy CONNECT connection closed\n");
306             }
307             else {
308               error = SELECT_ERROR;
309               failf(data, "Proxy CONNECT aborted");
310             }
311           }
312           else {
313             /*
314              * We got a whole chunk of data, which can be anything from one
315              * byte to a set of lines and possibly just a piece of the last
316              * line.
317              */
318             int i;
319
320             nread += gotbytes;
321
322             if(keepon > TRUE) {
323               /* This means we are currently ignoring a response-body */
324
325               nread = 0; /* make next read start over in the read buffer */
326               ptr=data->state.buffer;
327               if(cl) {
328                 /* A Content-Length based body: simply count down the counter
329                    and make sure to break out of the loop when we're done! */
330                 cl -= gotbytes;
331                 if(cl<=0) {
332                   keepon = FALSE;
333                   break;
334                 }
335               }
336               else {
337                 /* chunked-encoded body, so we need to do the chunked dance
338                    properly to know when the end of the body is reached */
339                 CHUNKcode r;
340                 ssize_t tookcareof=0;
341
342                 /* now parse the chunked piece of data so that we can
343                    properly tell when the stream ends */
344                 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
345                 if(r == CHUNKE_STOP) {
346                   /* we're done reading chunks! */
347                   infof(data, "chunk reading DONE\n");
348                   keepon = FALSE;
349                   /* we did the full CONNECT treatment, go COMPLETE */
350                   conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
351                 }
352                 else
353                   infof(data, "Read %zd bytes of chunk, continue\n",
354                         tookcareof);
355               }
356             }
357             else
358               for(i = 0; i < gotbytes; ptr++, i++) {
359                 perline++; /* amount of bytes in this line so far */
360                 if(*ptr == 0x0a) {
361                   char letter;
362                   int writetype;
363
364                   /* convert from the network encoding */
365                   result = Curl_convert_from_network(data, line_start,
366                                                      perline);
367                   /* Curl_convert_from_network calls failf if unsuccessful */
368                   if(result)
369                     return result;
370
371                   /* output debug if that is requested */
372                   if(data->set.verbose)
373                     Curl_debug(data, CURLINFO_HEADER_IN,
374                                line_start, (size_t)perline, conn);
375
376                   /* send the header to the callback */
377                   writetype = CLIENTWRITE_HEADER;
378                   if(data->set.include_header)
379                     writetype |= CLIENTWRITE_BODY;
380
381                   result = Curl_client_write(conn, writetype, line_start,
382                                              perline);
383
384                   data->info.header_size += (long)perline;
385                   data->req.headerbytecount += (long)perline;
386
387                   if(result)
388                     return result;
389
390                   /* Newlines are CRLF, so the CR is ignored as the line isn't
391                      really terminated until the LF comes. Treat a following CR
392                      as end-of-headers as well.*/
393
394                   if(('\r' == line_start[0]) ||
395                      ('\n' == line_start[0])) {
396                     /* end of response-headers from the proxy */
397                     nread = 0; /* make next read start over in the read
398                                   buffer */
399                     ptr=data->state.buffer;
400                     if((407 == k->httpcode) && !data->state.authproblem) {
401                       /* If we get a 407 response code with content length
402                          when we have no auth problem, we must ignore the
403                          whole response-body */
404                       keepon = 2;
405
406                       if(cl) {
407                         infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
408                               " bytes of response-body\n", cl);
409
410                         /* remove the remaining chunk of what we already
411                            read */
412                         cl -= (gotbytes - i);
413
414                         if(cl<=0)
415                           /* if the whole thing was already read, we are done!
416                            */
417                           keepon=FALSE;
418                       }
419                       else if(chunked_encoding) {
420                         CHUNKcode r;
421                         /* We set ignorebody true here since the chunked
422                            decoder function will acknowledge that. Pay
423                            attention so that this is cleared again when this
424                            function returns! */
425                         k->ignorebody = TRUE;
426                         infof(data, "%zd bytes of chunk left\n", gotbytes-i);
427
428                         if(line_start[1] == '\n') {
429                           /* this can only be a LF if the letter at index 0
430                              was a CR */
431                           line_start++;
432                           i++;
433                         }
434
435                         /* now parse the chunked piece of data so that we can
436                            properly tell when the stream ends */
437                         r = Curl_httpchunk_read(conn, line_start+1,
438                                                   gotbytes -i, &gotbytes);
439                         if(r == CHUNKE_STOP) {
440                           /* we're done reading chunks! */
441                           infof(data, "chunk reading DONE\n");
442                           keepon = FALSE;
443                           /* we did the full CONNECT treatment, go to
444                              COMPLETE */
445                           conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
446                         }
447                         else
448                           infof(data, "Read %zd bytes of chunk, continue\n",
449                                 gotbytes);
450                       }
451                       else {
452                         /* without content-length or chunked encoding, we
453                            can't keep the connection alive since the close is
454                            the end signal so we bail out at once instead */
455                         keepon=FALSE;
456                       }
457                     }
458                     else {
459                       keepon = FALSE;
460                       if(200 == data->info.httpproxycode) {
461                         if(gotbytes - (i+1))
462                           failf(data, "Proxy CONNECT followed by %zd bytes "
463                                 "of opaque data. Data ignored (known bug #39)",
464                                 gotbytes - (i+1));
465                       }
466                     }
467                     /* we did the full CONNECT treatment, go to COMPLETE */
468                     conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
469                     break; /* breaks out of for-loop, not switch() */
470                   }
471
472                   /* keep a backup of the position we are about to blank */
473                   letter = line_start[perline];
474                   line_start[perline]=0; /* zero terminate the buffer */
475                   if((checkprefix("WWW-Authenticate:", line_start) &&
476                       (401 == k->httpcode)) ||
477                      (checkprefix("Proxy-authenticate:", line_start) &&
478                       (407 == k->httpcode))) {
479
480                     bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
481                     char *auth = Curl_copy_header_value(line_start);
482                     if(!auth)
483                       return CURLE_OUT_OF_MEMORY;
484
485                     result = Curl_http_input_auth(conn, proxy, auth);
486
487                     free(auth);
488
489                     if(result)
490                       return result;
491                   }
492                   else if(checkprefix("Content-Length:", line_start)) {
493                     cl = curlx_strtoofft(line_start +
494                                          strlen("Content-Length:"), NULL, 10);
495                   }
496                   else if(Curl_compareheader(line_start,
497                                              "Connection:", "close"))
498                     closeConnection = TRUE;
499                   else if(Curl_compareheader(line_start,
500                                              "Transfer-Encoding:",
501                                              "chunked")) {
502                     infof(data, "CONNECT responded chunked\n");
503                     chunked_encoding = TRUE;
504                     /* init our chunky engine */
505                     Curl_httpchunk_init(conn);
506                   }
507                   else if(Curl_compareheader(line_start,
508                                              "Proxy-Connection:", "close"))
509                     closeConnection = TRUE;
510                   else if(2 == sscanf(line_start, "HTTP/1.%d %d",
511                                       &subversion,
512                                       &k->httpcode)) {
513                     /* store the HTTP code from the proxy */
514                     data->info.httpproxycode = k->httpcode;
515                   }
516                   /* put back the letter we blanked out before */
517                   line_start[perline]= letter;
518
519                   perline=0; /* line starts over here */
520                   line_start = ptr+1; /* this skips the zero byte we wrote */
521                 }
522               }
523           }
524           break;
525         } /* switch */
526         if(Curl_pgrsUpdate(conn))
527           return CURLE_ABORTED_BY_CALLBACK;
528       } /* while there's buffer left and loop is requested */
529
530       if(error)
531         return CURLE_RECV_ERROR;
532
533       if(data->info.httpproxycode != 200) {
534         /* Deal with the possibly already received authenticate
535            headers. 'newurl' is set to a new URL if we must loop. */
536         result = Curl_http_auth_act(conn);
537         if(result)
538           return result;
539
540         if(conn->bits.close)
541           /* the connection has been marked for closure, most likely in the
542              Curl_http_auth_act() function and thus we can kill it at once
543              below
544           */
545           closeConnection = TRUE;
546       }
547
548       if(closeConnection && data->req.newurl) {
549         /* Connection closed by server. Don't use it anymore */
550         Curl_closesocket(conn, conn->sock[sockindex]);
551         conn->sock[sockindex] = CURL_SOCKET_BAD;
552         break;
553       }
554     } /* END READING RESPONSE PHASE */
555
556     /* If we are supposed to continue and request a new URL, which basically
557      * means the HTTP authentication is still going on so if the tunnel
558      * is complete we start over in INIT state */
559     if(data->req.newurl &&
560        (TUNNEL_COMPLETE == conn->tunnel_state[sockindex])) {
561       conn->tunnel_state[sockindex] = TUNNEL_INIT;
562       infof(data, "TUNNEL_STATE switched to: %d\n",
563             conn->tunnel_state[sockindex]);
564     }
565
566   } while(data->req.newurl);
567
568   if(200 != data->req.httpcode) {
569     if(closeConnection && data->req.newurl) {
570       conn->bits.proxy_connect_closed = TRUE;
571       infof(data, "Connect me again please\n");
572     }
573     else {
574       free(data->req.newurl);
575       data->req.newurl = NULL;
576       /* failure, close this connection to avoid re-use */
577       streamclose(conn, "proxy CONNECT failure");
578       Curl_closesocket(conn, conn->sock[sockindex]);
579       conn->sock[sockindex] = CURL_SOCKET_BAD;
580     }
581
582     /* to back to init state */
583     conn->tunnel_state[sockindex] = TUNNEL_INIT;
584
585     if(conn->bits.proxy_connect_closed)
586       /* this is not an error, just part of the connection negotiation */
587       return CURLE_OK;
588     else {
589       failf(data, "Received HTTP code %d from proxy after CONNECT",
590             data->req.httpcode);
591       return CURLE_RECV_ERROR;
592     }
593   }
594
595   conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
596
597   /* If a proxy-authorization header was used for the proxy, then we should
598      make sure that it isn't accidentally used for the document request
599      after we've connected. So let's free and clear it here. */
600   Curl_safefree(conn->allocptr.proxyuserpwd);
601   conn->allocptr.proxyuserpwd = NULL;
602
603   data->state.authproxy.done = TRUE;
604
605   infof (data, "Proxy replied OK to CONNECT request\n");
606   data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
607   conn->bits.rewindaftersend = FALSE; /* make sure this isn't set for the
608                                          document request  */
609   return CURLE_OK;
610 }
611 #endif /* CURL_DISABLE_PROXY */