OSDN Git Service

Normalise whitespace in GNU Classpath.
[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.List;
54 import java.util.Map;
55
56 import javax.net.ssl.HandshakeCompletedEvent;
57 import javax.net.ssl.HandshakeCompletedListener;
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                 // FIXME: use the verifier
174                 // HostnameVerifier verifier = getHostnameVerifier();
175                 if (factory != null)
176                   {
177                     connection.setSSLSocketFactory(factory);
178                   }
179                 connection.addHandshakeCompletedListener(this);
180                 // TODO verifier
181               }
182           }
183         if (proxyHostname != null)
184           {
185             if (proxyPort < 0)
186               {
187                 proxyPort = secure ? HTTPConnection.HTTPS_PORT :
188                   HTTPConnection.HTTP_PORT;
189               }
190             connection.setProxy(proxyHostname, proxyPort);
191           }
192         try
193           {
194             request = connection.newRequest(method, file);
195             if (!keepAlive)
196               {
197                 request.setHeader("Connection", "close");
198               }
199             if (agent != null)
200               {
201                 request.setHeader("User-Agent", agent);
202               }
203             request.getHeaders().putAll(requestHeaders);
204             if (requestSink != null)
205               {
206                 byte[] content = requestSink.toByteArray();
207                 RequestBodyWriter writer = new ByteArrayRequestBodyWriter(content);
208                 request.setRequestBodyWriter(writer);
209               }
210             if (creds != null)
211               {
212                 request.setAuthenticator(new Authenticator() {
213                     public Credentials getCredentials(String realm, int attempts)
214                     {
215                       return (attempts < 2) ? creds : null;
216                     }
217                   });
218               }
219             response = request.dispatch();
220           }
221         catch (IOException ioe)
222           {
223             if (connection.useCount > 0)
224               {
225                 // Connection re-use failed: Try a new connection.
226                 try
227                   {
228                     connection.close();
229                   }
230                 catch (IOException _)
231                   {
232                     // Ignore.
233                   }
234                 connection = null;
235                 retry = true;
236                 continue;
237               }
238             else
239               {
240                 // First time the connection was used: Hard failure.
241                 throw ioe;
242               }
243           }
244
245         if (response.isRedirect() && getInstanceFollowRedirects())
246           {
247             // Read the response body, if there is one.  If the
248             // redirect points us back at the same server, we will use
249             // the cached connection, so we must make sure there is no
250             // pending data in it.
251             InputStream body = response.getBody();
252             if (body != null)
253               {
254                 byte[] ignore = new byte[1024];
255                 while (true)
256                   {
257                     int n = body.read(ignore, 0, ignore.length);
258                     if (n == -1)
259                       break;
260                   }
261               }
262
263             // Follow redirect
264             String location = response.getHeader("Location");
265             if (location != null)
266               {
267                 String connectionUri = connection.getURI();
268                 int start = connectionUri.length();
269                 if (location.startsWith(connectionUri) &&
270                     location.charAt(start) == '/')
271                   {
272                     file = location.substring(start);
273                     retry = true;
274                   }
275                 else if (location.startsWith("http:"))
276                   {
277                     connection.close();
278                     connection = null;
279                     secure = false;
280                     start = 7;
281                     int end = location.indexOf('/', start);
282                     if (end == -1)
283                       end = location.length();
284                     host = location.substring(start, end);
285                     int ci = host.lastIndexOf(':');
286                     if (ci != -1)
287                       {
288                         port = Integer.parseInt(host.substring (ci + 1));
289                         host = host.substring(0, ci);
290                       }
291                     else
292                       {
293                         port = HTTPConnection.HTTP_PORT;
294                       }
295                     file = location.substring(end);
296                     retry = true;
297                   }
298                 else if (location.startsWith("https:"))
299                   {
300                     connection.close();
301                     connection = null;
302                     secure = true;
303                     start = 8;
304                     int end = location.indexOf('/', start);
305                     if (end == -1)
306                       end = location.length();
307                     host = location.substring(start, end);
308                     int ci = host.lastIndexOf(':');
309                     if (ci != -1)
310                       {
311                         port = Integer.parseInt(host.substring (ci + 1));
312                         host = host.substring(0, ci);
313                       }
314                     else
315                       {
316                         port = HTTPConnection.HTTPS_PORT;
317                       }
318                     file = location.substring(end);
319                     retry = true;
320                   }
321                 else if (location.length() > 0)
322                   {
323                     // Malformed absolute URI, treat as file part of URI
324                     if (location.charAt(0) == '/')
325                       {
326                         // Absolute path
327                         file = location;
328                       }
329                     else
330                       {
331                         // Relative path
332                         int lsi = file.lastIndexOf('/');
333                         file = (lsi == -1) ? "/" : file.substring(0, lsi + 1);
334                     file += location;
335                       }
336                     retry = true;
337                   }
338               }
339           }
340         else
341           {
342             responseSink = response.getBody();
343
344             if (response.isError())
345               errorSink = responseSink;
346           }
347       }
348     while (retry);
349     connected = true;
350   }
351
352   /**
353    * Returns a connection, from the pool if necessary.
354    */
355   HTTPConnection getConnection(String host, int port, boolean secure)
356     throws IOException
357   {
358     HTTPConnection connection;
359     if (keepAlive)
360       {
361         connection = HTTPConnection.Pool.instance.get(host, port, secure,
362                                                       getConnectTimeout(),
363                                                       getReadTimeout());
364       }
365     else
366       {
367         connection = new HTTPConnection(host, port, secure,
368                                         getConnectTimeout(), getReadTimeout());
369       }
370     return connection;
371   }
372
373   public void disconnect()
374   {
375     if (connection != null)
376       {
377         try
378           {
379             connection.close();
380           }
381         catch (IOException e)
382           {
383           }
384       }
385   }
386
387   public boolean usingProxy()
388   {
389     return (proxyHostname != null);
390   }
391
392   /**
393    * Overrides the corresponding method in HttpURLConnection to permit
394    * arbitrary methods, as long as they're valid ASCII alphabetic
395    * characters. This is to permit WebDAV and other HTTP extensions to
396    * function.
397    * @param method the method
398    */
399   public void setRequestMethod(String method)
400     throws ProtocolException
401   {
402     if (connected)
403       {
404         throw new ProtocolException("Already connected");
405       }
406     // Validate
407     method = method.toUpperCase();
408     int len = method.length();
409     if (len == 0)
410       {
411         throw new ProtocolException("Empty method name");
412       }
413     for (int i = 0; i < len; i++)
414       {
415         char c = method.charAt(i);
416         if (c < 0x41 || c > 0x5a)
417           {
418             throw new ProtocolException("Illegal character '" + c +
419                                         "' at index " + i);
420           }
421       }
422     // OK
423     this.method = method;
424     requestMethodSetExplicitly = true;
425   }
426
427   public String getRequestProperty(String key)
428   {
429     return requestHeaders.getValue(key);
430   }
431
432   public Map<String, List<String>> getRequestProperties()
433   {
434     if (connected)
435       throw new IllegalStateException("Already connected");
436
437     Map<String, List<String>> m = requestHeaders.getAsMap();
438     return Collections.unmodifiableMap(m);
439   }
440
441   public void setRequestProperty(String key, String value)
442   {
443     super.setRequestProperty(key, value);
444
445     requestHeaders.put(key, value);
446   }
447
448   public void addRequestProperty(String key, String value)
449   {
450     super.addRequestProperty(key, value);
451     requestHeaders.addValue(key, value);
452   }
453
454   public OutputStream getOutputStream()
455     throws IOException
456   {
457     if (connected)
458       {
459         throw new ProtocolException("Already connected");
460       }
461     if (!doOutput)
462       {
463         throw new ProtocolException("doOutput is false");
464       }
465     else if (!requestMethodSetExplicitly)
466       {
467         /*
468          * Silently change the method to POST if no method was set
469          * explicitly. This is due to broken applications depending on this
470          * behaviour (Apache XMLRPC for one).
471          */
472         method = "POST";
473       }
474     if (requestSink == null)
475       {
476         requestSink = new ByteArrayOutputStream();
477       }
478     return requestSink;
479   }
480
481   // -- Response --
482
483   public InputStream getInputStream()
484     throws IOException
485   {
486     if (!connected)
487       {
488         connect();
489       }
490     if (!doInput)
491       {
492         throw new ProtocolException("doInput is false");
493       }
494
495     if (response.isError())
496       {
497         int code = response.getCode();
498         if (code == 404 || code == 410)
499           throw new FileNotFoundException(url.toString());
500
501         throw new IOException("Server returned HTTP response code " + code
502                               + " for URL " + url.toString());
503       }
504
505     return responseSink;
506   }
507
508   public InputStream getErrorStream()
509   {
510     return errorSink;
511   }
512
513   public Map<String,List<String>> getHeaderFields()
514   {
515     if (!connected)
516       {
517         try
518           {
519             connect();
520           }
521         catch (IOException e)
522           {
523             return null;
524           }
525       }
526     Map<String,List<String>> m = response.getHeaders().getAsMap();
527     m.put(null, Collections.singletonList(getStatusLine(response)));
528     return Collections.unmodifiableMap(m);
529   }
530
531   String getStatusLine(Response response)
532   {
533     return "HTTP/" + response.getMajorVersion() +
534       "." + response.getMinorVersion() +
535       " " + response.getCode() +
536       " " + response.getMessage();
537   }
538
539   public String getHeaderField(int index)
540   {
541     if (!connected)
542       {
543         try
544           {
545             connect();
546           }
547         catch (IOException e)
548           {
549             return null;
550           }
551       }
552     if (index == 0)
553       {
554         return getStatusLine(response);
555       }
556     return response.getHeaders().getHeaderValue(index - 1);
557   }
558
559   public String getHeaderFieldKey(int index)
560   {
561     if (!connected)
562       {
563         try
564           {
565             connect();
566           }
567         catch (IOException e)
568           {
569             return null;
570           }
571       }
572     // index of zero is the status line.
573     return response.getHeaders().getHeaderName(index - 1);
574   }
575
576   public String getHeaderField(String name)
577   {
578     if (!connected)
579       {
580         try
581           {
582             connect();
583           }
584         catch (IOException e)
585           {
586             return null;
587           }
588       }
589     return response.getHeader(name);
590   }
591
592   public long getHeaderFieldDate(String name, long def)
593   {
594     if (!connected)
595       {
596         try
597           {
598             connect();
599           }
600         catch (IOException e)
601           {
602             return def;
603           }
604       }
605     Date date = response.getDateHeader(name);
606     return (date == null) ? def : date.getTime();
607   }
608
609   public String getContentType()
610   {
611     return getHeaderField("Content-Type");
612   }
613
614   public int getResponseCode()
615     throws IOException
616   {
617     if (!connected)
618       {
619         connect();
620       }
621     return response.getCode();
622   }
623
624   public String getResponseMessage()
625     throws IOException
626   {
627     if (!connected)
628       {
629         connect();
630       }
631     return response.getMessage();
632   }
633
634   // -- HTTPS specific --
635
636   public String getCipherSuite()
637   {
638     if (!connected)
639       {
640         throw new IllegalStateException("not connected");
641       }
642     return handshakeEvent.getCipherSuite();
643   }
644
645   public Certificate[] getLocalCertificates()
646   {
647     if (!connected)
648       {
649         throw new IllegalStateException("not connected");
650       }
651     return handshakeEvent.getLocalCertificates();
652   }
653
654   public Certificate[] getServerCertificates()
655     throws SSLPeerUnverifiedException
656   {
657     if (!connected)
658       {
659         throw new IllegalStateException("not connected");
660       }
661     return handshakeEvent.getPeerCertificates();
662   }
663
664   // HandshakeCompletedListener
665
666   public void handshakeCompleted(HandshakeCompletedEvent event)
667   {
668     handshakeEvent = event;
669   }
670
671   /**
672    * Set the read timeout, in milliseconds, or zero if the timeout
673    * is to be considered infinite.
674    *
675    * Overloaded.
676    *
677    */
678   public void setReadTimeout(int timeout)
679     throws IllegalArgumentException
680   {
681     super.setReadTimeout(timeout);
682     if (connection == null)
683       return;
684     try
685       {
686         connection.getSocket().setSoTimeout(timeout);
687       }
688     catch (IOException se)
689       {
690         // Ignore socket exceptions.
691       }
692   }
693 }