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