/* * @(#)HttpURLConnection.java 1.86 06/10/10 * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ package sun.net.www.protocol.http; import java.net.URL; import java.net.URLConnection; import java.net.ProtocolException; import java.net.PasswordAuthentication; import java.net.Authenticator; import java.net.InetAddress; import java.net.UnknownHostException; import java.net.SocketTimeoutException; import java.io.*; import java.util.Date; import java.util.Map; import java.util.Locale; import java.util.StringTokenizer; import sun.net.*; import sun.net.www.*; import sun.net.www.http.HttpClient; import sun.net.www.http.PosterOutputStream; import sun.net.www.http.ChunkedInputStream; import java.text.SimpleDateFormat; import java.util.TimeZone; import java.net.MalformedURLException; import sun.misc.NetworkMetrics; import sun.misc.NetworkMetricsInf; /** * A class to represent an HTTP connection to a remote object. */ public class HttpURLConnection extends java.net.HttpURLConnection { static final String version; public static final String userAgent; /* max # of allowed re-directs */ static final int defaultmaxRedirects = 20; static final int maxRedirects; /* Not all servers support the (Proxy)-Authentication-Info headers. * By default, we don't require them to be sent */ static final boolean validateProxy; static final boolean validateServer; static { maxRedirects = ((Integer)java.security.AccessController.doPrivileged( new sun.security.action.GetIntegerAction("http.maxRedirects", defaultmaxRedirects))).intValue(); version = (String) java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("java.version")); String agent = (String) java.security.AccessController.doPrivileged( new sun.security.action.GetPropertyAction("http.agent")); if (agent == null) { agent = "Java/"+version; } else { agent = agent + " Java/"+version; } userAgent = agent; validateProxy = ((Boolean)java.security.AccessController.doPrivileged( new sun.security.action.GetBooleanAction( "http.auth.digest.validateProxy"))).booleanValue(); validateServer = ((Boolean)java.security.AccessController.doPrivileged( new sun.security.action.GetBooleanAction( "http.auth.digest.validateServer"))).booleanValue(); } static final String httpVersion = "HTTP/1.1"; static final String acceptString = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"; // the following http request headers should NOT have their values // returned for security reasons. private static final String[] EXCLUDE_HEADERS = { "Proxy-Authorization", "Authorization" }; protected HttpClient http; protected Handler handler; /* output stream to server */ protected PrintStream ps = null; /* We only have a single static authenticator for now. * For backwards compatibility with JDK 1.1. Should be * eliminated for JDK 2.0. */ private static HttpAuthenticator defaultAuth; /* all the headers we send * NOTE: do *NOT* dump out the content of 'requests' in the * output or stacktrace since it may contain security-sensitive * headers such as those defined in EXCLUDE_HEADERS. */ private MessageHeader requests; /* The following two fields are only used with Digest Authentication */ String domain; /* The list of authentication domains */ DigestAuthentication.Parameters digestparams; /* Current credentials in use */ AuthenticationInfo currentProxyCredentials = null; AuthenticationInfo currentServerCredentials = null; boolean needToCheck = true; private boolean doingNTLM2ndStage = false; /* doing the 2nd stage of an NTLM server authentication */ private boolean doingNTLMp2ndStage = false; /* doing the 2nd stage of an NTLM proxy authentication */ Object authObj; /* Progress entry */ protected ProgressEntry pe; /* all the response headers we get back */ private MessageHeader responses; /* the stream _from_ the server */ private InputStream inputStream = null; /* post stream _to_ the server, if any */ private PosterOutputStream poster = null; /* Indicates if the std. request headers have been set in requests. */ private boolean setRequests=false; /* Indicates whether a request has already failed or not */ private boolean failedOnce=false; /* Remembered Exception, we will throw it again if somebody calls getInputStream after disconnect */ private Exception rememberedException = null; /* If we decide we want to reuse a client, we put it here */ private HttpClient reuseClient = null; protected int netMetricCode = 0; protected NetworkMetricsInf nm; private boolean sentMetric = false; /* * privileged request password authentication * */ private static PasswordAuthentication privilegedRequestPasswordAuthentication( final String host, final InetAddress addr, final int port, final String protocol, final String prompt, final String scheme) { return (PasswordAuthentication) java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { return Authenticator.requestPasswordAuthentication( host, addr, port, protocol, prompt, scheme); } }); } /* * checks the validity of http message header and throws * IllegalArgumentException if invalid. */ private void checkMessageHeader(String key, String value) { char LF = '\n'; int index = key.indexOf(LF); if (index != -1) { throw new IllegalArgumentException( "Illegal character(s) in message header field: " + key); } else { if (value == null) { return; } index = value.indexOf(LF); while (index != -1) { index++; if (index < value.length()) { char c = value.charAt(index); if ((c==' ') || (c=='\t')) { // ok, check the next occurrence index = value.indexOf(LF, index); continue; } } throw new IllegalArgumentException( "Illegal character(s) in message header value: " + value); } } } /* adds the standard key/val pairs to reqests if necessary & write to * given PrintStream */ private void writeRequests() throws IOException { /* print all message headers in the MessageHeader * onto the wire - all the ones we've set and any * others that have been set */ if (!setRequests) { /* We're very particular about the order in which we * set the request headers here. The order should not * matter, but some careless CGI programs have been * written to expect a very particular order of the * standard headers. To name names, the order in which * Navigator3.0 sends them. In particular, we make *sure* * to send Content-type: <> and Content-length:<> second * to last and last, respectively, in the case of a POST * request. */ if (!failedOnce) requests.prepend(method + " " + http.getURLFile()+" " + httpVersion, null); if (!getUseCaches()) { requests.setIfNotSet ("Cache-Control", "no-cache"); requests.setIfNotSet ("Pragma", "no-cache"); } requests.setIfNotSet("User-Agent", userAgent); int port = url.getPort(); String host = url.getHost(); if (port != -1 && port != 80) { host += ":" + String.valueOf(port); } requests.setIfNotSet("Host", host); requests.setIfNotSet("Accept", acceptString); /* * For HTTP/1.1 the default behavior is to keep connections alive. * However, we may be talking to a 1.0 server so we should set * keep-alive just in case, except if we have encountered an error * or if keep alive is disabled via a system property */ // Try keep-alive only on first attempt if (!failedOnce && http.getHttpKeepAliveSet()) { if (http.usingProxy) { requests.setIfNotSet("Proxy-Connection", "keep-alive"); } else { requests.setIfNotSet("Connection", "keep-alive"); } } // send any pre-emptive authentication if (http.usingProxy) { setPreemptiveProxyAuthentication(requests); } // Set modified since if necessary long modTime = getIfModifiedSince(); if (modTime != 0 ) { Date date = new Date(modTime); //use the preferred date format according to RFC 2068(HTTP1.1), // RFC 822 and RFC 1123 SimpleDateFormat fo = new SimpleDateFormat ("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); fo.setTimeZone(TimeZone.getTimeZone("GMT")); requests.setIfNotSet("If-Modified-Since", fo.format(date)); } // check for preemptive authorization AuthenticationInfo sauth = AuthenticationInfo.getServerAuth(url); if (sauth != null && sauth.supportsPreemptiveAuthorization() ) { // Sets "Authorization" requests.setIfNotSet(sauth.getHeaderName(), sauth.getHeaderValue(url,method)); currentServerCredentials = sauth; } if (poster != null) { /* add Content-Length & POST/PUT data */ synchronized (poster) { /* close it, so no more data can be added */ poster.close(); if (!method.equals("PUT")) { String type = "application/x-www-form-urlencoded"; requests.setIfNotSet("Content-Type", type); } requests.set("Content-Length", String.valueOf(poster.size())); } } setRequests=true; } final int bytesWritten = http.writeRequests(requests, poster); java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { public Object run() { if (NetworkMetrics.metricsAvailable()) { int methodType = (method.equals("POST") ? NetworkMetricsInf.POST : method.equals("HEAD") ? NetworkMetricsInf.HEAD : NetworkMetricsInf.GET); Class nmClass = NetworkMetrics.getImpl(); if (nmClass == null) { return null; } try { nm = (NetworkMetricsInf) nmClass.newInstance(); } catch (Exception e) { return null; } nm.initReq(NetworkMetricsInf.HTTP, getURL()); nm.sendMetricReq(http.getServerSocket(), methodType, bytesWritten); sentMetric = true; } return null; } }); if (ps.checkError()) { String proxyHost = http.getProxyHostUsed(); int proxyPort = http.getProxyPortUsed(); disconnectInternal(); if (failedOnce) { throw new IOException("Error writing to server"); } else { // try once more failedOnce=true; if (proxyHost != null) { setProxiedClient(url, proxyHost, proxyPort); } else { setNewClient (url); } ps = (PrintStream) http.getOutputStream(); connected=true; responses = new MessageHeader(); setRequests=false; writeRequests(); } } } /** * Create a new HttpClient object, bypassing the cache of * HTTP client objects/connections. * * @param url the URL being accessed */ protected void setNewClient (URL url) throws IOException { setNewClient(url, false); } /** * Obtain a HttpsClient object. Use the cached copy if specified. * * @param url the URL being accessed * @param useCache whether the cached connection should be used * if present */ protected void setNewClient (URL url, boolean useCache) throws IOException { http = HttpClient.New(url, useCache); } /** * Create a new HttpClient object, set up so that it uses * per-instance proxying to the given HTTP proxy. This * bypasses the cache of HTTP client objects/connections. * * @param url the URL being accessed * @param proxyHost the proxy host to use * @param proxyPort the proxy port to use */ protected void setProxiedClient (URL url, String proxyHost, int proxyPort) throws IOException { setProxiedClient(url, proxyHost, proxyPort, false); } /** * Obtain a HttpClient object, set up so that it uses per-instance * proxying to the given HTTP proxy. Use the cached copy of HTTP * client objects/connections if specified. * * @param url the URL being accessed * @param proxyHost the proxy host to use * @param proxyPort the proxy port to use * @param useCache whether the cached connection should be used * if present */ protected void setProxiedClient (URL url, String proxyHost, int proxyPort, boolean useCache) throws IOException { proxiedConnect(url, proxyHost, proxyPort, useCache); } protected void proxiedConnect(URL url, String proxyHost, int proxyPort, boolean useCache) throws IOException { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkConnect(proxyHost, proxyPort); } http = HttpClient.New (url, proxyHost, proxyPort, useCache); } protected HttpURLConnection(URL u, Handler handler) throws IOException { super(u); requests = new MessageHeader(); responses = new MessageHeader(); this.handler = handler; } /** this constructor is used by other protocol handlers such as ftp that want to use http to fetch urls on their behalf. */ public HttpURLConnection(URL u, String host, int port) throws IOException { this(u, new Handler(host, port)); } /** * @deprecated. Use java.net.Authenticator.setDefault() instead. */ public static void setDefaultAuthenticator(HttpAuthenticator a) { defaultAuth = a; } /** * opens a stream allowing redirects only to the same host. */ public static InputStream openConnectionCheckRedirects(URLConnection c) throws IOException { boolean redir; int redirects = 0; InputStream in = null; do { if (c instanceof HttpURLConnection) { ((HttpURLConnection) c).setInstanceFollowRedirects(false); } // We want to open the input stream before // getting headers, because getHeaderField() // et al swallow IOExceptions. in = c.getInputStream(); redir = false; if (c instanceof HttpURLConnection) { HttpURLConnection http = (HttpURLConnection) c; int stat = http.getResponseCode(); if (stat >= 300 && stat <= 307 && stat != 306 && stat != HttpURLConnection.HTTP_NOT_MODIFIED) { URL base = http.getURL(); String loc = http.getHeaderField("Location"); URL target = null; if (loc != null) { target = new URL(base, loc); } http.disconnect(); if (target == null || !base.getProtocol().equals(target.getProtocol()) || base.getPort() != target.getPort() || !hostsEqual(base, target) || redirects >= 5) { throw new SecurityException("illegal URL redirect"); } redir = true; c = target.openConnection(); redirects++; } } } while (redir); return in; } // // Same as java.net.URL.hostsEqual // private static boolean hostsEqual(URL u1, URL u2) { final String h1 = u1.getHost(); final String h2 = u2.getHost(); if (h1 == null) { return h2 == null; } else if (h2 == null) { return false; } else if (h1.equalsIgnoreCase(h2)) { return true; } // Have to resolve addresses before comparing, otherwise // names like tachyon and tachyon.eng would compare different final boolean result[] = {false}; java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { try { InetAddress a1 = InetAddress.getByName(h1); InetAddress a2 = InetAddress.getByName(h2); result[0] = a1.equals(a2); } catch(UnknownHostException e) { } catch(SecurityException e) { } return null; } }); return result[0]; } // overridden in HTTPS subclass public void connect() throws IOException { plainConnect(); } private boolean checkReuseConnection () { if (connected) { return true; } if (reuseClient != null) { http = reuseClient; http.reuse = false; reuseClient = null; connected = true; return true; } return false; } protected void plainConnect() throws IOException { if (connected) { return; } try { if ("http".equals(url.getProtocol()) && !failedOnce) { http = HttpClient.New(url); } else { // make sure to construct new connection if first // attempt failed http = new HttpClient(url, handler.proxy, handler.proxyPort); } ps = (PrintStream)http.getOutputStream(); } catch (IOException e) { throw e; } // constructor to HTTP client calls openserver connected = true; } /* * Allowable input/output sequences: * [interpreted as POST/PUT] * - get output, [write output,] get input, [read input] * - get output, [write output] * [interpreted as GET] * - get input, [read input] * Disallowed: * - get input, [read input,] get output, [write output] */ public synchronized OutputStream getOutputStream() throws IOException { try { if (!doOutput) { throw new ProtocolException("cannot write to a URLConnection" + " if doOutput=false - call setDoOutput(true)"); } if (method.equals("GET")) { method = "POST"; // Backward compatibility } if (!"POST".equals(method) && !"PUT".equals(method) && "http".equals(url.getProtocol())) { throw new ProtocolException("HTTP method " + method + " doesn't support output"); } // if there's already an input stream open, throw an exception if (inputStream != null) { throw new ProtocolException("Cannot write output after reading input."); } if (!checkReuseConnection()) connect(); /* This exists to fix the HttpsURLConnection subclass. * Hotjava needs to run on JDK1.1. Do proper fix in subclass * for 1.2 and remove this. */ ps = (PrintStream)http.getOutputStream(); if (poster == null) poster = new PosterOutputStream(); return poster; } catch (RuntimeException e) { disconnectInternal(); throw e; } catch (IOException e) { disconnectInternal(); throw e; } } public synchronized InputStream getInputStream() throws IOException { if (!doInput) { throw new ProtocolException("Cannot read from URLConnection" + " if doInput=false (call setDoInput(true))"); } if (rememberedException != null) { if (rememberedException instanceof RuntimeException) throw new RuntimeException(rememberedException); else { IOException exception; try { exception = new IOException(); exception.initCause(rememberedException); } catch (Exception t) { exception = (IOException) rememberedException; } throw exception; } } if (inputStream != null) { return inputStream; } int redirects = 0; int respCode = 0; AuthenticationInfo serverAuthentication = null; AuthenticationInfo proxyAuthentication = null; AuthenticationHeader srvHdr = null; try { do { pe = new ProgressEntry(url.getFile(), null); ProgressData.pdata.register(pe); if (!checkReuseConnection()) connect(); /* This exists to fix the HttpsURLConnection subclass. * Hotjava needs to run on JDK1.1. Do proper fix once a * proper solution for SSL can be found. */ ps = (PrintStream)http.getOutputStream(); writeRequests(); http.parseHTTP(responses, pe); inputStream = new HttpInputStream (http.getInputStream()); respCode = netMetricCode = getResponseCode(); if (respCode == HTTP_PROXY_AUTH) { AuthenticationHeader authhdr = new AuthenticationHeader ( "Proxy-Authenticate", responses ); if (!doingNTLMp2ndStage) { proxyAuthentication = resetProxyAuthentication(proxyAuthentication, authhdr); if (proxyAuthentication != null) { redirects++; disconnectInternal(); continue; } } else { /* in this case, only one header field will be present */ String raw = responses.findValue ("Proxy-Authenticate"); reset (); if (!proxyAuthentication.setHeaders(this, authhdr.headerParser(), raw)) { disconnectInternal(); throw new IOException ("Authentication failure"); } if (serverAuthentication != null && srvHdr != null && !serverAuthentication.setHeaders(this, srvHdr.headerParser(), raw)) { disconnectInternal (); throw new IOException ("Authentication failure"); } authObj = null; doingNTLMp2ndStage = false; continue; } } // cache proxy authentication info if (proxyAuthentication != null) { // cache auth info on success, domain header not relevant. proxyAuthentication.addToCache(); } if (respCode == HTTP_UNAUTHORIZED) { srvHdr = new AuthenticationHeader ( "WWW-Authenticate", responses ); String raw = srvHdr.raw(); if (!doingNTLM2ndStage) { if (serverAuthentication != null) { if (serverAuthentication.isAuthorizationStale (raw)) { /* we can retry with the current credentials */ disconnectInternal(); redirects++; requests.set(serverAuthentication.getHeaderName(), serverAuthentication.getHeaderValue(url, method)); currentServerCredentials = serverAuthentication; continue; } else { serverAuthentication.removeFromCache(); } } serverAuthentication = getServerAuthentication(srvHdr); currentServerCredentials = serverAuthentication; if (serverAuthentication != null) { disconnectInternal(); redirects++; // don't let things loop ad nauseum continue; } } else { reset (); /* header not used for ntlm */ if (!serverAuthentication.setHeaders(this, null, raw)) { disconnectInternal(); throw new IOException ("Authentication failure"); } doingNTLM2ndStage = false; authObj = null; continue; } } // cache server authentication info if (serverAuthentication != null) { // cache auth info on success if (!(serverAuthentication instanceof DigestAuthentication) || (domain == null)) { if (serverAuthentication instanceof BasicAuthentication) { // check if the path is shorter than the existing entry String npath = AuthenticationInfo.reducePath (url.getPath()); String opath = serverAuthentication.path; if (!opath.startsWith (npath) || npath.length() >= opath.length()) { /* npath is longer, there must be a common root */ npath = BasicAuthentication.getRootPath (opath, npath); } // remove the entry and create a new one BasicAuthentication a = (BasicAuthentication) serverAuthentication.clone(); serverAuthentication.removeFromCache(); a.path = npath; serverAuthentication = a; } serverAuthentication.addToCache(); } else { // what we cache is based on the domain list in the request DigestAuthentication srv = (DigestAuthentication) serverAuthentication; StringTokenizer tok = new StringTokenizer (domain," "); String realm = srv.realm; PasswordAuthentication pw = srv.pw; digestparams = srv.params; while (tok.hasMoreTokens()) { String path = tok.nextToken(); try { /* path could be an abs_path or a complete URI */ URL u = new URL (url, path); DigestAuthentication d = new DigestAuthentication ( false, u, realm, "Digest", pw, digestparams); d.addToCache (); } catch (Exception e) {} } } } if (respCode == HTTP_OK) { checkResponseCredentials (false); } else { needToCheck = false; } if (followRedirect()) { /* if we should follow a redirect, then the followRedirects() * method will disconnect() and re-connect us to the new * location */ redirects++; continue; } int cl = -1; try { cl = Integer.parseInt(responses.findValue("content-length")); } catch (Exception exc) { }; if (method.equals("HEAD") || method.equals("TRACE") || cl == 0 || respCode == HTTP_NOT_MODIFIED || respCode == HTTP_NO_CONTENT) { if (pe != null) { ProgressData.pdata.unregister(pe); } http.finished(); http = null; inputStream = new EmptyInputStream(); if ( respCode < 400) { connected = false; return inputStream; } } if (respCode >= 400) { if (respCode == 404 || respCode == 410) { throw new FileNotFoundException(url.toString()); } else { throw new java.io.IOException("Server returned HTTP" + " response code: " + respCode + " for URL: " + url.toString()); } } return inputStream; } while (redirects < maxRedirects); throw new ProtocolException("Server redirected too many " + " times ("+ redirects + ")"); } catch (RuntimeException e) { disconnectInternal(); rememberedException = e; throw e; } catch (IOException e) { rememberedException = e; throw e; } finally { if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) { proxyAuthentication.endAuthRequest(); } else if (respCode == HTTP_UNAUTHORIZED && serverAuthentication != null) { serverAuthentication.endAuthRequest(); } } } public InputStream getErrorStream() { if (connected && responseCode >= 400) { // Client Error 4xx and Server Error 5xx if (inputStream != null) { return inputStream; } } return null; } /** * set or reset proxy authentication info in request headers * after receiving a 407 error. In the case of NTLM however, * receiving a 407 is normal and we just skip the stale check * because ntlm does not support this feature. */ private AuthenticationInfo resetProxyAuthentication(AuthenticationInfo proxyAuthentication, AuthenticationHeader auth) { if (proxyAuthentication != null ) { String raw = auth.raw(); if (proxyAuthentication.isAuthorizationStale (raw)) { /* we can retry with the current credentials */ requests.set (proxyAuthentication.getHeaderName(), proxyAuthentication.getHeaderValue( url, method)); currentProxyCredentials = proxyAuthentication; return proxyAuthentication; } else { proxyAuthentication.removeFromCache(); } } proxyAuthentication = getHttpProxyAuthentication(auth); currentProxyCredentials = proxyAuthentication; return proxyAuthentication; } /** * establish a tunnel through proxy server */ protected synchronized void doTunneling() throws IOException { int retryTunnel = 0; String statusLine = ""; int respCode = 0; AuthenticationInfo proxyAuthentication = null; String proxyHost = null; int proxyPort = -1; try { do { if (!checkReuseConnection()) { proxiedConnect(url, proxyHost, proxyPort, false); } // send the "CONNECT" request to establish a tunnel // through proxy server sendCONNECTRequest(); responses.reset(); http.parseHTTP(responses, new ProgressEntry(url.getFile(), null)); statusLine = responses.getValue(0); StringTokenizer st = new StringTokenizer(statusLine); st.nextToken(); respCode = Integer.parseInt(st.nextToken().trim()); if (respCode == HTTP_PROXY_AUTH) { AuthenticationHeader authhdr = new AuthenticationHeader ( "Proxy-Authenticate", responses ); if (!doingNTLMp2ndStage) { proxyAuthentication = resetProxyAuthentication(proxyAuthentication, authhdr); if (proxyAuthentication != null) { proxyHost = http.getProxyHostUsed(); proxyPort = http.getProxyPortUsed(); disconnectInternal(); retryTunnel++; continue; } } else { String raw = responses.findValue ("Proxy-Authenticate"); reset (); if (!proxyAuthentication.setHeaders(this, authhdr.headerParser(), raw)) { proxyHost = http.getProxyHostUsed(); proxyPort = http.getProxyPortUsed(); disconnectInternal(); throw new IOException ("Authentication failure"); } authObj = null; doingNTLMp2ndStage = false; continue; } } // cache proxy authentication info if (proxyAuthentication != null) { // cache auth info on success, domain header not relevant. proxyAuthentication.addToCache(); } if (respCode == HTTP_OK) { break; } // we don't know how to deal with other response code // so disconnect and report error disconnectInternal(); break; } while (retryTunnel < maxRedirects); if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) { throw new IOException("Unable to tunnel through proxy."+ " Proxy returns \"" + statusLine + "\""); } } finally { if (respCode == HTTP_PROXY_AUTH && proxyAuthentication != null) { proxyAuthentication.endAuthRequest(); } } // remove tunneling related requests int i; if ((i = requests.getKey("Proxy-authorization")) >= 0) requests.set(i, null, null); // reset responses responses.reset(); } /** * send a CONNECT request for establishing a tunnel to proxy server */ private void sendCONNECTRequest() throws IOException { int port = url.getPort(); if (port == -1) { port = url.getDefaultPort(); } requests.prepend("CONNECT " + url.getHost() + ":" + port + " " + httpVersion, null); requests.setIfNotSet("User-Agent", userAgent); String host = url.getHost(); if (port != -1 && port != 80) { host += ":" + String.valueOf(port); } requests.setIfNotSet("Host", host); // Not really necessary for a tunnel, but can't hurt requests.setIfNotSet("Accept", acceptString); setPreemptiveProxyAuthentication(requests); http.writeRequests(requests, null); // remove CONNECT header requests.set(0, null, null); } /** * Sets pre-emptive proxy authentication in header */ private void setPreemptiveProxyAuthentication(MessageHeader requests) { AuthenticationInfo pauth = AuthenticationInfo.getProxyAuth(http.getProxyHostUsed(), http.getProxyPortUsed()); if (pauth != null && pauth.supportsPreemptiveAuthorization()) { // Sets "Proxy-authorization" requests.setIfNotSet(pauth.getHeaderName(), pauth.getHeaderValue(url,method)); currentProxyCredentials = pauth; } } /** * Gets the authentication for an HTTP proxy, and applies it to * the connection. */ private AuthenticationInfo getHttpProxyAuthentication (AuthenticationHeader authhdr) { /* get authorization from authenticator */ AuthenticationInfo ret = null; String raw = authhdr.raw(); String host = http.getProxyHostUsed(); int port = http.getProxyPortUsed(); if (host != null && authhdr.isPresent()) { HeaderParser p = authhdr.headerParser(); String realm = p.findValue("realm"); String scheme = authhdr.scheme(); char schemeID; if ("basic".equalsIgnoreCase(scheme)) { schemeID = BasicAuthentication.BASIC_AUTH; } else if ("digest".equalsIgnoreCase(scheme)) { schemeID = DigestAuthentication.DIGEST_AUTH; } else { schemeID = 0; } if (realm == null) realm = ""; ret = AuthenticationInfo.getProxyAuth(host, port, realm, schemeID); if (ret == null) { if (schemeID == BasicAuthentication.BASIC_AUTH) { InetAddress addr = null; try { final String finalHost = host; addr = (InetAddress) java.security.AccessController.doPrivileged (new java.security.PrivilegedExceptionAction() { public Object run() throws java.net.UnknownHostException { return InetAddress.getByName(finalHost); } }); } catch (java.security.PrivilegedActionException ignored) { // User will have an unknown host. } PasswordAuthentication a = privilegedRequestPasswordAuthentication( host, addr, port, "http", realm, scheme); if (a != null) { ret = new BasicAuthentication(true, host, port, realm, a); } } else if (schemeID == DigestAuthentication.DIGEST_AUTH) { PasswordAuthentication a = privilegedRequestPasswordAuthentication( host, null, port, url.getProtocol(), realm, scheme); if (a != null) { DigestAuthentication.Parameters params = new DigestAuthentication.Parameters(); ret = new DigestAuthentication(true, host, port, realm, scheme, a, params); } } } // For backwards compatibility, we also try defaultAuth if (ret == null && defaultAuth != null && defaultAuth.schemeSupported(scheme)) { try { URL u = new URL("http", host, port, "/"); String a = defaultAuth.authString(u, scheme, realm); if (a != null) { ret = new BasicAuthentication (true, host, port, realm, a); // not in cache by default - cache on success } } catch (java.net.MalformedURLException ignored) { } } if (ret != null) { if (!ret.setHeaders(this, p, raw)) { ret = null; } } } return ret; } /** * Gets the authentication for an HTTP server, and applies it to * the connection. */ private AuthenticationInfo getServerAuthentication (AuthenticationHeader authhdr) { /* get authorization from authenticator */ AuthenticationInfo ret = null; String raw = authhdr.raw(); /* When we get an NTLM auth from cache, don't set any special headers */ if (authhdr.isPresent()) { HeaderParser p = authhdr.headerParser(); String realm = p.findValue("realm"); String scheme = authhdr.scheme(); char schemeID; if ("basic".equalsIgnoreCase(scheme)) { schemeID = BasicAuthentication.BASIC_AUTH; } else if ("digest".equalsIgnoreCase(scheme)) { schemeID = DigestAuthentication.DIGEST_AUTH; } else { schemeID = 0; } domain = p.findValue ("domain"); if (realm == null) realm = ""; ret = AuthenticationInfo.getServerAuth(url, realm, schemeID); InetAddress addr = null; if (ret == null) { try { addr = InetAddress.getByName(url.getHost()); } catch (java.net.UnknownHostException ignored) { // User will have addr = null } } // replacing -1 with default port for a protocol int port = url.getPort(); if (port == -1) { port = url.getDefaultPort(); } if (ret == null) { if (schemeID == BasicAuthentication.BASIC_AUTH) { PasswordAuthentication a = privilegedRequestPasswordAuthentication( url.getHost(), addr, port, url.getProtocol(), realm, scheme); if (a != null) { ret = new BasicAuthentication(false, url, realm, a); } } if (schemeID == DigestAuthentication.DIGEST_AUTH) { PasswordAuthentication a = privilegedRequestPasswordAuthentication( url.getHost(), addr, port, url.getProtocol(), realm, scheme); if (a != null) { digestparams = new DigestAuthentication.Parameters(); ret = new DigestAuthentication(false, url, realm, scheme, a, digestparams); } } } // For backwards compatibility, we also try defaultAuth if (ret == null && defaultAuth != null && defaultAuth.schemeSupported(scheme)) { String a = defaultAuth.authString(url, scheme, realm); if (a != null) { ret = new BasicAuthentication (false, url, realm, a); // not in cache by default - cache on success } } if (ret != null ) { if (!ret.setHeaders(this, p, raw)) { ret = null; } } } return ret; } /* inclose will be true if called from close(), in which case we * force the call to check because this is the last chance to do so. * If not in close(), then the authentication info could arrive in a trailer * field, which we have not read yet. */ private void checkResponseCredentials (boolean inClose) throws IOException { try { if (!needToCheck) return; if (validateProxy && currentProxyCredentials != null) { String raw = responses.findValue ("Proxy-Authentication-Info"); if (inClose || (raw != null)) { currentProxyCredentials.checkResponse (raw, method, url); currentProxyCredentials = null; } } if (validateServer && currentServerCredentials != null) { String raw = responses.findValue ("Authentication-Info"); if (inClose || (raw != null)) { currentServerCredentials.checkResponse (raw, method, url); currentServerCredentials = null; } } if ((currentServerCredentials==null) && (currentProxyCredentials == null)) { needToCheck = false; } } catch (IOException e) { disconnectInternal(); connected = false; throw e; } } /* Tells us whether to follow a redirect. If so, it * closes the connection (break any keep-alive) and * resets the url, re-connects, and resets the request * property. */ private boolean followRedirect() throws IOException { if (!getInstanceFollowRedirects()) { return false; } int stat = getResponseCode(); if (stat < 300 || stat > 307 || stat == 306 || stat == HTTP_NOT_MODIFIED) { return false; } String loc = getHeaderField("Location"); if (loc == null) { /* this should be present - if not, we have no choice * but to go forward w/ the response we got */ return false; } URL locUrl; try { locUrl = new URL(loc); if (!url.getProtocol().equalsIgnoreCase(locUrl.getProtocol())) { return false; } } catch (MalformedURLException mue) { // treat loc as a relative URI to conform to popular browsers locUrl = new URL(url, loc); } disconnectInternal(); // clear out old response headers!!!! responses = new MessageHeader(); if (stat == HTTP_USE_PROXY) { /* This means we must re-request the resource through the * proxy denoted in the "Location:" field of the response. * Judging by the spec, the string in the Location header * _should_ denote a URL - let's hope for "http://my.proxy.org" * Make a new HttpClient to the proxy, using HttpClient's * Instance-specific proxy fields, but note we're still fetching * the same URL. */ setProxiedClient (url, locUrl.getHost(), locUrl.getPort()); requests.set(0, method + " " + http.getURLFile()+" " + httpVersion, null); connected = true; } else { // maintain previous headers, just change the name // of the file we're getting url = locUrl; if (method.equals("POST") && !Boolean.getBoolean("http.strictPostRedirect") && (stat!=307)) { /* The HTTP/1.1 spec says that a redirect from a POST * *should not* be immediately turned into a GET, and * that some HTTP/1.0 clients incorrectly did this. * Correct behavior redirects a POST to another POST. * Unfortunately, since most browsers have this incorrect * behavior, the web works this way now. Typical usage * seems to be: * POST a login code or passwd to a web page. * after validation, the server redirects to another * (welcome) page * The second request is (erroneously) expected to be GET * * We will do the incorrect thing (POST-->GET) by default. * We will provide the capability to do the "right" thing * (POST-->POST) by a system property, "http.strictPostRedirect=true" */ requests = new MessageHeader(); setRequests = false; setRequestMethod("GET"); poster = null; if (!checkReuseConnection()) connect(); } else { if (!checkReuseConnection()) connect(); requests.set(0, method + " " + http.getURLFile()+" " + httpVersion, null); requests.set("Host", url.getHost() + ((url.getPort() == -1 || url.getPort() == 80) ? "": ":"+String.valueOf(url.getPort()))); } } return true; } /* dummy byte buffer for reading off socket prior to closing */ byte[] cdata = new byte [128]; /** * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance */ private void reset() throws IOException { http.reuse = true; /* must save before calling close */ reuseClient = http; InputStream is = http.getInputStream(); try { /* we want to read the rest of the response without using the * hurry mechanism, because that would close the connection * if everything is not available immediately */ if ((is instanceof ChunkedInputStream) || (is instanceof MeteredStream)) { /* reading until eof will not block */ while (is.read (cdata) > 0) {} } else { /* raw stream, which will block on read, so only read * the expected number of bytes, probably 0 */ int cl = 0, n=0; try { cl = Integer.parseInt (responses.findValue ("Content-Length")); } catch (Exception e) {} for (int i=0; i<cl; ) { if ((n = is.read (cdata)) == -1) { break; } else { i+= n; } } } } catch (IOException e) { http.reuse = false; reuseClient = null; disconnectInternal(); return; } try { if (is instanceof MeteredStream) { is.close(); } } catch (IOException e) { } responseCode = -1; responses = new MessageHeader(); connected = false; } /** * Disconnect from the server (for internal use) */ private void disconnectInternal() { responseCode = -1; if (pe != null) { ProgressData.pdata.unregister(pe); } if (http != null) { http.closeServer(); http = null; connected = false; } } /** * Disconnect from the server (public API) */ public void disconnect() { responseCode = -1; if (pe != null) { ProgressData.pdata.unregister(pe); } if (http != null) { /* * If we have an input stream this means we received a response * from the server. That stream may have been read to EOF and * dependening on the stream type may already be closed or the * the http client may be returned to the keep-alive cache. * If the http client has been returned to the keep-alive cache * it may be closed (idle timeout) or may be allocated to * another request. * * In other to avoid timing issues we close the input stream * which will either close the underlying connection or return * the client to the cache. If there's a possibility that the * client has been returned to the cache (ie: stream is a keep * alive stream or a chunked input stream) then we remove an * idle connection to the server. Note that this approach * can be considered an approximation in that we may close a * different idle connection to that used by the request. * Additionally it's possible that we close two connections * - the first becuase it wasn't an EOF (and couldn't be * hurried) - the second, another idle connection to the * same server. The is okay because "disconnect" is an * indication that the application doesn't intend to access * this http server for a while. */ if (inputStream != null) { HttpClient hc = http; // un-synchronized boolean ka = hc.isKeepingAlive(); try { inputStream.close(); } catch (IOException ioe) { } // if the connection is persistent it may have been closed // or returned to the keep-alive cache. If it's been returned // to the keep-alive cache then we would like to close it // but it may have been allocated if (ka) { hc.closeIdleConnection(); } } else { http.closeServer(); } // poster = null; http = null; connected = false; } } public boolean usingProxy() { if (http != null) { return (http.getProxyHostUsed() != null); } return false; } /** * Gets a header field by name. Returns null if not known. * @param name the name of the header field */ public String getHeaderField(String name) { try { getInputStream(); } catch (IOException e) {} return responses.findValue(name); } /** * Returns an unmodifiable Map of the header fields. * The Map keys are Strings that represent the * response-header field names. Each Map value is an * unmodifiable List of Strings that represents * the corresponding field values. * * @return a Map of header fields * @since 1.4 */ public Map getHeaderFields() { try { getInputStream(); } catch (IOException e) {} return responses.getHeaders(); } /** * Gets a header field by index. Returns null if not known. * @param n the index of the header field */ public String getHeaderField(int n) { try { getInputStream(); } catch (IOException e) {} return responses.getValue(n); } /** * Gets a header field by index. Returns null if not known. * @param n the index of the header field */ public String getHeaderFieldKey(int n) { try { getInputStream(); } catch (IOException e) {} return responses.getKey(n); } /** * Sets request property. If a property with the key already * exists, overwrite its value with the new value. * @param value the value to be set */ public void setRequestProperty(String key, String value) { super.setRequestProperty(key, value); checkMessageHeader(key, value); requests.set(key, value); } /** * Adds a general request property specified by a * key-value pair. This method will not overwrite * existing values associated with the same key. * * @param key the keyword by which the request is known * (e.g., "<code>accept</code>"). * @param value the value associated with it. * @see #getRequestProperties(java.lang.String) * @since 1.4 */ public void addRequestProperty(String key, String value) { super.addRequestProperty(key, value); checkMessageHeader(key, value); requests.add(key, value); } // // Set a property for authentication. This can safely disregard // the connected test. // void setAuthenticationProperty(String key, String value) { checkMessageHeader(key, value); requests.set(key, value); } public String getRequestProperty (String key) { // don't return headers containing security sensitive information if (key != null) { for (int i=0; i < EXCLUDE_HEADERS.length; i++) { if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) { return null; } } } return requests.findValue(key); } /** * Returns an unmodifiable Map of general request * properties for this connection. The Map keys * are Strings that represent the request-header * field names. Each Map value is a unmodifiable List * of Strings that represents the corresponding * field values. * * @return a Map of the general request properties for this connection. * @throws IllegalStateException if already connected * @since 1.4 */ public Map getRequestProperties() { if (connected) throw new IllegalStateException("Already connected"); // exclude headers containing security-sensitive info return requests.getHeaders(EXCLUDE_HEADERS); } public void finalize() { // this should do nothing. The stream finalizer will close // the fd } String getMethod() { return method; } /* The purpose of this wrapper is just to capture the close() call * so we can check authentication information that may have * arrived in a Trailer field */ class HttpInputStream extends FilterInputStream { public HttpInputStream (InputStream is) { super (is); } public int read() throws IOException { int ret = super.read(); return ret; } public int read(byte b[], int off, int len) throws IOException { int ret = super.read(b, off, len); return ret; } public void close () throws IOException { try { super.close (); if (sentMetric && NetworkMetrics.metricsAvailable()) { java.security.AccessController.doPrivileged(new java.security.PrivilegedAction() { public Object run() { int totalBytesRead = http.getBytesRead(); nm.sendMetricResponse(http.getServerSocket(), netMetricCode, totalBytesRead); return null; } }); netMetricCode = 0; sentMetric = false; } } finally { HttpURLConnection.this.http = null; checkResponseCredentials (true); } } } } /** An input stream that just returns EOF. This is for * HTTP URLConnections that are KeepAlive && use the * HEAD method - i.e., stream not dead, but nothing to be read. */ class EmptyInputStream extends InputStream { public int available() { return 0; } public int read() { return -1; } }