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