OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / gnu / java / net / protocol / http / HTTPURLConnection.java
1 /* HTTPURLConnection.java --
2    Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10  
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package gnu.java.net.protocol.http;
40
41 import gnu.classpath.SystemProperties;
42
43 import java.io.ByteArrayOutputStream;
44 import java.io.FileNotFoundException;
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.OutputStream;
48 import java.net.ProtocolException;
49 import java.net.URL;
50 import java.security.cert.Certificate;
51 import java.util.Collections;
52 import java.util.Date;
53 import java.util.Map;
54
55 import javax.net.ssl.HandshakeCompletedEvent;
56 import javax.net.ssl.HandshakeCompletedListener;
57 import javax.net.ssl.HostnameVerifier;
58 import javax.net.ssl.HttpsURLConnection;
59 import javax.net.ssl.SSLPeerUnverifiedException;
60 import javax.net.ssl.SSLSocketFactory;
61
62 /**
63  * A URLConnection that uses the HTTPConnection class.
64  *
65  * @author Chris Burdess (dog@gnu.org)
66  */
67 public class HTTPURLConnection
68  extends HttpsURLConnection
69   implements HandshakeCompletedListener
70 {
71   /*
72    * The underlying connection.
73    */
74   private HTTPConnection connection;
75
76   // These are package private for use in anonymous inner classes.
77   String proxyHostname;
78   int proxyPort = -1;
79   String agent;
80   boolean keepAlive;
81
82   private Request request;
83   private Headers requestHeaders;
84   private ByteArrayOutputStream requestSink;
85   private boolean requestMethodSetExplicitly;
86
87   private Response response;
88   private InputStream responseSink;
89   private InputStream errorSink;
90
91   private HandshakeCompletedEvent handshakeEvent;
92
93   /**
94    * Constructor.
95    * @param url the URL
96    */
97   public HTTPURLConnection(URL url)
98     throws IOException
99   {
100     super(url);
101     requestHeaders = new Headers();
102     String proxy = SystemProperties.getProperty("http.proxyHost");
103     if (proxy != null && proxy.length() > 0)
104       {
105         String port = SystemProperties.getProperty("http.proxyPort");
106         if (port != null && port.length() > 0)
107           {
108             try
109               {
110                 proxyPort = Integer.parseInt(port);
111                 proxyHostname = proxy;
112               }
113             catch (NumberFormatException _)
114               {
115                 // Ignore.
116               }
117           }
118       }
119     agent = SystemProperties.getProperty("http.agent");
120     String ka = SystemProperties.getProperty("http.keepAlive");
121     keepAlive = !(ka != null && "false".equals(ka));
122   }
123
124   public void connect()
125     throws IOException
126   {
127     if (connected)
128       {
129         return;
130       }
131     String protocol = url.getProtocol();
132     boolean secure = "https".equals(protocol);
133     String host = url.getHost();
134     int port = url.getPort();
135     if (port < 0)
136       {
137         port = secure ? HTTPConnection.HTTPS_PORT :
138           HTTPConnection.HTTP_PORT;
139       }
140     String file = url.getFile();
141     String username = url.getUserInfo();
142     String password = null;
143     if (username != null)
144       {
145         int ci = username.indexOf(':');
146         if (ci != -1)
147           {
148             password = username.substring(ci + 1);
149             username = username.substring(0, ci);
150           }
151       }
152     final Credentials creds = (username == null) ? null :
153       new Credentials (username, password);
154     
155     if ("POST".equals(method))
156       {
157         String contentType = requestHeaders.getValue("Content-Type");
158         if (null == contentType)
159           requestHeaders.addValue("Content-Type",
160                                   "application/x-www-form-urlencoded");
161       }
162
163     boolean retry;
164     do
165       {
166         retry = false;
167         if (connection == null)
168           {
169             connection = getConnection(host, port, secure);
170             if (secure)
171               {
172                 SSLSocketFactory factory = getSSLSocketFactory();
173                 HostnameVerifier verifier = getHostnameVerifier();
174                 if (factory != null)
175                   {
176                     connection.setSSLSocketFactory(factory);
177                   }
178                 connection.addHandshakeCompletedListener(this);
179                 // TODO verifier
180               }
181           }
182         if (proxyHostname != null)
183           {
184             if (proxyPort < 0)
185               {
186                 proxyPort = secure ? HTTPConnection.HTTPS_PORT :
187                   HTTPConnection.HTTP_PORT;
188               }
189             connection.setProxy(proxyHostname, proxyPort);
190           }
191         try
192           {
193             request = connection.newRequest(method, file);
194             if (!keepAlive)
195               {
196                 request.setHeader("Connection", "close");
197               }
198             if (agent != null)
199               {
200                 request.setHeader("User-Agent", agent);
201               }
202             request.getHeaders().putAll(requestHeaders);
203             if (requestSink != null)
204               {
205                 byte[] content = requestSink.toByteArray();
206                 RequestBodyWriter writer = new ByteArrayRequestBodyWriter(content);
207                 request.setRequestBodyWriter(writer);
208               }
209             if (creds != null)
210               {
211                 request.setAuthenticator(new Authenticator() {
212                     public Credentials getCredentials(String realm, int attempts)
213                     {
214                       return (attempts < 2) ? creds : null;
215                     }
216                   });
217               }
218             response = request.dispatch();
219           }
220         catch (IOException ioe)
221           {
222             if (connection.useCount > 0)
223               {
224                 // Connection re-use failed: Try a new connection.
225                 try
226                   {
227                     connection.close();
228                   }
229                 catch (IOException _)
230                   {
231                     // Ignore.
232                   }
233                 connection = null;
234                 retry = true;
235                 continue;
236               }
237             else
238               {
239                 // First time the connection was used: Hard failure.
240                 throw ioe;
241               }
242           }
243         
244         if (response.isRedirect() && getInstanceFollowRedirects())
245           {
246             // Read the response body, if there is one.  If the
247             // redirect points us back at the same server, we will use
248             // the cached connection, so we must make sure there is no
249             // pending data in it.
250             InputStream body = response.getBody();
251             if (body != null)
252               {
253                 byte[] ignore = new byte[1024];
254                 while (true)
255                   {
256                     int n = body.read(ignore, 0, ignore.length);
257                     if (n == -1)
258                       break;
259                   }
260               }
261
262             // Follow redirect
263             String location = response.getHeader("Location");
264             if (location != null)
265               {
266                 String connectionUri = connection.getURI();
267                 int start = connectionUri.length();
268                 if (location.startsWith(connectionUri) &&
269                     location.charAt(start) == '/')
270                   {
271                     file = location.substring(start);
272                     retry = true;
273                   }
274                 else if (location.startsWith("http:"))
275                   {
276                     connection.close();
277                     connection = null;
278                     secure = false;
279                     start = 7;
280                     int end = location.indexOf('/', start);
281                     if (end == -1)
282                       end = location.length();
283                     host = location.substring(start, end);
284                     int ci = host.lastIndexOf(':');
285                     if (ci != -1)
286                       {
287                         port = Integer.parseInt(host.substring (ci + 1));
288                         host = host.substring(0, ci);
289                       }
290                     else
291                       {
292                         port = HTTPConnection.HTTP_PORT;
293                       }
294                     file = location.substring(end);
295                     retry = true;
296                   }
297                 else if (location.startsWith("https:"))
298                   {
299                     connection.close();
300                     connection = null;
301                     secure = true;
302                     start = 8;
303                     int end = location.indexOf('/', start);
304                     if (end == -1)
305                       end = location.length();
306                     host = location.substring(start, end);
307                     int ci = host.lastIndexOf(':');
308                     if (ci != -1)
309                       {
310                         port = Integer.parseInt(host.substring (ci + 1));
311                         host = host.substring(0, ci);
312                       }
313                     else
314                       {
315                         port = HTTPConnection.HTTPS_PORT;
316                       }
317                     file = location.substring(end);
318                     retry = true;
319                   }
320                 else if (location.length() > 0)
321                   {
322                     // Malformed absolute URI, treat as file part of URI
323                     if (location.charAt(0) == '/')
324                       {
325                         // Absolute path
326                         file = location;
327                       }
328                     else
329                       {
330                         // Relative path
331                         int lsi = file.lastIndexOf('/');
332                         file = (lsi == -1) ? "/" : file.substring(0, lsi + 1);
333                     file += location;
334                       }
335                     retry = true;
336                   }
337               }
338           }
339         else
340           {
341             responseSink = response.getBody();
342             
343             if (response.isError())
344               errorSink = responseSink;
345           }
346       }
347     while (retry);
348     connected = true;
349   }  
350
351   /**
352    * Returns a connection, from the pool if necessary.
353    */
354   HTTPConnection getConnection(String host, int port, boolean secure)
355     throws IOException
356   {
357     HTTPConnection connection;
358     if (keepAlive)
359       {
360         connection = HTTPConnection.Pool.instance.get(host, port, secure,
361                                                       getConnectTimeout(),
362                                                       getReadTimeout());
363       }
364     else
365       {
366         connection = new HTTPConnection(host, port, secure,
367                                         getConnectTimeout(), getReadTimeout());
368       }
369     return connection;
370   }
371
372   public void disconnect()
373   {
374     if (connection != null)
375       {
376         try
377           {
378             connection.close();
379           }
380         catch (IOException e)
381           {
382           }
383       }
384   }
385
386   public boolean usingProxy()
387   {
388     return (proxyHostname != null);
389   }
390
391   /**
392    * Overrides the corresponding method in HttpURLConnection to permit
393    * arbitrary methods, as long as they're valid ASCII alphabetic
394    * characters. This is to permit WebDAV and other HTTP extensions to
395    * function.
396    * @param method the method
397    */
398   public void setRequestMethod(String method)
399     throws ProtocolException
400   {
401     if (connected)
402       {
403         throw new ProtocolException("Already connected");
404       }
405     // Validate
406     method = method.toUpperCase();
407     int len = method.length();
408     if (len == 0)
409       {
410         throw new ProtocolException("Empty method name");
411       }
412     for (int i = 0; i < len; i++)
413       {
414         char c = method.charAt(i);
415         if (c < 0x41 || c > 0x5a)
416           {
417             throw new ProtocolException("Illegal character '" + c +
418                                         "' at index " + i);
419           }
420       }
421     // OK
422     this.method = method;
423     requestMethodSetExplicitly = true;
424   }
425
426   public String getRequestProperty(String key)
427   {    
428     return requestHeaders.getValue(key);
429   }
430
431   public Map getRequestProperties()
432   {
433     if (connected)
434       throw new IllegalStateException("Already connected");
435     
436     Map m = requestHeaders.getAsMap();
437     return Collections.unmodifiableMap(m);
438   }
439
440   public void setRequestProperty(String key, String value)
441   {
442     super.setRequestProperty(key, value);
443     
444     requestHeaders.put(key, value);
445   }
446
447   public void addRequestProperty(String key, String value)
448   {
449     super.addRequestProperty(key, value);
450     requestHeaders.addValue(key, value);
451   }
452
453   public OutputStream getOutputStream()
454     throws IOException
455   {
456     if (connected)
457       {
458         throw new ProtocolException("Already connected");
459       }
460     if (!doOutput)
461       {
462         throw new ProtocolException("doOutput is false");
463       }
464     else if (!requestMethodSetExplicitly)
465       {
466         /*
467          * Silently change the method to POST if no method was set
468          * explicitly. This is due to broken applications depending on this
469          * behaviour (Apache XMLRPC for one).
470          */
471         method = "POST";
472       }
473     if (requestSink == null)
474       {
475         requestSink = new ByteArrayOutputStream();
476       }
477     return requestSink;
478   }
479   
480   // -- Response --
481   
482   public InputStream getInputStream()
483     throws IOException
484   {
485     if (!connected)
486       {
487         connect();
488       }
489     if (!doInput)
490       {
491         throw new ProtocolException("doInput is false");
492       }
493     
494     if (response.isError())
495       {
496         int code = response.getCode();
497         if (code == 404 || code == 410)
498           throw new FileNotFoundException(url.toString());
499       
500         throw new IOException("Server returned HTTP response code " + code
501                               + " for URL " + url.toString());
502       }
503     
504     return responseSink;
505   }
506
507   public InputStream getErrorStream()
508   {
509     return errorSink;
510   }
511
512   public Map getHeaderFields()
513   {
514     if (!connected)
515       {
516         try
517           {
518             connect();
519           }
520         catch (IOException e)
521           {
522             return null;
523           }
524       }
525     Map m = response.getHeaders().getAsMap();
526     m.put(null, Collections.singletonList(getStatusLine(response)));
527     return Collections.unmodifiableMap(m);
528   }
529
530   String getStatusLine(Response response)
531   {
532     return "HTTP/" + response.getMajorVersion() +
533       "." + response.getMinorVersion() +
534       " " + response.getCode() +
535       " " + response.getMessage();
536   }
537   
538   public String getHeaderField(int index)
539   {
540     if (!connected)
541       {
542         try
543           {
544             connect();
545           }
546         catch (IOException e)
547           {
548             return null;
549           }
550       }
551     if (index == 0)
552       {
553         return getStatusLine(response);
554       }
555     return response.getHeaders().getHeaderValue(index - 1);
556   }
557
558   public String getHeaderFieldKey(int index)
559   {
560     if (!connected)
561       {
562         try
563           {
564             connect();
565           }
566         catch (IOException e)
567           {
568             return null;
569           }
570       }
571     // index of zero is the status line.
572     return response.getHeaders().getHeaderName(index - 1);
573   }
574
575   public String getHeaderField(String name)
576   {
577     if (!connected)
578       {
579         try
580           {
581             connect();
582           }
583         catch (IOException e)
584           {
585             return null;
586           }
587       }
588     return response.getHeader(name);
589   }
590
591   public long getHeaderFieldDate(String name, long def)
592   {
593     if (!connected)
594       {
595         try
596           {
597             connect();
598           }
599         catch (IOException e)
600           {
601             return def;
602           }
603       }
604     Date date = response.getDateHeader(name);
605     return (date == null) ? def : date.getTime();
606   }
607
608   public String getContentType()
609   {
610     return getHeaderField("Content-Type");
611   }
612
613   public int getResponseCode()
614     throws IOException
615   {
616     if (!connected)
617       {
618         connect();
619       }
620     return response.getCode();
621   }
622
623   public String getResponseMessage()
624     throws IOException
625   {
626     if (!connected)
627       {
628         connect();
629       }
630     return response.getMessage();
631   }
632
633   // -- HTTPS specific --
634
635   public String getCipherSuite()
636   {
637     if (!connected)
638       {
639         throw new IllegalStateException("not connected");
640       }
641     return handshakeEvent.getCipherSuite();
642   }
643   
644   public Certificate[] getLocalCertificates()
645   {
646     if (!connected)
647       {
648         throw new IllegalStateException("not connected");
649       }
650     return handshakeEvent.getLocalCertificates();
651   }
652
653   public Certificate[] getServerCertificates()
654     throws SSLPeerUnverifiedException
655   {
656     if (!connected)
657       {
658         throw new IllegalStateException("not connected");
659       }
660     return handshakeEvent.getPeerCertificates();
661   }
662
663   // HandshakeCompletedListener
664
665   public void handshakeCompleted(HandshakeCompletedEvent event)
666   {
667     handshakeEvent = event;
668   }
669
670   /**
671    * Set the read timeout, in milliseconds, or zero if the timeout
672    * is to be considered infinite.
673    *
674    * Overloaded.
675    *
676    */
677   public void setReadTimeout(int timeout)
678     throws IllegalArgumentException
679   {
680     super.setReadTimeout(timeout);
681     if (connection == null)
682       return;
683     try 
684       {
685         connection.getSocket().setSoTimeout(timeout);
686       } 
687     catch (IOException se)
688       {
689         // Ignore socket exceptions.
690       }
691   }
692 }
693