14ef9dc1e3f9fdf0fc833dfac1d45d83588a7e1a
[platform/upstream/curl.git] / lib / curl_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 "curl_urldata.h"
28 #include <curl/curl.h>
29 #include "curl_http_proxy.h"
30 #include "curl_sendf.h"
31 #include "curl_http.h"
32 #include "curl_url.h"
33 #include "curl_select.h"
34 #include "curl_rawstr.h"
35 #include "curl_progress.h"
36 #include "curl_non_ascii.h"
37 #include "curl_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 "curl_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  * This badly needs to be rewritten. CONNECT should be sent and dealt with
92  * like any ordinary HTTP request, and not specially crafted like this. This
93  * function only remains here like this for now since the rewrite is a bit too
94  * much work to do at the moment.
95  *
96  * This function is BLOCKING which is nasty for all multi interface using apps.
97  */
98
99 CURLcode Curl_proxyCONNECT(struct connectdata *conn,
100                            int sockindex,
101                            const char *hostname,
102                            unsigned short remote_port)
103 {
104   int subversion=0;
105   struct SessionHandle *data=conn->data;
106   struct SingleRequest *k = &data->req;
107   CURLcode result;
108   long timeout =
109     data->set.timeout?data->set.timeout:PROXY_TIMEOUT; /* in milliseconds */
110   curl_socket_t tunnelsocket = conn->sock[sockindex];
111   curl_off_t cl=0;
112   bool closeConnection = FALSE;
113   bool chunked_encoding = FALSE;
114   long check;
115
116 #define SELECT_OK      0
117 #define SELECT_ERROR   1
118 #define SELECT_TIMEOUT 2
119   int error = SELECT_OK;
120
121   if(conn->tunnel_state[sockindex] == TUNNEL_COMPLETE)
122     return CURLE_OK; /* CONNECT is already completed */
123
124   conn->bits.proxy_connect_closed = FALSE;
125
126   do {
127     if(TUNNEL_INIT == conn->tunnel_state[sockindex]) {
128       /* BEGIN CONNECT PHASE */
129       char *host_port;
130       Curl_send_buffer *req_buffer;
131
132       infof(data, "Establish HTTP proxy tunnel to %s:%hu\n",
133             hostname, remote_port);
134
135       if(data->req.newurl) {
136         /* This only happens if we've looped here due to authentication
137            reasons, and we don't really use the newly cloned URL here
138            then. Just free() it. */
139         free(data->req.newurl);
140         data->req.newurl = NULL;
141       }
142
143       /* initialize a dynamic send-buffer */
144       req_buffer = Curl_add_buffer_init();
145
146       if(!req_buffer)
147         return CURLE_OUT_OF_MEMORY;
148
149       host_port = aprintf("%s:%hu", hostname, remote_port);
150       if(!host_port) {
151         free(req_buffer);
152         return CURLE_OUT_OF_MEMORY;
153       }
154
155       /* Setup the proxy-authorization header, if any */
156       result = Curl_http_output_auth(conn, "CONNECT", host_port, TRUE);
157
158       free(host_port);
159
160       if(CURLE_OK == result) {
161         char *host=(char *)"";
162         const char *proxyconn="";
163         const char *useragent="";
164         const char *http = (conn->proxytype == CURLPROXY_HTTP_1_0) ?
165           "1.0" : "1.1";
166         char *hostheader= /* host:port with IPv6 support */
167           aprintf("%s%s%s:%hu", conn->bits.ipv6_ip?"[":"",
168                   hostname, conn->bits.ipv6_ip?"]":"",
169                   remote_port);
170         if(!hostheader) {
171           free(req_buffer);
172           return CURLE_OUT_OF_MEMORY;
173         }
174
175         if(!Curl_checkheaders(data, "Host:")) {
176           host = aprintf("Host: %s\r\n", hostheader);
177           if(!host) {
178             free(hostheader);
179             free(req_buffer);
180             return CURLE_OUT_OF_MEMORY;
181           }
182         }
183         if(!Curl_checkheaders(data, "Proxy-Connection:"))
184           proxyconn = "Proxy-Connection: Keep-Alive\r\n";
185
186         if(!Curl_checkheaders(data, "User-Agent:") &&
187            data->set.str[STRING_USERAGENT])
188           useragent = conn->allocptr.uagent;
189
190         result =
191           Curl_add_bufferf(req_buffer,
192                            "CONNECT %s HTTP/%s\r\n"
193                            "%s"  /* Host: */
194                            "%s"  /* Proxy-Authorization */
195                            "%s"  /* User-Agent */
196                            "%s", /* Proxy-Connection */
197                            hostheader,
198                            http,
199                            host,
200                            conn->allocptr.proxyuserpwd?
201                            conn->allocptr.proxyuserpwd:"",
202                            useragent,
203                            proxyconn);
204
205         if(host && *host)
206           free(host);
207         free(hostheader);
208
209         if(CURLE_OK == result)
210           result = Curl_add_custom_headers(conn, req_buffer);
211
212         if(CURLE_OK == result)
213           /* CRLF terminate the request */
214           result = Curl_add_bufferf(req_buffer, "\r\n");
215
216         if(CURLE_OK == result) {
217           /* Send the connect request to the proxy */
218           /* BLOCKING */
219           result =
220             Curl_add_buffer_send(req_buffer, conn,
221                                  &data->info.request_size, 0, sockindex);
222         }
223         req_buffer = NULL;
224         if(result)
225           failf(data, "Failed sending CONNECT to proxy");
226       }
227
228       Curl_safefree(req_buffer);
229       if(result)
230         return result;
231
232       conn->tunnel_state[sockindex] = TUNNEL_CONNECT;
233     } /* END CONNECT PHASE */
234
235     /* now we've issued the CONNECT and we're waiting to hear back -
236        we try not to block here in multi-mode because that might be a LONG
237        wait if the proxy cannot connect-through to the remote host. */
238
239     /* if timeout is requested, find out how much remaining time we have */
240     check = timeout - /* timeout time */
241       Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
242     if(check <= 0) {
243       failf(data, "Proxy CONNECT aborted due to timeout");
244       return CURLE_RECV_ERROR;
245     }
246
247     /* if we're in multi-mode and we would block, return instead for a retry */
248     if(Curl_if_multi == data->state.used_interface) {
249       if(0 == Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD, 0))
250         /* return so we'll be called again polling-style */
251         return CURLE_OK;
252       else {
253         DEBUGF(infof(data,
254                      "Multi mode finished polling for response from "
255                      "proxy CONNECT\n"));
256       }
257     }
258     else {
259       DEBUGF(infof(data, "Easy mode waiting response from proxy CONNECT\n"));
260     }
261
262     /* at this point, either:
263        1) we're in easy-mode and so it's okay to block waiting for a CONNECT
264        response
265        2) we're in multi-mode and we didn't block - it's either an error or we
266        now have some data waiting.
267        In any case, the tunnel_connecting phase is over. */
268
269     { /* BEGIN NEGOTIATION PHASE */
270       size_t nread;   /* total size read */
271       int perline; /* count bytes per line */
272       int keepon=TRUE;
273       ssize_t gotbytes;
274       char *ptr;
275       char *line_start;
276
277       ptr=data->state.buffer;
278       line_start = ptr;
279
280       nread=0;
281       perline=0;
282       keepon=TRUE;
283
284       while((nread<BUFSIZE) && (keepon && !error)) {
285
286         /* if timeout is requested, find out how much remaining time we have */
287         check = timeout - /* timeout time */
288           Curl_tvdiff(Curl_tvnow(), conn->now); /* spent time */
289         if(check <= 0) {
290           failf(data, "Proxy CONNECT aborted due to timeout");
291           error = SELECT_TIMEOUT; /* already too little time */
292           break;
293         }
294
295         /* loop every second at least, less if the timeout is near */
296         switch (Curl_socket_ready(tunnelsocket, CURL_SOCKET_BAD,
297                                   check<1000L?check:1000)) {
298         case -1: /* select() error, stop reading */
299           error = SELECT_ERROR;
300           failf(data, "Proxy CONNECT aborted due to select/poll error");
301           break;
302         case 0: /* timeout */
303           break;
304         default:
305           DEBUGASSERT(ptr+BUFSIZE-nread <= data->state.buffer+BUFSIZE+1);
306           result = Curl_read(conn, tunnelsocket, ptr, BUFSIZE-nread,
307                              &gotbytes);
308           if(result==CURLE_AGAIN)
309             continue; /* go loop yourself */
310           else if(result)
311             keepon = FALSE;
312           else if(gotbytes <= 0) {
313             keepon = FALSE;
314             if(data->set.proxyauth && data->state.authproxy.avail) {
315               /* proxy auth was requested and there was proxy auth available,
316                  then deem this as "mere" proxy disconnect */
317               conn->bits.proxy_connect_closed = TRUE;
318             }
319             else {
320               error = SELECT_ERROR;
321               failf(data, "Proxy CONNECT aborted");
322             }
323           }
324           else {
325             /*
326              * We got a whole chunk of data, which can be anything from one
327              * byte to a set of lines and possibly just a piece of the last
328              * line.
329              */
330             int i;
331
332             nread += gotbytes;
333
334             if(keepon > TRUE) {
335               /* This means we are currently ignoring a response-body */
336
337               nread = 0; /* make next read start over in the read buffer */
338               ptr=data->state.buffer;
339               if(cl) {
340                 /* A Content-Length based body: simply count down the counter
341                    and make sure to break out of the loop when we're done! */
342                 cl -= gotbytes;
343                 if(cl<=0) {
344                   keepon = FALSE;
345                   break;
346                 }
347               }
348               else {
349                 /* chunked-encoded body, so we need to do the chunked dance
350                    properly to know when the end of the body is reached */
351                 CHUNKcode r;
352                 ssize_t tookcareof=0;
353
354                 /* now parse the chunked piece of data so that we can
355                    properly tell when the stream ends */
356                 r = Curl_httpchunk_read(conn, ptr, gotbytes, &tookcareof);
357                 if(r == CHUNKE_STOP) {
358                   /* we're done reading chunks! */
359                   infof(data, "chunk reading DONE\n");
360                   keepon = FALSE;
361                   /* we did the full CONNECT treatment, go COMPLETE */
362                   conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
363                 }
364                 else
365                   infof(data, "Read %zd bytes of chunk, continue\n",
366                         tookcareof);
367               }
368             }
369             else
370               for(i = 0; i < gotbytes; ptr++, i++) {
371                 perline++; /* amount of bytes in this line so far */
372                 if(*ptr == 0x0a) {
373                   char letter;
374                   int writetype;
375
376                   /* convert from the network encoding */
377                   result = Curl_convert_from_network(data, line_start,
378                                                      perline);
379                   /* Curl_convert_from_network calls failf if unsuccessful */
380                   if(result)
381                     return result;
382
383                   /* output debug if that is requested */
384                   if(data->set.verbose)
385                     Curl_debug(data, CURLINFO_HEADER_IN,
386                                line_start, (size_t)perline, conn);
387
388                   /* send the header to the callback */
389                   writetype = CLIENTWRITE_HEADER;
390                   if(data->set.include_header)
391                     writetype |= CLIENTWRITE_BODY;
392
393                   result = Curl_client_write(conn, writetype, line_start,
394                                              perline);
395                   if(result)
396                     return result;
397
398                   /* Newlines are CRLF, so the CR is ignored as the line isn't
399                      really terminated until the LF comes. Treat a following CR
400                      as end-of-headers as well.*/
401
402                   if(('\r' == line_start[0]) ||
403                      ('\n' == line_start[0])) {
404                     /* end of response-headers from the proxy */
405                     nread = 0; /* make next read start over in the read
406                                   buffer */
407                     ptr=data->state.buffer;
408                     if((407 == k->httpcode) && !data->state.authproblem) {
409                       /* If we get a 407 response code with content length
410                          when we have no auth problem, we must ignore the
411                          whole response-body */
412                       keepon = 2;
413
414                       if(cl) {
415
416                         infof(data, "Ignore %" FORMAT_OFF_T
417                               " bytes of response-body\n", cl);
418                         /* remove the remaining chunk of what we already
419                            read */
420                         cl -= (gotbytes - i);
421
422                         if(cl<=0)
423                           /* if the whole thing was already read, we are done!
424                            */
425                           keepon=FALSE;
426                       }
427                       else if(chunked_encoding) {
428                         CHUNKcode r;
429                         /* We set ignorebody true here since the chunked
430                            decoder function will acknowledge that. Pay
431                            attention so that this is cleared again when this
432                            function returns! */
433                         k->ignorebody = TRUE;
434                         infof(data, "%zd bytes of chunk left\n", gotbytes-i);
435
436                         if(line_start[1] == '\n') {
437                           /* this can only be a LF if the letter at index 0
438                              was a CR */
439                           line_start++;
440                           i++;
441                         }
442
443                         /* now parse the chunked piece of data so that we can
444                            properly tell when the stream ends */
445                         r = Curl_httpchunk_read(conn, line_start+1,
446                                                   gotbytes -i, &gotbytes);
447                         if(r == CHUNKE_STOP) {
448                           /* we're done reading chunks! */
449                           infof(data, "chunk reading DONE\n");
450                           keepon = FALSE;
451                           /* we did the full CONNECT treatment, go to
452                              COMPLETE */
453                           conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
454                         }
455                         else
456                           infof(data, "Read %zd bytes of chunk, continue\n",
457                                 gotbytes);
458                       }
459                       else {
460                         /* without content-length or chunked encoding, we
461                            can't keep the connection alive since the close is
462                            the end signal so we bail out at once instead */
463                         keepon=FALSE;
464                       }
465                     }
466                     else {
467                       keepon = FALSE;
468                       if(200 == data->info.httpproxycode) {
469                         if(gotbytes - (i+1))
470                           failf(data, "Proxy CONNECT followed by %zd bytes "
471                                 "of opaque data. Data ignored (known bug #39)",
472                                 gotbytes - (i+1));
473                       }
474                     }
475                     /* we did the full CONNECT treatment, go to COMPLETE */
476                     conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
477                     break; /* breaks out of for-loop, not switch() */
478                   }
479
480                   /* keep a backup of the position we are about to blank */
481                   letter = line_start[perline];
482                   line_start[perline]=0; /* zero terminate the buffer */
483                   if((checkprefix("WWW-Authenticate:", line_start) &&
484                       (401 == k->httpcode)) ||
485                      (checkprefix("Proxy-authenticate:", line_start) &&
486                       (407 == k->httpcode))) {
487                     result = Curl_http_input_auth(conn, k->httpcode,
488                                                   line_start);
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 NEGOTIATION 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     failf(data, "Received HTTP code %d from proxy after CONNECT",
570           data->req.httpcode);
571
572     if(closeConnection && data->req.newurl)
573       conn->bits.proxy_connect_closed = TRUE;
574
575     /* to back to init state */
576     conn->tunnel_state[sockindex] = TUNNEL_INIT;
577
578     return CURLE_RECV_ERROR;
579   }
580
581   conn->tunnel_state[sockindex] = TUNNEL_COMPLETE;
582
583   /* If a proxy-authorization header was used for the proxy, then we should
584      make sure that it isn't accidentally used for the document request
585      after we've connected. So let's free and clear it here. */
586   Curl_safefree(conn->allocptr.proxyuserpwd);
587   conn->allocptr.proxyuserpwd = NULL;
588
589   data->state.authproxy.done = TRUE;
590
591   infof (data, "Proxy replied OK to CONNECT request\n");
592   data->req.ignorebody = FALSE; /* put it (back) to non-ignore state */
593   return CURLE_OK;
594 }
595 #endif /* CURL_DISABLE_PROXY */