0001: /*
0002: * Copyright 1995-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package sun.net.www.protocol.http;
0027:
0028: import java.net.URL;
0029: import java.net.URLConnection;
0030: import java.net.ProtocolException;
0031: import java.net.HttpRetryException;
0032: import java.net.PasswordAuthentication;
0033: import java.net.Authenticator;
0034: import java.net.InetAddress;
0035: import java.net.UnknownHostException;
0036: import java.net.SocketTimeoutException;
0037: import java.net.Proxy;
0038: import java.net.ProxySelector;
0039: import java.net.URI;
0040: import java.net.InetSocketAddress;
0041: import java.net.CookieHandler;
0042: import java.net.ResponseCache;
0043: import java.net.CacheResponse;
0044: import java.net.SecureCacheResponse;
0045: import java.net.CacheRequest;
0046: import java.net.Authenticator.RequestorType;
0047: import java.io.*;
0048: import java.util.Date;
0049: import java.util.Map;
0050: import java.util.List;
0051: import java.util.Locale;
0052: import java.util.StringTokenizer;
0053: import java.util.Iterator;
0054: import java.util.Set;
0055: import java.util.logging.Level;
0056: import java.util.logging.Logger;
0057: import sun.net.*;
0058: import sun.net.www.*;
0059: import sun.net.www.http.HttpClient;
0060: import sun.net.www.http.PosterOutputStream;
0061: import sun.net.www.http.ChunkedInputStream;
0062: import sun.net.www.http.ChunkedOutputStream;
0063: import java.text.SimpleDateFormat;
0064: import java.util.TimeZone;
0065: import java.net.MalformedURLException;
0066: import java.nio.ByteBuffer;
0067: import java.nio.channels.ReadableByteChannel;
0068: import java.nio.channels.WritableByteChannel;
0069: import java.nio.channels.Selector;
0070: import java.nio.channels.SelectionKey;
0071: import java.nio.channels.SelectableChannel;
0072: import java.lang.reflect.*;
0073:
0074: /**
0075: * A class to represent an HTTP connection to a remote object.
0076: */
0077:
0078: public class HttpURLConnection extends java.net.HttpURLConnection {
0079:
0080: private static Logger logger = Logger
0081: .getLogger("sun.net.www.protocol.http.HttpURLConnection");
0082:
0083: static final String version;
0084: public static final String userAgent;
0085:
0086: /* max # of allowed re-directs */
0087: static final int defaultmaxRedirects = 20;
0088: static final int maxRedirects;
0089:
0090: /* Not all servers support the (Proxy)-Authentication-Info headers.
0091: * By default, we don't require them to be sent
0092: */
0093: static final boolean validateProxy;
0094: static final boolean validateServer;
0095:
0096: private StreamingOutputStream strOutputStream;
0097: private final static String RETRY_MSG1 = "cannot retry due to proxy authentication, in streaming mode";
0098: private final static String RETRY_MSG2 = "cannot retry due to server authentication, in streaming mode";
0099: private final static String RETRY_MSG3 = "cannot retry due to redirection, in streaming mode";
0100:
0101: /*
0102: * System properties related to error stream handling:
0103: *
0104: * sun.net.http.errorstream.enableBuffering = <boolean>
0105: *
0106: * With the above system property set to true (default is false),
0107: * when the response code is >=400, the HTTP handler will try to
0108: * buffer the response body (up to a certain amount and within a
0109: * time limit). Thus freeing up the underlying socket connection
0110: * for reuse. The rationale behind this is that usually when the
0111: * server responds with a >=400 error (client error or server
0112: * error, such as 404 file not found), the server will send a
0113: * small response body to explain who to contact and what to do to
0114: * recover. With this property set to true, even if the
0115: * application doesn't call getErrorStream(), read the response
0116: * body, and then call close(), the underlying socket connection
0117: * can still be kept-alive and reused. The following two system
0118: * properties provide further control to the error stream
0119: * buffering behaviour.
0120: *
0121: * sun.net.http.errorstream.timeout = <int>
0122: * the timeout (in millisec) waiting the error stream
0123: * to be buffered; default is 300 ms
0124: *
0125: * sun.net.http.errorstream.bufferSize = <int>
0126: * the size (in bytes) to use for the buffering the error stream;
0127: * default is 4k
0128: */
0129:
0130: /* Should we enable buffering of error streams? */
0131: private static boolean enableESBuffer = false;
0132:
0133: /* timeout waiting for read for buffered error stream;
0134: */
0135: private static int timeout4ESBuffer = 0;
0136:
0137: /* buffer size for buffered error stream;
0138: */
0139: private static int bufSize4ES = 0;
0140:
0141: static {
0142: maxRedirects = ((Integer) java.security.AccessController
0143: .doPrivileged(new sun.security.action.GetIntegerAction(
0144: "http.maxRedirects", defaultmaxRedirects)))
0145: .intValue();
0146: version = (String) java.security.AccessController
0147: .doPrivileged(new sun.security.action.GetPropertyAction(
0148: "java.version"));
0149: String agent = (String) java.security.AccessController
0150: .doPrivileged(new sun.security.action.GetPropertyAction(
0151: "http.agent"));
0152: if (agent == null) {
0153: agent = "Java/" + version;
0154: } else {
0155: agent = agent + " Java/" + version;
0156: }
0157: userAgent = agent;
0158: validateProxy = ((Boolean) java.security.AccessController
0159: .doPrivileged(new sun.security.action.GetBooleanAction(
0160: "http.auth.digest.validateProxy")))
0161: .booleanValue();
0162: validateServer = ((Boolean) java.security.AccessController
0163: .doPrivileged(new sun.security.action.GetBooleanAction(
0164: "http.auth.digest.validateServer")))
0165: .booleanValue();
0166:
0167: enableESBuffer = ((Boolean) java.security.AccessController
0168: .doPrivileged(new sun.security.action.GetBooleanAction(
0169: "sun.net.http.errorstream.enableBuffering")))
0170: .booleanValue();
0171: timeout4ESBuffer = ((Integer) java.security.AccessController
0172: .doPrivileged(new sun.security.action.GetIntegerAction(
0173: "sun.net.http.errorstream.timeout", 300)))
0174: .intValue();
0175: if (timeout4ESBuffer <= 0) {
0176: timeout4ESBuffer = 300; // use the default
0177: }
0178:
0179: bufSize4ES = ((Integer) java.security.AccessController
0180: .doPrivileged(new sun.security.action.GetIntegerAction(
0181: "sun.net.http.errorstream.bufferSize", 4096)))
0182: .intValue();
0183: if (bufSize4ES <= 0) {
0184: bufSize4ES = 4096; // use the default
0185: }
0186:
0187: }
0188:
0189: static final String httpVersion = "HTTP/1.1";
0190: static final String acceptString = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
0191:
0192: // the following http request headers should NOT have their values
0193: // returned for security reasons.
0194: private static final String[] EXCLUDE_HEADERS = {
0195: "Proxy-Authorization", "Authorization" };
0196: protected HttpClient http;
0197: protected Handler handler;
0198: protected Proxy instProxy;
0199:
0200: private CookieHandler cookieHandler;
0201: private ResponseCache cacheHandler;
0202:
0203: // the cached response, and cached response headers and body
0204: protected CacheResponse cachedResponse;
0205: private MessageHeader cachedHeaders;
0206: private InputStream cachedInputStream;
0207:
0208: /* output stream to server */
0209: protected PrintStream ps = null;
0210:
0211: /* buffered error stream */
0212: private InputStream errorStream = null;
0213:
0214: /* User set Cookies */
0215: private boolean setUserCookies = true;
0216: private String userCookies = null;
0217:
0218: /* We only have a single static authenticator for now.
0219: * REMIND: backwards compatibility with JDK 1.1. Should be
0220: * eliminated for JDK 2.0.
0221: */
0222: private static HttpAuthenticator defaultAuth;
0223:
0224: /* all the headers we send
0225: * NOTE: do *NOT* dump out the content of 'requests' in the
0226: * output or stacktrace since it may contain security-sensitive
0227: * headers such as those defined in EXCLUDE_HEADERS.
0228: */
0229: private MessageHeader requests;
0230:
0231: /* The following two fields are only used with Digest Authentication */
0232: String domain; /* The list of authentication domains */
0233: DigestAuthentication.Parameters digestparams;
0234:
0235: /* Current credentials in use */
0236: AuthenticationInfo currentProxyCredentials = null;
0237: AuthenticationInfo currentServerCredentials = null;
0238: boolean needToCheck = true;
0239: private boolean doingNTLM2ndStage = false; /* doing the 2nd stage of an NTLM server authentication */
0240: private boolean doingNTLMp2ndStage = false; /* doing the 2nd stage of an NTLM proxy authentication */
0241: /* try auth without calling Authenticator */
0242: private boolean tryTransparentNTLMServer = NTLMAuthentication
0243: .supportsTransparentAuth();
0244: private boolean tryTransparentNTLMProxy = NTLMAuthentication
0245: .supportsTransparentAuth();
0246: Object authObj;
0247:
0248: /* Set if the user is manually setting the Authorization or Proxy-Authorization headers */
0249: boolean isUserServerAuth;
0250: boolean isUserProxyAuth;
0251:
0252: /* Progress source */
0253: protected ProgressSource pi;
0254:
0255: /* all the response headers we get back */
0256: private MessageHeader responses;
0257: /* the stream _from_ the server */
0258: private InputStream inputStream = null;
0259: /* post stream _to_ the server, if any */
0260: private PosterOutputStream poster = null;
0261:
0262: /* Indicates if the std. request headers have been set in requests. */
0263: private boolean setRequests = false;
0264:
0265: /* Indicates whether a request has already failed or not */
0266: private boolean failedOnce = false;
0267:
0268: /* Remembered Exception, we will throw it again if somebody
0269: calls getInputStream after disconnect */
0270: private Exception rememberedException = null;
0271:
0272: /* If we decide we want to reuse a client, we put it here */
0273: private HttpClient reuseClient = null;
0274:
0275: /* Redefine timeouts from java.net.URLConnection as we nee -1 to mean
0276: * not set. This is to ensure backward compatibility.
0277: */
0278: private int connectTimeout = -1;
0279: private int readTimeout = -1;
0280:
0281: /*
0282: * privileged request password authentication
0283: *
0284: */
0285: private static PasswordAuthentication privilegedRequestPasswordAuthentication(
0286: final String host, final InetAddress addr, final int port,
0287: final String protocol, final String prompt,
0288: final String scheme, final URL url,
0289: final RequestorType authType) {
0290: return (PasswordAuthentication) java.security.AccessController
0291: .doPrivileged(new java.security.PrivilegedAction() {
0292: public Object run() {
0293: return Authenticator
0294: .requestPasswordAuthentication(host,
0295: addr, port, protocol, prompt,
0296: scheme, url, authType);
0297: }
0298: });
0299: }
0300:
0301: /*
0302: * checks the validity of http message header and throws
0303: * IllegalArgumentException if invalid.
0304: */
0305: private void checkMessageHeader(String key, String value) {
0306: char LF = '\n';
0307: int index = key.indexOf(LF);
0308: if (index != -1) {
0309: throw new IllegalArgumentException(
0310: "Illegal character(s) in message header field: "
0311: + key);
0312: } else {
0313: if (value == null) {
0314: return;
0315: }
0316:
0317: index = value.indexOf(LF);
0318: while (index != -1) {
0319: index++;
0320: if (index < value.length()) {
0321: char c = value.charAt(index);
0322: if ((c == ' ') || (c == '\t')) {
0323: // ok, check the next occurrence
0324: index = value.indexOf(LF, index);
0325: continue;
0326: }
0327: }
0328: throw new IllegalArgumentException(
0329: "Illegal character(s) in message header value: "
0330: + value);
0331: }
0332: }
0333: }
0334:
0335: /* adds the standard key/val pairs to reqests if necessary & write to
0336: * given PrintStream
0337: */
0338: private void writeRequests() throws IOException {
0339: /* print all message headers in the MessageHeader
0340: * onto the wire - all the ones we've set and any
0341: * others that have been set
0342: */
0343: // send any pre-emptive authentication
0344: if (http.usingProxy) {
0345: setPreemptiveProxyAuthentication(requests);
0346: }
0347: if (!setRequests) {
0348:
0349: /* We're very particular about the order in which we
0350: * set the request headers here. The order should not
0351: * matter, but some careless CGI programs have been
0352: * written to expect a very particular order of the
0353: * standard headers. To name names, the order in which
0354: * Navigator3.0 sends them. In particular, we make *sure*
0355: * to send Content-type: <> and Content-length:<> second
0356: * to last and last, respectively, in the case of a POST
0357: * request.
0358: */
0359: if (!failedOnce)
0360: requests.prepend(method + " " + http.getURLFile() + " "
0361: + httpVersion, null);
0362: if (!getUseCaches()) {
0363: requests.setIfNotSet("Cache-Control", "no-cache");
0364: requests.setIfNotSet("Pragma", "no-cache");
0365: }
0366: requests.setIfNotSet("User-Agent", userAgent);
0367: int port = url.getPort();
0368: String host = url.getHost();
0369: if (port != -1 && port != url.getDefaultPort()) {
0370: host += ":" + String.valueOf(port);
0371: }
0372: requests.setIfNotSet("Host", host);
0373: requests.setIfNotSet("Accept", acceptString);
0374:
0375: /*
0376: * For HTTP/1.1 the default behavior is to keep connections alive.
0377: * However, we may be talking to a 1.0 server so we should set
0378: * keep-alive just in case, except if we have encountered an error
0379: * or if keep alive is disabled via a system property
0380: */
0381:
0382: // Try keep-alive only on first attempt
0383: if (!failedOnce && http.getHttpKeepAliveSet()) {
0384: if (http.usingProxy) {
0385: requests.setIfNotSet("Proxy-Connection",
0386: "keep-alive");
0387: } else {
0388: requests.setIfNotSet("Connection", "keep-alive");
0389: }
0390: } else {
0391: /*
0392: * RFC 2616 HTTP/1.1 section 14.10 says:
0393: * HTTP/1.1 applications that do not support persistent
0394: * connections MUST include the "close" connection option
0395: * in every message
0396: */
0397: requests.setIfNotSet("Connection", "close");
0398: }
0399: // Set modified since if necessary
0400: long modTime = getIfModifiedSince();
0401: if (modTime != 0) {
0402: Date date = new Date(modTime);
0403: //use the preferred date format according to RFC 2068(HTTP1.1),
0404: // RFC 822 and RFC 1123
0405: SimpleDateFormat fo = new SimpleDateFormat(
0406: "EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
0407: fo.setTimeZone(TimeZone.getTimeZone("GMT"));
0408: requests.setIfNotSet("If-Modified-Since", fo
0409: .format(date));
0410: }
0411: // check for preemptive authorization
0412: AuthenticationInfo sauth = AuthenticationInfo
0413: .getServerAuth(url);
0414: if (sauth != null
0415: && sauth.supportsPreemptiveAuthorization()) {
0416: // Sets "Authorization"
0417: requests.setIfNotSet(sauth.getHeaderName(), sauth
0418: .getHeaderValue(url, method));
0419: currentServerCredentials = sauth;
0420: }
0421:
0422: if (!method.equals("PUT")
0423: && (poster != null || streaming())) {
0424: requests.setIfNotSet("Content-type",
0425: "application/x-www-form-urlencoded");
0426: }
0427:
0428: if (streaming()) {
0429: if (chunkLength != -1) {
0430: requests.set("Transfer-Encoding", "chunked");
0431: } else {
0432: requests.set("Content-Length", String
0433: .valueOf(fixedContentLength));
0434: }
0435: } else if (poster != null) {
0436: /* add Content-Length & POST/PUT data */
0437: synchronized (poster) {
0438: /* close it, so no more data can be added */
0439: poster.close();
0440: requests.set("Content-Length", String
0441: .valueOf(poster.size()));
0442: }
0443: }
0444:
0445: // get applicable cookies based on the uri and request headers
0446: // add them to the existing request headers
0447: setCookieHeader();
0448:
0449: setRequests = true;
0450: }
0451: if (logger.isLoggable(Level.FINEST)) {
0452: logger.fine(requests.toString());
0453: }
0454: http.writeRequests(requests, poster);
0455: if (ps.checkError()) {
0456: String proxyHost = http.getProxyHostUsed();
0457: int proxyPort = http.getProxyPortUsed();
0458: disconnectInternal();
0459: if (failedOnce) {
0460: throw new IOException("Error writing to server");
0461: } else { // try once more
0462: failedOnce = true;
0463: if (proxyHost != null) {
0464: setProxiedClient(url, proxyHost, proxyPort);
0465: } else {
0466: setNewClient(url);
0467: }
0468: ps = (PrintStream) http.getOutputStream();
0469: connected = true;
0470: responses = new MessageHeader();
0471: setRequests = false;
0472: writeRequests();
0473: }
0474: }
0475: }
0476:
0477: /**
0478: * Create a new HttpClient object, bypassing the cache of
0479: * HTTP client objects/connections.
0480: *
0481: * @param url the URL being accessed
0482: */
0483: protected void setNewClient(URL url) throws IOException {
0484: setNewClient(url, false);
0485: }
0486:
0487: /**
0488: * Obtain a HttpsClient object. Use the cached copy if specified.
0489: *
0490: * @param url the URL being accessed
0491: * @param useCache whether the cached connection should be used
0492: * if present
0493: */
0494: protected void setNewClient(URL url, boolean useCache)
0495: throws IOException {
0496: http = HttpClient.New(url, null, -1, useCache, connectTimeout);
0497: http.setReadTimeout(readTimeout);
0498: }
0499:
0500: /**
0501: * Create a new HttpClient object, set up so that it uses
0502: * per-instance proxying to the given HTTP proxy. This
0503: * bypasses the cache of HTTP client objects/connections.
0504: *
0505: * @param url the URL being accessed
0506: * @param proxyHost the proxy host to use
0507: * @param proxyPort the proxy port to use
0508: */
0509: protected void setProxiedClient(URL url, String proxyHost,
0510: int proxyPort) throws IOException {
0511: setProxiedClient(url, proxyHost, proxyPort, false);
0512: }
0513:
0514: /**
0515: * Obtain a HttpClient object, set up so that it uses per-instance
0516: * proxying to the given HTTP proxy. Use the cached copy of HTTP
0517: * client objects/connections if specified.
0518: *
0519: * @param url the URL being accessed
0520: * @param proxyHost the proxy host to use
0521: * @param proxyPort the proxy port to use
0522: * @param useCache whether the cached connection should be used
0523: * if present
0524: */
0525: protected void setProxiedClient(URL url, String proxyHost,
0526: int proxyPort, boolean useCache) throws IOException {
0527: proxiedConnect(url, proxyHost, proxyPort, useCache);
0528: }
0529:
0530: protected void proxiedConnect(URL url, String proxyHost,
0531: int proxyPort, boolean useCache) throws IOException {
0532: http = HttpClient.New(url, proxyHost, proxyPort, useCache,
0533: connectTimeout);
0534: http.setReadTimeout(readTimeout);
0535: }
0536:
0537: protected HttpURLConnection(URL u, Handler handler)
0538: throws IOException {
0539: // we set proxy == null to distinguish this case with the case
0540: // when per connection proxy is set
0541: this (u, null, handler);
0542: }
0543:
0544: public HttpURLConnection(URL u, String host, int port) {
0545: this (u, new Proxy(Proxy.Type.HTTP, InetSocketAddress
0546: .createUnresolved(host, port)));
0547: }
0548:
0549: /** this constructor is used by other protocol handlers such as ftp
0550: that want to use http to fetch urls on their behalf.*/
0551: public HttpURLConnection(URL u, Proxy p) {
0552: this (u, p, new Handler());
0553: }
0554:
0555: protected HttpURLConnection(URL u, Proxy p, Handler handler) {
0556: super (u);
0557: requests = new MessageHeader();
0558: responses = new MessageHeader();
0559: this .handler = handler;
0560: instProxy = p;
0561: cookieHandler = (CookieHandler) java.security.AccessController
0562: .doPrivileged(new java.security.PrivilegedAction() {
0563: public Object run() {
0564: return CookieHandler.getDefault();
0565: }
0566: });
0567: cacheHandler = (ResponseCache) java.security.AccessController
0568: .doPrivileged(new java.security.PrivilegedAction() {
0569: public Object run() {
0570: return ResponseCache.getDefault();
0571: }
0572: });
0573: }
0574:
0575: /**
0576: * @deprecated. Use java.net.Authenticator.setDefault() instead.
0577: */
0578: public static void setDefaultAuthenticator(HttpAuthenticator a) {
0579: defaultAuth = a;
0580: }
0581:
0582: /**
0583: * opens a stream allowing redirects only to the same host.
0584: */
0585: public static InputStream openConnectionCheckRedirects(
0586: URLConnection c) throws IOException {
0587: boolean redir;
0588: int redirects = 0;
0589: InputStream in = null;
0590:
0591: do {
0592: if (c instanceof HttpURLConnection) {
0593: ((HttpURLConnection) c)
0594: .setInstanceFollowRedirects(false);
0595: }
0596:
0597: // We want to open the input stream before
0598: // getting headers, because getHeaderField()
0599: // et al swallow IOExceptions.
0600: in = c.getInputStream();
0601: redir = false;
0602:
0603: if (c instanceof HttpURLConnection) {
0604: HttpURLConnection http = (HttpURLConnection) c;
0605: int stat = http.getResponseCode();
0606: if (stat >= 300 && stat <= 307 && stat != 306
0607: && stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
0608: URL base = http.getURL();
0609: String loc = http.getHeaderField("Location");
0610: URL target = null;
0611: if (loc != null) {
0612: target = new URL(base, loc);
0613: }
0614: http.disconnect();
0615: if (target == null
0616: || !base.getProtocol().equals(
0617: target.getProtocol())
0618: || base.getPort() != target.getPort()
0619: || !hostsEqual(base, target)
0620: || redirects >= 5) {
0621: throw new SecurityException(
0622: "illegal URL redirect");
0623: }
0624: redir = true;
0625: c = target.openConnection();
0626: redirects++;
0627: }
0628: }
0629: } while (redir);
0630: return in;
0631: }
0632:
0633: //
0634: // Same as java.net.URL.hostsEqual
0635: //
0636: private static boolean hostsEqual(URL u1, URL u2) {
0637: final String h1 = u1.getHost();
0638: final String h2 = u2.getHost();
0639:
0640: if (h1 == null) {
0641: return h2 == null;
0642: } else if (h2 == null) {
0643: return false;
0644: } else if (h1.equalsIgnoreCase(h2)) {
0645: return true;
0646: }
0647: // Have to resolve addresses before comparing, otherwise
0648: // names like tachyon and tachyon.eng would compare different
0649: final boolean result[] = { false };
0650:
0651: java.security.AccessController
0652: .doPrivileged(new java.security.PrivilegedAction() {
0653: public Object run() {
0654: try {
0655: InetAddress a1 = InetAddress.getByName(h1);
0656: InetAddress a2 = InetAddress.getByName(h2);
0657: result[0] = a1.equals(a2);
0658: } catch (UnknownHostException e) {
0659: } catch (SecurityException e) {
0660: }
0661: return null;
0662: }
0663: });
0664:
0665: return result[0];
0666: }
0667:
0668: // overridden in HTTPS subclass
0669:
0670: public void connect() throws IOException {
0671: plainConnect();
0672: }
0673:
0674: private boolean checkReuseConnection() {
0675: if (connected) {
0676: return true;
0677: }
0678: if (reuseClient != null) {
0679: http = reuseClient;
0680: http.setReadTimeout(getReadTimeout());
0681: http.reuse = false;
0682: reuseClient = null;
0683: connected = true;
0684: return true;
0685: }
0686: return false;
0687: }
0688:
0689: protected void plainConnect() throws IOException {
0690: if (connected) {
0691: return;
0692: }
0693: // try to see if request can be served from local cache
0694: if (cacheHandler != null && getUseCaches()) {
0695: try {
0696: URI uri = ParseUtil.toURI(url);
0697: if (uri != null) {
0698: cachedResponse = cacheHandler.get(uri,
0699: getRequestMethod(), requests
0700: .getHeaders(EXCLUDE_HEADERS));
0701: if ("https".equalsIgnoreCase(uri.getScheme())
0702: && !(cachedResponse instanceof SecureCacheResponse)) {
0703: cachedResponse = null;
0704: }
0705: if (cachedResponse != null) {
0706: cachedHeaders = mapToMessageHeader(cachedResponse
0707: .getHeaders());
0708: cachedInputStream = cachedResponse.getBody();
0709: }
0710: }
0711: } catch (IOException ioex) {
0712: // ignore and commence normal connection
0713: }
0714: if (cachedHeaders != null && cachedInputStream != null) {
0715: connected = true;
0716: return;
0717: } else {
0718: cachedResponse = null;
0719: }
0720: }
0721: try {
0722: /* Try to open connections using the following scheme,
0723: * return on the first one that's successful:
0724: * 1) if (instProxy != null)
0725: * connect to instProxy; raise exception if failed
0726: * 2) else use system default ProxySelector
0727: * 3) is 2) fails, make direct connection
0728: */
0729:
0730: if (instProxy == null) { // no instance Proxy is set
0731: /**
0732: * Do we have to use a proxy?
0733: */
0734: ProxySelector sel = (ProxySelector) java.security.AccessController
0735: .doPrivileged(new java.security.PrivilegedAction() {
0736: public Object run() {
0737: return ProxySelector.getDefault();
0738: }
0739: });
0740: Proxy p = null;
0741: if (sel != null) {
0742: URI uri = sun.net.www.ParseUtil.toURI(url);
0743: Iterator<Proxy> it = sel.select(uri).iterator();
0744: while (it.hasNext()) {
0745: p = it.next();
0746: try {
0747: if (!failedOnce) {
0748: http = getNewHttpClient(url, p,
0749: connectTimeout);
0750: http.setReadTimeout(readTimeout);
0751: } else {
0752: // make sure to construct new connection if first
0753: // attempt failed
0754: http = getNewHttpClient(url, p,
0755: connectTimeout, false);
0756: http.setReadTimeout(readTimeout);
0757: }
0758: break;
0759: } catch (IOException ioex) {
0760: if (p != Proxy.NO_PROXY) {
0761: sel.connectFailed(uri, p.address(),
0762: ioex);
0763: if (!it.hasNext()) {
0764: // fallback to direct connection
0765: http = getNewHttpClient(url, null,
0766: connectTimeout, false);
0767: http.setReadTimeout(readTimeout);
0768: break;
0769: }
0770: } else {
0771: throw ioex;
0772: }
0773: continue;
0774: }
0775: }
0776: } else {
0777: // No proxy selector, create http client with no proxy
0778: if (!failedOnce) {
0779: http = getNewHttpClient(url, null,
0780: connectTimeout);
0781: http.setReadTimeout(readTimeout);
0782: } else {
0783: // make sure to construct new connection if first
0784: // attempt failed
0785: http = getNewHttpClient(url, null,
0786: connectTimeout, false);
0787: http.setReadTimeout(readTimeout);
0788: }
0789: }
0790: } else {
0791: if (!failedOnce) {
0792: http = getNewHttpClient(url, instProxy,
0793: connectTimeout);
0794: http.setReadTimeout(readTimeout);
0795: } else {
0796: // make sure to construct new connection if first
0797: // attempt failed
0798: http = getNewHttpClient(url, instProxy,
0799: connectTimeout, false);
0800: http.setReadTimeout(readTimeout);
0801: }
0802: }
0803:
0804: ps = (PrintStream) http.getOutputStream();
0805: } catch (IOException e) {
0806: throw e;
0807: }
0808: // constructor to HTTP client calls openserver
0809: connected = true;
0810: }
0811:
0812: // subclass HttpsClient will overwrite & return an instance of HttpsClient
0813: protected HttpClient getNewHttpClient(URL url, Proxy p,
0814: int connectTimeout) throws IOException {
0815: return HttpClient.New(url, p, connectTimeout);
0816: }
0817:
0818: // subclass HttpsClient will overwrite & return an instance of HttpsClient
0819: protected HttpClient getNewHttpClient(URL url, Proxy p,
0820: int connectTimeout, boolean useCache) throws IOException {
0821: return HttpClient.New(url, p, connectTimeout, useCache);
0822: }
0823:
0824: /*
0825: * Allowable input/output sequences:
0826: * [interpreted as POST/PUT]
0827: * - get output, [write output,] get input, [read input]
0828: * - get output, [write output]
0829: * [interpreted as GET]
0830: * - get input, [read input]
0831: * Disallowed:
0832: * - get input, [read input,] get output, [write output]
0833: */
0834:
0835: public synchronized OutputStream getOutputStream()
0836: throws IOException {
0837:
0838: try {
0839: if (!doOutput) {
0840: throw new ProtocolException(
0841: "cannot write to a URLConnection"
0842: + " if doOutput=false - call setDoOutput(true)");
0843: }
0844:
0845: if (method.equals("GET")) {
0846: method = "POST"; // Backward compatibility
0847: }
0848: if (!"POST".equals(method) && !"PUT".equals(method)
0849: && "http".equals(url.getProtocol())) {
0850: throw new ProtocolException("HTTP method " + method
0851: + " doesn't support output");
0852: }
0853:
0854: // if there's already an input stream open, throw an exception
0855: if (inputStream != null) {
0856: throw new ProtocolException(
0857: "Cannot write output after reading input.");
0858: }
0859:
0860: if (!checkReuseConnection())
0861: connect();
0862:
0863: /* REMIND: This exists to fix the HttpsURLConnection subclass.
0864: * Hotjava needs to run on JDK1.1FCS. Do proper fix in subclass
0865: * for 1.2 and remove this.
0866: */
0867:
0868: if (streaming() && strOutputStream == null) {
0869: writeRequests();
0870: }
0871: ps = (PrintStream) http.getOutputStream();
0872: if (streaming()) {
0873: if (strOutputStream == null) {
0874: if (fixedContentLength != -1) {
0875: strOutputStream = new StreamingOutputStream(ps,
0876: fixedContentLength);
0877: } else if (chunkLength != -1) {
0878: strOutputStream = new StreamingOutputStream(
0879: new ChunkedOutputStream(ps, chunkLength),
0880: -1);
0881: }
0882: }
0883: return strOutputStream;
0884: } else {
0885: if (poster == null) {
0886: poster = new PosterOutputStream();
0887: }
0888: return poster;
0889: }
0890: } catch (RuntimeException e) {
0891: disconnectInternal();
0892: throw e;
0893: } catch (IOException e) {
0894: disconnectInternal();
0895: throw e;
0896: }
0897: }
0898:
0899: private boolean streaming() {
0900: return (fixedContentLength != -1) || (chunkLength != -1);
0901: }
0902:
0903: /*
0904: * get applicable cookies based on the uri and request headers
0905: * add them to the existing request headers
0906: */
0907: private void setCookieHeader() throws IOException {
0908: if (cookieHandler != null) {
0909: // we only want to capture the user defined Cookies once, as
0910: // they cannot be changed by user code after we are connected,
0911: // only internally.
0912: if (setUserCookies) {
0913: int k = requests.getKey("Cookie");
0914: if (k != -1)
0915: userCookies = requests.getValue(k);
0916: setUserCookies = false;
0917: }
0918:
0919: // remove old Cookie header before setting new one.
0920: requests.remove("Cookie");
0921:
0922: URI uri = ParseUtil.toURI(url);
0923: if (uri != null) {
0924: Map cookies = cookieHandler.get(uri, requests
0925: .getHeaders(EXCLUDE_HEADERS));
0926: if (!cookies.isEmpty()) {
0927: Set s = cookies.entrySet();
0928: Iterator k_itr = s.iterator();
0929: while (k_itr.hasNext()) {
0930: Map.Entry entry = (Map.Entry) k_itr.next();
0931: String key = (String) entry.getKey();
0932: // ignore all entries that don't have "Cookie"
0933: // or "Cookie2" as keys
0934: if (!"Cookie".equalsIgnoreCase(key)
0935: && !"Cookie2".equalsIgnoreCase(key)) {
0936: continue;
0937: }
0938: List l = (List) entry.getValue();
0939: if (l != null && !l.isEmpty()) {
0940: Iterator v_itr = l.iterator();
0941: StringBuilder cookieValue = new StringBuilder();
0942: while (v_itr.hasNext()) {
0943: String value = (String) v_itr.next();
0944: cookieValue.append(value).append(';');
0945: }
0946: // strip off the ending ;-sign
0947: try {
0948: requests.add(key, cookieValue
0949: .substring(0, cookieValue
0950: .length() - 1));
0951: } catch (StringIndexOutOfBoundsException ignored) {
0952: // no-op
0953: }
0954: }
0955: }
0956: }
0957: }
0958: if (userCookies != null) {
0959: int k;
0960: if ((k = requests.getKey("Cookie")) != -1)
0961: requests.set("Cookie", requests.getValue(k) + ";"
0962: + userCookies);
0963: else
0964: requests.set("Cookie", userCookies);
0965: }
0966:
0967: } // end of getting cookies
0968: }
0969:
0970: public synchronized InputStream getInputStream() throws IOException {
0971:
0972: if (!doInput) {
0973: throw new ProtocolException(
0974: "Cannot read from URLConnection"
0975: + " if doInput=false (call setDoInput(true))");
0976: }
0977:
0978: if (rememberedException != null) {
0979: if (rememberedException instanceof RuntimeException)
0980: throw new RuntimeException(rememberedException);
0981: else {
0982: throw getChainedException((IOException) rememberedException);
0983: }
0984: }
0985:
0986: if (inputStream != null) {
0987: return inputStream;
0988: }
0989:
0990: if (streaming()) {
0991: if (strOutputStream == null) {
0992: getOutputStream();
0993: }
0994: /* make sure stream is closed */
0995: strOutputStream.close();
0996: if (!strOutputStream.writtenOK()) {
0997: throw new IOException("Incomplete output stream");
0998: }
0999: }
1000:
1001: int redirects = 0;
1002: int respCode = 0;
1003: int cl = -1;
1004: AuthenticationInfo serverAuthentication = null;
1005: AuthenticationInfo proxyAuthentication = null;
1006: AuthenticationHeader srvHdr = null;
1007:
1008: // If the user has set either of these headers then do not remove them
1009: isUserServerAuth = requests.getKey("Authorization") != -1;
1010: isUserProxyAuth = requests.getKey("Proxy-Authorization") != -1;
1011:
1012: try {
1013: do {
1014: if (!checkReuseConnection())
1015: connect();
1016:
1017: if (cachedInputStream != null) {
1018: return cachedInputStream;
1019: }
1020:
1021: // Check if URL should be metered
1022: boolean meteredInput = ProgressMonitor.getDefault()
1023: .shouldMeterInput(url, method);
1024:
1025: if (meteredInput) {
1026: pi = new ProgressSource(url, method);
1027: pi.beginTracking();
1028: }
1029:
1030: /* REMIND: This exists to fix the HttpsURLConnection subclass.
1031: * Hotjava needs to run on JDK1.1FCS. Do proper fix once a
1032: * proper solution for SSL can be found.
1033: */
1034: ps = (PrintStream) http.getOutputStream();
1035:
1036: if (!streaming()) {
1037: writeRequests();
1038: }
1039: http.parseHTTP(responses, pi, this );
1040: if (logger.isLoggable(Level.FINEST)) {
1041: logger.fine(responses.toString());
1042: }
1043: inputStream = http.getInputStream();
1044:
1045: respCode = getResponseCode();
1046: if (respCode == HTTP_PROXY_AUTH) {
1047: if (streaming()) {
1048: disconnectInternal();
1049: throw new HttpRetryException(RETRY_MSG1,
1050: HTTP_PROXY_AUTH);
1051: }
1052:
1053: // changes: add a 3rd parameter to the constructor of
1054: // AuthenticationHeader, so that NegotiateAuthentication.
1055: // isSupported can be tested.
1056: // The other 2 appearances of "new AuthenticationHeader" is
1057: // altered in similar ways.
1058:
1059: AuthenticationHeader authhdr = new AuthenticationHeader(
1060: "Proxy-Authenticate", responses, http
1061: .getProxyHostUsed());
1062:
1063: if (!doingNTLMp2ndStage) {
1064: proxyAuthentication = resetProxyAuthentication(
1065: proxyAuthentication, authhdr);
1066: if (proxyAuthentication != null) {
1067: redirects++;
1068: disconnectInternal();
1069: continue;
1070: }
1071: } else {
1072: /* in this case, only one header field will be present */
1073: String raw = responses
1074: .findValue("Proxy-Authenticate");
1075: reset();
1076: if (!proxyAuthentication.setHeaders(this ,
1077: authhdr.headerParser(), raw)) {
1078: disconnectInternal();
1079: throw new IOException(
1080: "Authentication failure");
1081: }
1082: if (serverAuthentication != null
1083: && srvHdr != null
1084: && !serverAuthentication.setHeaders(
1085: this , srvHdr.headerParser(),
1086: raw)) {
1087: disconnectInternal();
1088: throw new IOException(
1089: "Authentication failure");
1090: }
1091: authObj = null;
1092: doingNTLMp2ndStage = false;
1093: continue;
1094: }
1095: }
1096:
1097: // cache proxy authentication info
1098: if (proxyAuthentication != null) {
1099: // cache auth info on success, domain header not relevant.
1100: proxyAuthentication.addToCache();
1101: }
1102:
1103: if (respCode == HTTP_UNAUTHORIZED) {
1104: if (streaming()) {
1105: disconnectInternal();
1106: throw new HttpRetryException(RETRY_MSG2,
1107: HTTP_UNAUTHORIZED);
1108: }
1109:
1110: srvHdr = new AuthenticationHeader(
1111: "WWW-Authenticate", responses, url
1112: .getHost().toLowerCase());
1113:
1114: String raw = srvHdr.raw();
1115: if (!doingNTLM2ndStage) {
1116: if ((serverAuthentication != null)
1117: && !(serverAuthentication instanceof NTLMAuthentication)) {
1118: if (serverAuthentication
1119: .isAuthorizationStale(raw)) {
1120: /* we can retry with the current credentials */
1121: disconnectInternal();
1122: redirects++;
1123: requests.set(serverAuthentication
1124: .getHeaderName(),
1125: serverAuthentication
1126: .getHeaderValue(url,
1127: method));
1128: currentServerCredentials = serverAuthentication;
1129: setCookieHeader();
1130: continue;
1131: } else {
1132: serverAuthentication.removeFromCache();
1133: }
1134: }
1135: serverAuthentication = getServerAuthentication(srvHdr);
1136: currentServerCredentials = serverAuthentication;
1137:
1138: if (serverAuthentication != null) {
1139: disconnectInternal();
1140: redirects++; // don't let things loop ad nauseum
1141: setCookieHeader();
1142: continue;
1143: }
1144: } else {
1145: reset();
1146: /* header not used for ntlm */
1147: if (!serverAuthentication.setHeaders(this ,
1148: null, raw)) {
1149: disconnectInternal();
1150: throw new IOException(
1151: "Authentication failure");
1152: }
1153: doingNTLM2ndStage = false;
1154: authObj = null;
1155: setCookieHeader();
1156: continue;
1157: }
1158: }
1159: // cache server authentication info
1160: if (serverAuthentication != null) {
1161: // cache auth info on success
1162: if (!(serverAuthentication instanceof DigestAuthentication)
1163: || (domain == null)) {
1164: if (serverAuthentication instanceof BasicAuthentication) {
1165: // check if the path is shorter than the existing entry
1166: String npath = AuthenticationInfo
1167: .reducePath(url.getPath());
1168: String opath = serverAuthentication.path;
1169: if (!opath.startsWith(npath)
1170: || npath.length() >= opath.length()) {
1171: /* npath is longer, there must be a common root */
1172: npath = BasicAuthentication
1173: .getRootPath(opath, npath);
1174: }
1175: // remove the entry and create a new one
1176: BasicAuthentication a = (BasicAuthentication) serverAuthentication
1177: .clone();
1178: serverAuthentication.removeFromCache();
1179: a.path = npath;
1180: serverAuthentication = a;
1181: }
1182: serverAuthentication.addToCache();
1183: } else {
1184: // what we cache is based on the domain list in the request
1185: DigestAuthentication srv = (DigestAuthentication) serverAuthentication;
1186: StringTokenizer tok = new StringTokenizer(
1187: domain, " ");
1188: String realm = srv.realm;
1189: PasswordAuthentication pw = srv.pw;
1190: digestparams = srv.params;
1191: while (tok.hasMoreTokens()) {
1192: String path = tok.nextToken();
1193: try {
1194: /* path could be an abs_path or a complete URI */
1195: URL u = new URL(url, path);
1196: DigestAuthentication d = new DigestAuthentication(
1197: false, u, realm, "Digest", pw,
1198: digestparams);
1199: d.addToCache();
1200: } catch (Exception e) {
1201: }
1202: }
1203: }
1204: }
1205:
1206: // some flags should be reset to its initialized form so that
1207: // even after a redirect the necessary checks can still be
1208: // preformed.
1209:
1210: //serverAuthentication = null;
1211: doingNTLMp2ndStage = false;
1212: doingNTLM2ndStage = false;
1213: if (!isUserServerAuth)
1214: requests.remove("Authorization");
1215: if (!isUserProxyAuth)
1216: requests.remove("Proxy-Authorization");
1217:
1218: if (respCode == HTTP_OK) {
1219: checkResponseCredentials(false);
1220: } else {
1221: needToCheck = false;
1222: }
1223:
1224: // a flag need to clean
1225: needToCheck = true;
1226:
1227: if (followRedirect()) {
1228: /* if we should follow a redirect, then the followRedirects()
1229: * method will disconnect() and re-connect us to the new
1230: * location
1231: */
1232: redirects++;
1233:
1234: // redirecting HTTP response may have set cookie, so
1235: // need to re-generate request header
1236: setCookieHeader();
1237:
1238: continue;
1239: }
1240:
1241: try {
1242: cl = Integer.parseInt(responses
1243: .findValue("content-length"));
1244: } catch (Exception exc) {
1245: }
1246: ;
1247:
1248: if (method.equals("HEAD") || cl == 0
1249: || respCode == HTTP_NOT_MODIFIED
1250: || respCode == HTTP_NO_CONTENT) {
1251:
1252: if (pi != null) {
1253: pi.finishTracking();
1254: pi = null;
1255: }
1256: http.finished();
1257: http = null;
1258: inputStream = new EmptyInputStream();
1259: connected = false;
1260: }
1261:
1262: if (respCode == 200 || respCode == 203
1263: || respCode == 206 || respCode == 300
1264: || respCode == 301 || respCode == 410) {
1265: if (cacheHandler != null) {
1266: // give cache a chance to save response in cache
1267: URI uri = ParseUtil.toURI(url);
1268: if (uri != null) {
1269: URLConnection uconn = this ;
1270: if ("https".equalsIgnoreCase(uri
1271: .getScheme())) {
1272: try {
1273: // use reflection to get to the public
1274: // HttpsURLConnection instance saved in
1275: // DelegateHttpsURLConnection
1276: uconn = (URLConnection) this
1277: .getClass()
1278: .getField(
1279: "httpsURLConnection")
1280: .get(this );
1281: } catch (IllegalAccessException iae) {
1282: // ignored; use 'this'
1283: } catch (NoSuchFieldException nsfe) {
1284: // ignored; use 'this'
1285: }
1286: }
1287: CacheRequest cacheRequest = cacheHandler
1288: .put(uri, uconn);
1289: if (cacheRequest != null && http != null) {
1290: http.setCacheRequest(cacheRequest);
1291: inputStream = new HttpInputStream(
1292: inputStream, cacheRequest);
1293: }
1294: }
1295: }
1296: }
1297:
1298: if (!(inputStream instanceof HttpInputStream)) {
1299: inputStream = new HttpInputStream(inputStream);
1300: }
1301:
1302: if (respCode >= 400) {
1303: if (respCode == 404 || respCode == 410) {
1304: throw new FileNotFoundException(url.toString());
1305: } else {
1306: throw new java.io.IOException(
1307: "Server returned HTTP"
1308: + " response code: " + respCode
1309: + " for URL: " + url.toString());
1310: }
1311: }
1312: poster = null;
1313: strOutputStream = null;
1314: return inputStream;
1315: } while (redirects < maxRedirects);
1316:
1317: throw new ProtocolException("Server redirected too many "
1318: + " times (" + redirects + ")");
1319: } catch (RuntimeException e) {
1320: disconnectInternal();
1321: rememberedException = e;
1322: throw e;
1323: } catch (IOException e) {
1324: rememberedException = e;
1325:
1326: // buffer the error stream if bytes < 4k
1327: // and it can be buffered within 1 second
1328: String te = responses.findValue("Transfer-Encoding");
1329: if (http != null
1330: && http.isKeepingAlive()
1331: && enableESBuffer
1332: && (cl > 0 || (te != null && te
1333: .equalsIgnoreCase("chunked")))) {
1334: errorStream = ErrorStream.getErrorStream(inputStream,
1335: cl, http);
1336: }
1337: throw e;
1338: } finally {
1339: if (respCode == HTTP_PROXY_AUTH
1340: && proxyAuthentication != null) {
1341: proxyAuthentication.endAuthRequest();
1342: } else if (respCode == HTTP_UNAUTHORIZED
1343: && serverAuthentication != null) {
1344: serverAuthentication.endAuthRequest();
1345: }
1346: }
1347: }
1348:
1349: /*
1350: * Creates a chained exception that has the same type as
1351: * original exception and with the same message. Right now,
1352: * there is no convenient APIs for doing so.
1353: */
1354: private IOException getChainedException(
1355: IOException rememberedException) {
1356: try {
1357: final IOException originalException = rememberedException;
1358: final Class[] cls = new Class[1];
1359: cls[0] = String.class;
1360: final String[] args = new String[1];
1361: args[0] = originalException.getMessage();
1362: IOException chainedException = (IOException) java.security.AccessController
1363: .doPrivileged(new java.security.PrivilegedExceptionAction() {
1364: public Object run() throws Exception {
1365: Constructor ctr = originalException
1366: .getClass().getConstructor(cls);
1367: return (IOException) ctr
1368: .newInstance((Object[]) args);
1369: }
1370: });
1371: chainedException.initCause(originalException);
1372: return chainedException;
1373: } catch (Exception ignored) {
1374: return (IOException) rememberedException;
1375: }
1376: }
1377:
1378: public InputStream getErrorStream() {
1379: if (connected && responseCode >= 400) {
1380: // Client Error 4xx and Server Error 5xx
1381: if (errorStream != null) {
1382: return errorStream;
1383: } else if (inputStream != null) {
1384: return inputStream;
1385: }
1386: }
1387: return null;
1388: }
1389:
1390: /**
1391: * set or reset proxy authentication info in request headers
1392: * after receiving a 407 error. In the case of NTLM however,
1393: * receiving a 407 is normal and we just skip the stale check
1394: * because ntlm does not support this feature.
1395: */
1396: private AuthenticationInfo resetProxyAuthentication(
1397: AuthenticationInfo proxyAuthentication,
1398: AuthenticationHeader auth) {
1399: if ((proxyAuthentication != null)
1400: && !(proxyAuthentication instanceof NTLMAuthentication)) {
1401: String raw = auth.raw();
1402: if (proxyAuthentication.isAuthorizationStale(raw)) {
1403: /* we can retry with the current credentials */
1404: requests
1405: .set(proxyAuthentication.getHeaderName(),
1406: proxyAuthentication.getHeaderValue(url,
1407: method));
1408: currentProxyCredentials = proxyAuthentication;
1409: return proxyAuthentication;
1410: } else {
1411: proxyAuthentication.removeFromCache();
1412: }
1413: }
1414: proxyAuthentication = getHttpProxyAuthentication(auth);
1415: currentProxyCredentials = proxyAuthentication;
1416: return proxyAuthentication;
1417: }
1418:
1419: /**
1420: * establish a tunnel through proxy server
1421: */
1422: public synchronized void doTunneling() throws IOException {
1423: int retryTunnel = 0;
1424: String statusLine = "";
1425: int respCode = 0;
1426: AuthenticationInfo proxyAuthentication = null;
1427: String proxyHost = null;
1428: int proxyPort = -1;
1429:
1430: // save current requests so that they can be restored after tunnel is setup.
1431: MessageHeader savedRequests = requests;
1432: requests = new MessageHeader();
1433:
1434: try {
1435: do {
1436: if (!checkReuseConnection()) {
1437: proxiedConnect(url, proxyHost, proxyPort, false);
1438: }
1439: // send the "CONNECT" request to establish a tunnel
1440: // through proxy server
1441: sendCONNECTRequest();
1442: responses.reset();
1443:
1444: // There is no need to track progress in HTTP Tunneling,
1445: // so ProgressSource is null.
1446: http.parseHTTP(responses, null, this );
1447:
1448: statusLine = responses.getValue(0);
1449: StringTokenizer st = new StringTokenizer(statusLine);
1450: st.nextToken();
1451: respCode = Integer.parseInt(st.nextToken().trim());
1452: if (respCode == HTTP_PROXY_AUTH) {
1453: AuthenticationHeader authhdr = new AuthenticationHeader(
1454: "Proxy-Authenticate", responses, http
1455: .getProxyHostUsed());
1456: if (!doingNTLMp2ndStage) {
1457: proxyAuthentication = resetProxyAuthentication(
1458: proxyAuthentication, authhdr);
1459: if (proxyAuthentication != null) {
1460: proxyHost = http.getProxyHostUsed();
1461: proxyPort = http.getProxyPortUsed();
1462: disconnectInternal();
1463: retryTunnel++;
1464: continue;
1465: }
1466: } else {
1467: String raw = responses
1468: .findValue("Proxy-Authenticate");
1469: reset();
1470: if (!proxyAuthentication.setHeaders(this ,
1471: authhdr.headerParser(), raw)) {
1472: proxyHost = http.getProxyHostUsed();
1473: proxyPort = http.getProxyPortUsed();
1474: disconnectInternal();
1475: throw new IOException(
1476: "Authentication failure");
1477: }
1478: authObj = null;
1479: doingNTLMp2ndStage = false;
1480: continue;
1481: }
1482: }
1483: // cache proxy authentication info
1484: if (proxyAuthentication != null) {
1485: // cache auth info on success, domain header not relevant.
1486: proxyAuthentication.addToCache();
1487: }
1488:
1489: if (respCode == HTTP_OK) {
1490: break;
1491: }
1492: // we don't know how to deal with other response code
1493: // so disconnect and report error
1494: disconnectInternal();
1495: break;
1496: } while (retryTunnel < maxRedirects);
1497:
1498: if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) {
1499: throw new IOException("Unable to tunnel through proxy."
1500: + " Proxy returns \"" + statusLine + "\"");
1501: }
1502: } finally {
1503: if (respCode == HTTP_PROXY_AUTH
1504: && proxyAuthentication != null) {
1505: proxyAuthentication.endAuthRequest();
1506: }
1507: }
1508:
1509: // restore original request headers
1510: requests = savedRequests;
1511:
1512: // reset responses
1513: responses.reset();
1514: }
1515:
1516: /**
1517: * send a CONNECT request for establishing a tunnel to proxy server
1518: */
1519: private void sendCONNECTRequest() throws IOException {
1520: int port = url.getPort();
1521:
1522: // setRequests == true indicates the std. request headers
1523: // have been set in (previous) requests.
1524: // so the first one must be the http method (GET, etc.).
1525: // we need to set it to CONNECT soon, remove this one first.
1526: // otherwise, there may have 2 http methods in headers
1527: if (setRequests)
1528: requests.set(0, null, null);
1529:
1530: requests.prepend("CONNECT " + url.getHost() + ":"
1531: + (port != -1 ? port : url.getDefaultPort()) + " "
1532: + httpVersion, null);
1533: requests.setIfNotSet("User-Agent", userAgent);
1534:
1535: String host = url.getHost();
1536: if (port != -1 && port != url.getDefaultPort()) {
1537: host += ":" + String.valueOf(port);
1538: }
1539: requests.setIfNotSet("Host", host);
1540:
1541: // Not really necessary for a tunnel, but can't hurt
1542: requests.setIfNotSet("Accept", acceptString);
1543:
1544: setPreemptiveProxyAuthentication(requests);
1545: http.writeRequests(requests, null);
1546: // remove CONNECT header
1547: requests.set(0, null, null);
1548: }
1549:
1550: /**
1551: * Sets pre-emptive proxy authentication in header
1552: */
1553: private void setPreemptiveProxyAuthentication(MessageHeader requests) {
1554: AuthenticationInfo pauth = AuthenticationInfo.getProxyAuth(http
1555: .getProxyHostUsed(), http.getProxyPortUsed());
1556: if (pauth != null && pauth.supportsPreemptiveAuthorization()) {
1557: // Sets "Proxy-authorization"
1558: requests.set(pauth.getHeaderName(), pauth.getHeaderValue(
1559: url, method));
1560: currentProxyCredentials = pauth;
1561: }
1562: }
1563:
1564: /**
1565: * Gets the authentication for an HTTP proxy, and applies it to
1566: * the connection.
1567: */
1568: private AuthenticationInfo getHttpProxyAuthentication(
1569: AuthenticationHeader authhdr) {
1570: /* get authorization from authenticator */
1571: AuthenticationInfo ret = null;
1572: String raw = authhdr.raw();
1573: String host = http.getProxyHostUsed();
1574: int port = http.getProxyPortUsed();
1575: if (host != null && authhdr.isPresent()) {
1576: HeaderParser p = authhdr.headerParser();
1577: String realm = p.findValue("realm");
1578: String scheme = authhdr.scheme();
1579: char schemeID;
1580: if ("basic".equalsIgnoreCase(scheme)) {
1581: schemeID = BasicAuthentication.BASIC_AUTH;
1582: } else if ("digest".equalsIgnoreCase(scheme)) {
1583: schemeID = DigestAuthentication.DIGEST_AUTH;
1584: } else if ("ntlm".equalsIgnoreCase(scheme)) {
1585: schemeID = NTLMAuthentication.NTLM_AUTH;
1586: doingNTLMp2ndStage = true;
1587: } else if ("Kerberos".equalsIgnoreCase(scheme)) {
1588: schemeID = NegotiateAuthentication.KERBEROS_AUTH;
1589: doingNTLMp2ndStage = true;
1590: } else if ("Negotiate".equalsIgnoreCase(scheme)) {
1591: schemeID = NegotiateAuthentication.NEGOTIATE_AUTH;
1592: doingNTLMp2ndStage = true;
1593: } else {
1594: schemeID = 0;
1595: }
1596: if (realm == null)
1597: realm = "";
1598: ret = AuthenticationInfo.getProxyAuth(host, port, realm,
1599: schemeID);
1600: if (ret == null) {
1601: if (schemeID == BasicAuthentication.BASIC_AUTH) {
1602: InetAddress addr = null;
1603: try {
1604: final String finalHost = host;
1605: addr = (InetAddress) java.security.AccessController
1606: .doPrivileged(new java.security.PrivilegedExceptionAction() {
1607: public Object run()
1608: throws java.net.UnknownHostException {
1609: return InetAddress
1610: .getByName(finalHost);
1611: }
1612: });
1613: } catch (java.security.PrivilegedActionException ignored) {
1614: // User will have an unknown host.
1615: }
1616: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1617: host, addr, port, "http", realm, scheme,
1618: url, RequestorType.PROXY);
1619: if (a != null) {
1620: ret = new BasicAuthentication(true, host, port,
1621: realm, a);
1622: }
1623: } else if (schemeID == DigestAuthentication.DIGEST_AUTH) {
1624: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1625: host, null, port, url.getProtocol(), realm,
1626: scheme, url, RequestorType.PROXY);
1627: if (a != null) {
1628: DigestAuthentication.Parameters params = new DigestAuthentication.Parameters();
1629: ret = new DigestAuthentication(true, host,
1630: port, realm, scheme, a, params);
1631: }
1632: } else if (schemeID == NTLMAuthentication.NTLM_AUTH) {
1633: PasswordAuthentication a = null;
1634: if (!tryTransparentNTLMProxy) {
1635: a = privilegedRequestPasswordAuthentication(
1636: host, null, port, url.getProtocol(),
1637: "", scheme, url, RequestorType.PROXY);
1638: }
1639: /* If we are not trying transparent authentication then
1640: * we need to have a PasswordAuthentication instance. For
1641: * transparent authentication (Windows only) the username
1642: * and password will be picked up from the current logged
1643: * on users credentials.
1644: */
1645: if (tryTransparentNTLMProxy
1646: || (!tryTransparentNTLMProxy && a != null)) {
1647: ret = new NTLMAuthentication(true, host, port,
1648: a);
1649: }
1650:
1651: tryTransparentNTLMProxy = false;
1652: } else if (schemeID == NegotiateAuthentication.NEGOTIATE_AUTH) {
1653: ret = new NegotiateAuthentication(true, host, port,
1654: null, "Negotiate");
1655: } else if (schemeID == NegotiateAuthentication.KERBEROS_AUTH) {
1656: ret = new NegotiateAuthentication(true, host, port,
1657: null, "Kerberos");
1658: }
1659: }
1660: // For backwards compatibility, we also try defaultAuth
1661: // REMIND: Get rid of this for JDK2.0.
1662:
1663: if (ret == null && defaultAuth != null
1664: && defaultAuth.schemeSupported(scheme)) {
1665: try {
1666: URL u = new URL("http", host, port, "/");
1667: String a = defaultAuth.authString(u, scheme, realm);
1668: if (a != null) {
1669: ret = new BasicAuthentication(true, host, port,
1670: realm, a);
1671: // not in cache by default - cache on success
1672: }
1673: } catch (java.net.MalformedURLException ignored) {
1674: }
1675: }
1676: if (ret != null) {
1677: if (!ret.setHeaders(this , p, raw)) {
1678: ret = null;
1679: }
1680: }
1681: }
1682: return ret;
1683: }
1684:
1685: /**
1686: * Gets the authentication for an HTTP server, and applies it to
1687: * the connection.
1688: * @param authHdr the AuthenticationHeader which tells what auth scheme is
1689: * prefered.
1690: */
1691: private AuthenticationInfo getServerAuthentication(
1692: AuthenticationHeader authhdr) {
1693: /* get authorization from authenticator */
1694: AuthenticationInfo ret = null;
1695: String raw = authhdr.raw();
1696: /* When we get an NTLM auth from cache, don't set any special headers */
1697: if (authhdr.isPresent()) {
1698: HeaderParser p = authhdr.headerParser();
1699: String realm = p.findValue("realm");
1700: String scheme = authhdr.scheme();
1701: char schemeID;
1702: if ("basic".equalsIgnoreCase(scheme)) {
1703: schemeID = BasicAuthentication.BASIC_AUTH;
1704: } else if ("digest".equalsIgnoreCase(scheme)) {
1705: schemeID = DigestAuthentication.DIGEST_AUTH;
1706: } else if ("ntlm".equalsIgnoreCase(scheme)) {
1707: schemeID = NTLMAuthentication.NTLM_AUTH;
1708: doingNTLM2ndStage = true;
1709: } else if ("Kerberos".equalsIgnoreCase(scheme)) {
1710: schemeID = NegotiateAuthentication.KERBEROS_AUTH;
1711: doingNTLM2ndStage = true;
1712: } else if ("Negotiate".equalsIgnoreCase(scheme)) {
1713: schemeID = NegotiateAuthentication.NEGOTIATE_AUTH;
1714: doingNTLM2ndStage = true;
1715: } else {
1716: schemeID = 0;
1717: }
1718: domain = p.findValue("domain");
1719: if (realm == null)
1720: realm = "";
1721: ret = AuthenticationInfo
1722: .getServerAuth(url, realm, schemeID);
1723: InetAddress addr = null;
1724: if (ret == null) {
1725: try {
1726: addr = InetAddress.getByName(url.getHost());
1727: } catch (java.net.UnknownHostException ignored) {
1728: // User will have addr = null
1729: }
1730: }
1731: // replacing -1 with default port for a protocol
1732: int port = url.getPort();
1733: if (port == -1) {
1734: port = url.getDefaultPort();
1735: }
1736: if (ret == null) {
1737: if (schemeID == NegotiateAuthentication.KERBEROS_AUTH) {
1738: URL url1;
1739: try {
1740: url1 = new URL(url, "/"); /* truncate the path */
1741: } catch (Exception e) {
1742: url1 = url;
1743: }
1744: ret = new NegotiateAuthentication(false, url1,
1745: null, "Kerberos");
1746: }
1747: if (schemeID == NegotiateAuthentication.NEGOTIATE_AUTH) {
1748: URL url1;
1749: try {
1750: url1 = new URL(url, "/"); /* truncate the path */
1751: } catch (Exception e) {
1752: url1 = url;
1753: }
1754: ret = new NegotiateAuthentication(false, url1,
1755: null, "Negotiate");
1756: }
1757: if (schemeID == BasicAuthentication.BASIC_AUTH) {
1758: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1759: url.getHost(), addr, port, url
1760: .getProtocol(), realm, scheme, url,
1761: RequestorType.SERVER);
1762: if (a != null) {
1763: ret = new BasicAuthentication(false, url,
1764: realm, a);
1765: }
1766: }
1767:
1768: if (schemeID == DigestAuthentication.DIGEST_AUTH) {
1769: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1770: url.getHost(), addr, port, url
1771: .getProtocol(), realm, scheme, url,
1772: RequestorType.SERVER);
1773: if (a != null) {
1774: digestparams = new DigestAuthentication.Parameters();
1775: ret = new DigestAuthentication(false, url,
1776: realm, scheme, a, digestparams);
1777: }
1778: }
1779:
1780: if (schemeID == NTLMAuthentication.NTLM_AUTH) {
1781: URL url1;
1782: try {
1783: url1 = new URL(url, "/"); /* truncate the path */
1784: } catch (Exception e) {
1785: url1 = url;
1786: }
1787: PasswordAuthentication a = null;
1788: if (!tryTransparentNTLMServer) {
1789: a = privilegedRequestPasswordAuthentication(url
1790: .getHost(), addr, port, url
1791: .getProtocol(), "", scheme, url,
1792: RequestorType.SERVER);
1793: }
1794:
1795: /* If we are not trying transparent authentication then
1796: * we need to have a PasswordAuthentication instance. For
1797: * transparent authentication (Windows only) the username
1798: * and password will be picked up from the current logged
1799: * on users credentials.
1800: */
1801: if (tryTransparentNTLMServer
1802: || (!tryTransparentNTLMServer && a != null)) {
1803: ret = new NTLMAuthentication(false, url1, a);
1804: }
1805:
1806: tryTransparentNTLMServer = false;
1807: }
1808: }
1809:
1810: // For backwards compatibility, we also try defaultAuth
1811: // REMIND: Get rid of this for JDK2.0.
1812:
1813: if (ret == null && defaultAuth != null
1814: && defaultAuth.schemeSupported(scheme)) {
1815: String a = defaultAuth.authString(url, scheme, realm);
1816: if (a != null) {
1817: ret = new BasicAuthentication(false, url, realm, a);
1818: // not in cache by default - cache on success
1819: }
1820: }
1821:
1822: if (ret != null) {
1823: if (!ret.setHeaders(this , p, raw)) {
1824: ret = null;
1825: }
1826: }
1827: }
1828: return ret;
1829: }
1830:
1831: /* inclose will be true if called from close(), in which case we
1832: * force the call to check because this is the last chance to do so.
1833: * If not in close(), then the authentication info could arrive in a trailer
1834: * field, which we have not read yet.
1835: */
1836: private void checkResponseCredentials(boolean inClose)
1837: throws IOException {
1838: try {
1839: if (!needToCheck)
1840: return;
1841: if (validateProxy && currentProxyCredentials != null) {
1842: String raw = responses
1843: .findValue("Proxy-Authentication-Info");
1844: if (inClose || (raw != null)) {
1845: currentProxyCredentials.checkResponse(raw, method,
1846: url);
1847: currentProxyCredentials = null;
1848: }
1849: }
1850: if (validateServer && currentServerCredentials != null) {
1851: String raw = responses.findValue("Authentication-Info");
1852: if (inClose || (raw != null)) {
1853: currentServerCredentials.checkResponse(raw, method,
1854: url);
1855: currentServerCredentials = null;
1856: }
1857: }
1858: if ((currentServerCredentials == null)
1859: && (currentProxyCredentials == null)) {
1860: needToCheck = false;
1861: }
1862: } catch (IOException e) {
1863: disconnectInternal();
1864: connected = false;
1865: throw e;
1866: }
1867: }
1868:
1869: /* Tells us whether to follow a redirect. If so, it
1870: * closes the connection (break any keep-alive) and
1871: * resets the url, re-connects, and resets the request
1872: * property.
1873: */
1874: private boolean followRedirect() throws IOException {
1875: if (!getInstanceFollowRedirects()) {
1876: return false;
1877: }
1878:
1879: int stat = getResponseCode();
1880: if (stat < 300 || stat > 307 || stat == 306
1881: || stat == HTTP_NOT_MODIFIED) {
1882: return false;
1883: }
1884: String loc = getHeaderField("Location");
1885: if (loc == null) {
1886: /* this should be present - if not, we have no choice
1887: * but to go forward w/ the response we got
1888: */
1889: return false;
1890: }
1891: URL locUrl;
1892: try {
1893: locUrl = new URL(loc);
1894: if (!url.getProtocol().equalsIgnoreCase(
1895: locUrl.getProtocol())) {
1896: return false;
1897: }
1898:
1899: } catch (MalformedURLException mue) {
1900: // treat loc as a relative URI to conform to popular browsers
1901: locUrl = new URL(url, loc);
1902: }
1903: disconnectInternal();
1904: if (streaming()) {
1905: throw new HttpRetryException(RETRY_MSG3, stat, loc);
1906: }
1907:
1908: // clear out old response headers!!!!
1909: responses = new MessageHeader();
1910: if (stat == HTTP_USE_PROXY) {
1911: /* This means we must re-request the resource through the
1912: * proxy denoted in the "Location:" field of the response.
1913: * Judging by the spec, the string in the Location header
1914: * _should_ denote a URL - let's hope for "http://my.proxy.org"
1915: * Make a new HttpClient to the proxy, using HttpClient's
1916: * Instance-specific proxy fields, but note we're still fetching
1917: * the same URL.
1918: */
1919: String proxyHost = locUrl.getHost();
1920: int proxyPort = locUrl.getPort();
1921:
1922: SecurityManager security = System.getSecurityManager();
1923: if (security != null) {
1924: security.checkConnect(proxyHost, proxyPort);
1925: }
1926:
1927: setProxiedClient(url, proxyHost, proxyPort);
1928: requests.set(0, method + " " + http.getURLFile() + " "
1929: + httpVersion, null);
1930: connected = true;
1931: } else {
1932: // maintain previous headers, just change the name
1933: // of the file we're getting
1934: url = locUrl;
1935: if (method.equals("POST")
1936: && !Boolean.getBoolean("http.strictPostRedirect")
1937: && (stat != 307)) {
1938: /* The HTTP/1.1 spec says that a redirect from a POST
1939: * *should not* be immediately turned into a GET, and
1940: * that some HTTP/1.0 clients incorrectly did this.
1941: * Correct behavior redirects a POST to another POST.
1942: * Unfortunately, since most browsers have this incorrect
1943: * behavior, the web works this way now. Typical usage
1944: * seems to be:
1945: * POST a login code or passwd to a web page.
1946: * after validation, the server redirects to another
1947: * (welcome) page
1948: * The second request is (erroneously) expected to be GET
1949: *
1950: * We will do the incorrect thing (POST-->GET) by default.
1951: * We will provide the capability to do the "right" thing
1952: * (POST-->POST) by a system property, "http.strictPostRedirect=true"
1953: */
1954:
1955: requests = new MessageHeader();
1956: setRequests = false;
1957: setRequestMethod("GET");
1958: poster = null;
1959: if (!checkReuseConnection())
1960: connect();
1961: } else {
1962: if (!checkReuseConnection())
1963: connect();
1964: /* Even after a connect() call, http variable still can be
1965: * null, if a ResponseCache has been installed and it returns
1966: * a non-null CacheResponse instance. So check nullity before using it.
1967: *
1968: * And further, if http is null, there's no need to do anything
1969: * about request headers because successive http session will use
1970: * cachedInputStream/cachedHeaders anyway, which is returned by
1971: * CacheResponse.
1972: */
1973: if (http != null) {
1974: requests.set(0, method + " " + http.getURLFile()
1975: + " " + httpVersion, null);
1976: int port = url.getPort();
1977: String host = url.getHost();
1978: if (port != -1 && port != url.getDefaultPort()) {
1979: host += ":" + String.valueOf(port);
1980: }
1981: requests.set("Host", host);
1982: }
1983: }
1984: }
1985: return true;
1986: }
1987:
1988: /* dummy byte buffer for reading off socket prior to closing */
1989: byte[] cdata = new byte[128];
1990:
1991: /**
1992: * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance
1993: */
1994: private void reset() throws IOException {
1995: http.reuse = true;
1996: /* must save before calling close */
1997: reuseClient = http;
1998: InputStream is = http.getInputStream();
1999: if (!method.equals("HEAD")) {
2000: try {
2001: /* we want to read the rest of the response without using the
2002: * hurry mechanism, because that would close the connection
2003: * if everything is not available immediately
2004: */
2005: if ((is instanceof ChunkedInputStream)
2006: || (is instanceof MeteredStream)) {
2007: /* reading until eof will not block */
2008: while (is.read(cdata) > 0) {
2009: }
2010: } else {
2011: /* raw stream, which will block on read, so only read
2012: * the expected number of bytes, probably 0
2013: */
2014: int cl = 0, n = 0;
2015: try {
2016: cl = Integer.parseInt(responses
2017: .findValue("Content-Length"));
2018: } catch (Exception e) {
2019: }
2020: for (int i = 0; i < cl;) {
2021: if ((n = is.read(cdata)) == -1) {
2022: break;
2023: } else {
2024: i += n;
2025: }
2026: }
2027: }
2028: } catch (IOException e) {
2029: http.reuse = false;
2030: reuseClient = null;
2031: disconnectInternal();
2032: return;
2033: }
2034: try {
2035: if (is instanceof MeteredStream) {
2036: is.close();
2037: }
2038: } catch (IOException e) {
2039: }
2040: }
2041: responseCode = -1;
2042: responses = new MessageHeader();
2043: connected = false;
2044: }
2045:
2046: /**
2047: * Disconnect from the server (for internal use)
2048: */
2049: private void disconnectInternal() {
2050: responseCode = -1;
2051: if (pi != null) {
2052: pi.finishTracking();
2053: pi = null;
2054: }
2055: if (http != null) {
2056: http.closeServer();
2057: http = null;
2058: connected = false;
2059: }
2060: }
2061:
2062: /**
2063: * Disconnect from the server (public API)
2064: */
2065: public void disconnect() {
2066:
2067: responseCode = -1;
2068: if (pi != null) {
2069: pi.finishTracking();
2070: pi = null;
2071: }
2072:
2073: if (http != null) {
2074: /*
2075: * If we have an input stream this means we received a response
2076: * from the server. That stream may have been read to EOF and
2077: * dependening on the stream type may already be closed or the
2078: * the http client may be returned to the keep-alive cache.
2079: * If the http client has been returned to the keep-alive cache
2080: * it may be closed (idle timeout) or may be allocated to
2081: * another request.
2082: *
2083: * In other to avoid timing issues we close the input stream
2084: * which will either close the underlying connection or return
2085: * the client to the cache. If there's a possibility that the
2086: * client has been returned to the cache (ie: stream is a keep
2087: * alive stream or a chunked input stream) then we remove an
2088: * idle connection to the server. Note that this approach
2089: * can be considered an approximation in that we may close a
2090: * different idle connection to that used by the request.
2091: * Additionally it's possible that we close two connections
2092: * - the first becuase it wasn't an EOF (and couldn't be
2093: * hurried) - the second, another idle connection to the
2094: * same server. The is okay because "disconnect" is an
2095: * indication that the application doesn't intend to access
2096: * this http server for a while.
2097: */
2098:
2099: if (inputStream != null) {
2100: HttpClient hc = http;
2101:
2102: // un-synchronized
2103: boolean ka = hc.isKeepingAlive();
2104:
2105: try {
2106: inputStream.close();
2107: } catch (IOException ioe) {
2108: }
2109:
2110: // if the connection is persistent it may have been closed
2111: // or returned to the keep-alive cache. If it's been returned
2112: // to the keep-alive cache then we would like to close it
2113: // but it may have been allocated
2114:
2115: if (ka) {
2116: hc.closeIdleConnection();
2117: }
2118:
2119: } else {
2120: // We are deliberatly being disconnected so HttpClient
2121: // should not try to resend the request no matter what stage
2122: // of the connection we are in.
2123: http.setDoNotRetry(true);
2124:
2125: http.closeServer();
2126: }
2127:
2128: // poster = null;
2129: http = null;
2130: connected = false;
2131: }
2132: cachedInputStream = null;
2133: if (cachedHeaders != null) {
2134: cachedHeaders.reset();
2135: }
2136: }
2137:
2138: public boolean usingProxy() {
2139: if (http != null) {
2140: return (http.getProxyHostUsed() != null);
2141: }
2142: return false;
2143: }
2144:
2145: /**
2146: * Gets a header field by name. Returns null if not known.
2147: * @param name the name of the header field
2148: */
2149: public String getHeaderField(String name) {
2150: try {
2151: getInputStream();
2152: } catch (IOException e) {
2153: }
2154:
2155: if (cachedHeaders != null) {
2156: return cachedHeaders.findValue(name);
2157: }
2158:
2159: return responses.findValue(name);
2160: }
2161:
2162: /**
2163: * Returns an unmodifiable Map of the header fields.
2164: * The Map keys are Strings that represent the
2165: * response-header field names. Each Map value is an
2166: * unmodifiable List of Strings that represents
2167: * the corresponding field values.
2168: *
2169: * @return a Map of header fields
2170: * @since 1.4
2171: */
2172: public Map getHeaderFields() {
2173: try {
2174: getInputStream();
2175: } catch (IOException e) {
2176: }
2177:
2178: if (cachedHeaders != null) {
2179: return cachedHeaders.getHeaders();
2180: }
2181:
2182: return responses.getHeaders();
2183: }
2184:
2185: /**
2186: * Gets a header field by index. Returns null if not known.
2187: * @param n the index of the header field
2188: */
2189: public String getHeaderField(int n) {
2190: try {
2191: getInputStream();
2192: } catch (IOException e) {
2193: }
2194:
2195: if (cachedHeaders != null) {
2196: return cachedHeaders.getValue(n);
2197: }
2198: return responses.getValue(n);
2199: }
2200:
2201: /**
2202: * Gets a header field by index. Returns null if not known.
2203: * @param n the index of the header field
2204: */
2205: public String getHeaderFieldKey(int n) {
2206: try {
2207: getInputStream();
2208: } catch (IOException e) {
2209: }
2210:
2211: if (cachedHeaders != null) {
2212: return cachedHeaders.getKey(n);
2213: }
2214:
2215: return responses.getKey(n);
2216: }
2217:
2218: /**
2219: * Sets request property. If a property with the key already
2220: * exists, overwrite its value with the new value.
2221: * @param value the value to be set
2222: */
2223: public void setRequestProperty(String key, String value) {
2224: if (connected)
2225: throw new IllegalStateException("Already connected");
2226: if (key == null)
2227: throw new NullPointerException("key is null");
2228:
2229: checkMessageHeader(key, value);
2230: requests.set(key, value);
2231: }
2232:
2233: /**
2234: * Adds a general request property specified by a
2235: * key-value pair. This method will not overwrite
2236: * existing values associated with the same key.
2237: *
2238: * @param key the keyword by which the request is known
2239: * (e.g., "<code>accept</code>").
2240: * @param value the value associated with it.
2241: * @see #getRequestProperties(java.lang.String)
2242: * @since 1.4
2243: */
2244: public void addRequestProperty(String key, String value) {
2245: if (connected)
2246: throw new IllegalStateException("Already connected");
2247: if (key == null)
2248: throw new NullPointerException("key is null");
2249:
2250: checkMessageHeader(key, value);
2251: requests.add(key, value);
2252: }
2253:
2254: //
2255: // Set a property for authentication. This can safely disregard
2256: // the connected test.
2257: //
2258: void setAuthenticationProperty(String key, String value) {
2259: checkMessageHeader(key, value);
2260: requests.set(key, value);
2261: }
2262:
2263: public String getRequestProperty(String key) {
2264: // don't return headers containing security sensitive information
2265: if (key != null) {
2266: for (int i = 0; i < EXCLUDE_HEADERS.length; i++) {
2267: if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
2268: return null;
2269: }
2270: }
2271: }
2272: return requests.findValue(key);
2273: }
2274:
2275: /**
2276: * Returns an unmodifiable Map of general request
2277: * properties for this connection. The Map keys
2278: * are Strings that represent the request-header
2279: * field names. Each Map value is a unmodifiable List
2280: * of Strings that represents the corresponding
2281: * field values.
2282: *
2283: * @return a Map of the general request properties for this connection.
2284: * @throws IllegalStateException if already connected
2285: * @since 1.4
2286: */
2287: public Map getRequestProperties() {
2288: if (connected)
2289: throw new IllegalStateException("Already connected");
2290:
2291: // exclude headers containing security-sensitive info
2292: return requests.getHeaders(EXCLUDE_HEADERS);
2293: }
2294:
2295: public void setConnectTimeout(int timeout) {
2296: if (timeout < 0)
2297: throw new IllegalArgumentException(
2298: "timeouts can't be negative");
2299: connectTimeout = timeout;
2300: }
2301:
2302: /**
2303: * Returns setting for connect timeout.
2304: * <p>
2305: * 0 return implies that the option is disabled
2306: * (i.e., timeout of infinity).
2307: *
2308: * @return an <code>int</code> that indicates the connect timeout
2309: * value in milliseconds
2310: * @see java.net.URLConnection#setConnectTimeout(int)
2311: * @see java.net.URLConnection#connect()
2312: * @since 1.5
2313: */
2314: public int getConnectTimeout() {
2315: return (connectTimeout < 0 ? 0 : connectTimeout);
2316: }
2317:
2318: /**
2319: * Sets the read timeout to a specified timeout, in
2320: * milliseconds. A non-zero value specifies the timeout when
2321: * reading from Input stream when a connection is established to a
2322: * resource. If the timeout expires before there is data available
2323: * for read, a java.net.SocketTimeoutException is raised. A
2324: * timeout of zero is interpreted as an infinite timeout.
2325: *
2326: * <p> Some non-standard implementation of this method ignores the
2327: * specified timeout. To see the read timeout set, please call
2328: * getReadTimeout().
2329: *
2330: * @param timeout an <code>int</code> that specifies the timeout
2331: * value to be used in milliseconds
2332: * @throws IllegalArgumentException if the timeout parameter is negative
2333: *
2334: * @see java.net.URLConnectiongetReadTimeout()
2335: * @see java.io.InputStream#read()
2336: * @since 1.5
2337: */
2338: public void setReadTimeout(int timeout) {
2339: if (timeout < 0)
2340: throw new IllegalArgumentException(
2341: "timeouts can't be negative");
2342: readTimeout = timeout;
2343: }
2344:
2345: /**
2346: * Returns setting for read timeout. 0 return implies that the
2347: * option is disabled (i.e., timeout of infinity).
2348: *
2349: * @return an <code>int</code> that indicates the read timeout
2350: * value in milliseconds
2351: *
2352: * @see java.net.URLConnection#setReadTimeout(int)
2353: * @see java.io.InputStream#read()
2354: * @since 1.5
2355: */
2356: public int getReadTimeout() {
2357: return readTimeout < 0 ? 0 : readTimeout;
2358: }
2359:
2360: protected void finalize() {
2361: // this should do nothing. The stream finalizer will close
2362: // the fd
2363: }
2364:
2365: String getMethod() {
2366: return method;
2367: }
2368:
2369: private MessageHeader mapToMessageHeader(Map map) {
2370: MessageHeader headers = new MessageHeader();
2371: if (map == null || map.isEmpty()) {
2372: return headers;
2373: }
2374: Set entries = map.entrySet();
2375: Iterator itr1 = entries.iterator();
2376: while (itr1.hasNext()) {
2377: Map.Entry entry = (Map.Entry) itr1.next();
2378: String key = (String) entry.getKey();
2379: List values = (List) entry.getValue();
2380: Iterator itr2 = values.iterator();
2381: while (itr2.hasNext()) {
2382: String value = (String) itr2.next();
2383: if (key == null) {
2384: headers.prepend(key, value);
2385: } else {
2386: headers.add(key, value);
2387: }
2388: }
2389: }
2390: return headers;
2391: }
2392:
2393: /* The purpose of this wrapper is just to capture the close() call
2394: * so we can check authentication information that may have
2395: * arrived in a Trailer field
2396: */
2397: class HttpInputStream extends FilterInputStream {
2398: private CacheRequest cacheRequest;
2399: private OutputStream outputStream;
2400: private boolean marked = false;
2401: private int inCache = 0;
2402: private int markCount = 0;
2403:
2404: public HttpInputStream(InputStream is) {
2405: super (is);
2406: this .cacheRequest = null;
2407: this .outputStream = null;
2408: }
2409:
2410: public HttpInputStream(InputStream is, CacheRequest cacheRequest) {
2411: super (is);
2412: this .cacheRequest = cacheRequest;
2413: try {
2414: this .outputStream = cacheRequest.getBody();
2415: } catch (IOException ioex) {
2416: this .cacheRequest.abort();
2417: this .cacheRequest = null;
2418: this .outputStream = null;
2419: }
2420: }
2421:
2422: /**
2423: * Marks the current position in this input stream. A subsequent
2424: * call to the <code>reset</code> method repositions this stream at
2425: * the last marked position so that subsequent reads re-read the same
2426: * bytes.
2427: * <p>
2428: * The <code>readlimit</code> argument tells this input stream to
2429: * allow that many bytes to be read before the mark position gets
2430: * invalidated.
2431: * <p>
2432: * This method simply performs <code>in.mark(readlimit)</code>.
2433: *
2434: * @param readlimit the maximum limit of bytes that can be read before
2435: * the mark position becomes invalid.
2436: * @see java.io.FilterInputStream#in
2437: * @see java.io.FilterInputStream#reset()
2438: */
2439: public synchronized void mark(int readlimit) {
2440: super .mark(readlimit);
2441: if (cacheRequest != null) {
2442: marked = true;
2443: markCount = 0;
2444: }
2445: }
2446:
2447: /**
2448: * Repositions this stream to the position at the time the
2449: * <code>mark</code> method was last called on this input stream.
2450: * <p>
2451: * This method
2452: * simply performs <code>in.reset()</code>.
2453: * <p>
2454: * Stream marks are intended to be used in
2455: * situations where you need to read ahead a little to see what's in
2456: * the stream. Often this is most easily done by invoking some
2457: * general parser. If the stream is of the type handled by the
2458: * parse, it just chugs along happily. If the stream is not of
2459: * that type, the parser should toss an exception when it fails.
2460: * If this happens within readlimit bytes, it allows the outer
2461: * code to reset the stream and try another parser.
2462: *
2463: * @exception IOException if the stream has not been marked or if the
2464: * mark has been invalidated.
2465: * @see java.io.FilterInputStream#in
2466: * @see java.io.FilterInputStream#mark(int)
2467: */
2468: public synchronized void reset() throws IOException {
2469: super .reset();
2470: if (cacheRequest != null) {
2471: marked = false;
2472: inCache += markCount;
2473: }
2474: }
2475:
2476: public int read() throws IOException {
2477: try {
2478: byte[] b = new byte[1];
2479: int ret = read(b);
2480: return (ret == -1 ? ret : (b[0] & 0x00FF));
2481: } catch (IOException ioex) {
2482: if (cacheRequest != null) {
2483: cacheRequest.abort();
2484: }
2485: throw ioex;
2486: }
2487: }
2488:
2489: public int read(byte[] b) throws IOException {
2490: return read(b, 0, b.length);
2491: }
2492:
2493: public int read(byte[] b, int off, int len) throws IOException {
2494: try {
2495: int newLen = super .read(b, off, len);
2496: int nWrite;
2497: // write to cache
2498: if (inCache > 0) {
2499: if (inCache >= newLen) {
2500: inCache -= newLen;
2501: nWrite = 0;
2502: } else {
2503: nWrite = newLen - inCache;
2504: inCache = 0;
2505: }
2506: } else {
2507: nWrite = newLen;
2508: }
2509: if (nWrite > 0 && outputStream != null)
2510: outputStream.write(b, off + (newLen - nWrite),
2511: nWrite);
2512: if (marked) {
2513: markCount += newLen;
2514: }
2515: return newLen;
2516: } catch (IOException ioex) {
2517: if (cacheRequest != null) {
2518: cacheRequest.abort();
2519: }
2520: throw ioex;
2521: }
2522: }
2523:
2524: public void close() throws IOException {
2525: try {
2526: if (outputStream != null) {
2527: if (read() != -1) {
2528: cacheRequest.abort();
2529: } else {
2530: outputStream.close();
2531: }
2532: }
2533: super .close();
2534: } catch (IOException ioex) {
2535: if (cacheRequest != null) {
2536: cacheRequest.abort();
2537: }
2538: throw ioex;
2539: } finally {
2540: HttpURLConnection.this .http = null;
2541: checkResponseCredentials(true);
2542: }
2543: }
2544: }
2545:
2546: class StreamingOutputStream extends FilterOutputStream {
2547:
2548: int expected;
2549: int written;
2550: boolean closed;
2551: boolean error;
2552: IOException errorExcp;
2553:
2554: /**
2555: * expectedLength == -1 if the stream is chunked
2556: * expectedLength > 0 if the stream is fixed content-length
2557: * In the 2nd case, we make sure the expected number of
2558: * of bytes are actually written
2559: */
2560: StreamingOutputStream(OutputStream os, int expectedLength) {
2561: super (os);
2562: expected = expectedLength;
2563: written = 0;
2564: closed = false;
2565: error = false;
2566: }
2567:
2568: public void write(int b) throws IOException {
2569: checkError();
2570: written++;
2571: if (expected != -1 && written > expected) {
2572: throw new IOException("too many bytes written");
2573: }
2574: out.write(b);
2575: }
2576:
2577: public void write(byte[] b) throws IOException {
2578: write(b, 0, b.length);
2579: }
2580:
2581: public void write(byte[] b, int off, int len)
2582: throws IOException {
2583: checkError();
2584: written += len;
2585: if (expected != -1 && written > expected) {
2586: out.close();
2587: throw new IOException("too many bytes written");
2588: }
2589: out.write(b, off, len);
2590: }
2591:
2592: void checkError() throws IOException {
2593: if (closed) {
2594: throw new IOException("Stream is closed");
2595: }
2596: if (error) {
2597: throw errorExcp;
2598: }
2599: if (((PrintStream) out).checkError()) {
2600: throw new IOException(
2601: "Error writing request body to server");
2602: }
2603: }
2604:
2605: /* this is called to check that all the bytes
2606: * that were supposed to be written were written
2607: * and that the stream is now closed().
2608: */
2609: boolean writtenOK() {
2610: return closed && !error;
2611: }
2612:
2613: public void close() throws IOException {
2614: if (closed) {
2615: return;
2616: }
2617: closed = true;
2618: if (expected != -1) {
2619: /* not chunked */
2620: if (written != expected) {
2621: error = true;
2622: errorExcp = new IOException(
2623: "insufficient data written");
2624: out.close();
2625: throw errorExcp;
2626: }
2627: super .flush(); /* can't close the socket */
2628: } else {
2629: /* chunked */
2630: super .close(); /* force final chunk to be written */
2631: /* trailing \r\n */
2632: OutputStream o = http.getOutputStream();
2633: o.write('\r');
2634: o.write('\n');
2635: o.flush();
2636: }
2637: }
2638: }
2639:
2640: static class ErrorStream extends InputStream {
2641: ByteBuffer buffer;
2642: InputStream is;
2643:
2644: private ErrorStream(ByteBuffer buf) {
2645: buffer = buf;
2646: is = null;
2647: }
2648:
2649: private ErrorStream(ByteBuffer buf, InputStream is) {
2650: buffer = buf;
2651: this .is = is;
2652: }
2653:
2654: // when this method is called, it's either the case that cl > 0, or
2655: // if chunk-encoded, cl = -1; in other words, cl can't be 0
2656: public static InputStream getErrorStream(InputStream is,
2657: int cl, HttpClient http) {
2658:
2659: // cl can't be 0; this following is here for extra precaution
2660: if (cl == 0) {
2661: return null;
2662: }
2663:
2664: try {
2665: // set SO_TIMEOUT to 1/5th of the total timeout
2666: // remember the old timeout value so that we can restore it
2667: int oldTimeout = http.setTimeout(timeout4ESBuffer / 5);
2668:
2669: int expected = 0;
2670: // the chunked case
2671: if (cl < 0) {
2672: expected = bufSize4ES;
2673: } else {
2674: expected = cl;
2675: }
2676: if (expected <= bufSize4ES) {
2677: byte[] buffer = new byte[expected];
2678: int count = 0, time = 0, len = 0;
2679: do {
2680: try {
2681: len = is.read(buffer, count, buffer.length
2682: - count);
2683: if (len < 0) {
2684: if (cl < 0) {
2685: // chunked ended
2686: // if chunked ended prematurely,
2687: // an IOException would be thrown
2688: break;
2689: }
2690: // the server sends less than cl bytes of data
2691: throw new IOException(
2692: "the server closes"
2693: + " before sending "
2694: + cl + " bytes of data");
2695: }
2696: count += len;
2697: } catch (SocketTimeoutException ex) {
2698: time += timeout4ESBuffer / 5;
2699: }
2700: } while (count < expected
2701: && time < timeout4ESBuffer);
2702:
2703: // reset SO_TIMEOUT to old value
2704: http.setTimeout(oldTimeout);
2705:
2706: // if count < cl at this point, we will not try to reuse
2707: // the connection
2708: if (count == 0) {
2709: // since we haven't read anything,
2710: // we will return the underlying
2711: // inputstream back to the application
2712: return null;
2713: } else if (count == expected || (cl < 0 && len < 0)) {
2714: // put the connection into keep-alive cache
2715: // the inputstream will try to do the right thing
2716: is.close();
2717: return new ErrorStream(ByteBuffer.wrap(buffer,
2718: 0, count));
2719: } else {
2720: // we read part of the response body
2721: return new ErrorStream(ByteBuffer.wrap(buffer,
2722: 0, count), is);
2723: }
2724: }
2725: return null;
2726: } catch (IOException ioex) {
2727: // ioex.printStackTrace();
2728: return null;
2729: }
2730: }
2731:
2732: public int available() throws IOException {
2733: if (is == null) {
2734: return buffer.remaining();
2735: } else {
2736: return buffer.remaining() + is.available();
2737: }
2738: }
2739:
2740: public int read() throws IOException {
2741: byte[] b = new byte[1];
2742: int ret = read(b);
2743: return (ret == -1 ? ret : (b[0] & 0x00FF));
2744: }
2745:
2746: public int read(byte[] b) throws IOException {
2747: return read(b, 0, b.length);
2748: }
2749:
2750: public int read(byte[] b, int off, int len) throws IOException {
2751: int rem = buffer.remaining();
2752: if (rem > 0) {
2753: int ret = rem < len ? rem : len;
2754: buffer.get(b, off, ret);
2755: return ret;
2756: } else {
2757: if (is == null) {
2758: return -1;
2759: } else {
2760: return is.read(b, off, len);
2761: }
2762: }
2763: }
2764:
2765: public void close() throws IOException {
2766: buffer = null;
2767: if (is != null) {
2768: is.close();
2769: }
2770: }
2771: }
2772: }
2773:
2774: /** An input stream that just returns EOF. This is for
2775: * HTTP URLConnections that are KeepAlive && use the
2776: * HEAD method - i.e., stream not dead, but nothing to be read.
2777: */
2778:
2779: class EmptyInputStream extends InputStream {
2780:
2781: public int available() {
2782: return 0;
2783: }
2784:
2785: public int read() {
2786: return -1;
2787: }
2788: }
|