0001: /*
0002: * @(#)HttpURLConnection.java 1.86 06/10/10
0003: *
0004: * Copyright 1990-2006 Sun Microsystems, Inc. All Rights Reserved.
0005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
0006: *
0007: * This program is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU General Public License version
0009: * 2 only, as published by the Free Software Foundation.
0010: *
0011: * This program is distributed in the hope that it will be useful, but
0012: * WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * General Public License version 2 for more details (a copy is
0015: * included at /legal/license.txt).
0016: *
0017: * You should have received a copy of the GNU General Public License
0018: * version 2 along with this work; if not, write to the Free Software
0019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
0020: * 02110-1301 USA
0021: *
0022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
0023: * Clara, CA 95054 or visit www.sun.com if you need additional
0024: * information or have any questions.
0025: *
0026: */
0027:
0028: package sun.net.www.protocol.http;
0029:
0030: import java.net.URL;
0031: import java.net.URLConnection;
0032: import java.net.ProtocolException;
0033: import java.net.PasswordAuthentication;
0034: import java.net.Authenticator;
0035: import java.net.InetAddress;
0036: import java.net.UnknownHostException;
0037: import java.net.SocketTimeoutException;
0038: import java.io.*;
0039: import java.util.Date;
0040: import java.util.Map;
0041: import java.util.Locale;
0042: import java.util.StringTokenizer;
0043: import sun.net.*;
0044: import sun.net.www.*;
0045: import sun.net.www.http.HttpClient;
0046: import sun.net.www.http.PosterOutputStream;
0047: import sun.net.www.http.ChunkedInputStream;
0048: import java.text.SimpleDateFormat;
0049: import java.util.TimeZone;
0050: import java.net.MalformedURLException;
0051:
0052: /**
0053: * A class to represent an HTTP connection to a remote object.
0054: */
0055:
0056: public class HttpURLConnection extends java.net.HttpURLConnection {
0057:
0058: static final String version;
0059: public static final String userAgent;
0060:
0061: /* max # of allowed re-directs */
0062: static final int defaultmaxRedirects = 20;
0063: static final int maxRedirects;
0064:
0065: /* Not all servers support the (Proxy)-Authentication-Info headers.
0066: * By default, we don't require them to be sent
0067: */
0068: static final boolean validateProxy;
0069: static final boolean validateServer;
0070:
0071: static {
0072: maxRedirects = ((Integer) java.security.AccessController
0073: .doPrivileged(new sun.security.action.GetIntegerAction(
0074: "http.maxRedirects", defaultmaxRedirects)))
0075: .intValue();
0076: version = (String) java.security.AccessController
0077: .doPrivileged(new sun.security.action.GetPropertyAction(
0078: "java.version"));
0079: String agent = (String) java.security.AccessController
0080: .doPrivileged(new sun.security.action.GetPropertyAction(
0081: "http.agent"));
0082: if (agent == null) {
0083: agent = "Java/" + version;
0084: } else {
0085: agent = agent + " Java/" + version;
0086: }
0087: userAgent = agent;
0088: validateProxy = ((Boolean) java.security.AccessController
0089: .doPrivileged(new sun.security.action.GetBooleanAction(
0090: "http.auth.digest.validateProxy")))
0091: .booleanValue();
0092: validateServer = ((Boolean) java.security.AccessController
0093: .doPrivileged(new sun.security.action.GetBooleanAction(
0094: "http.auth.digest.validateServer")))
0095: .booleanValue();
0096: }
0097:
0098: static final String httpVersion = "HTTP/1.1";
0099: static final String acceptString = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
0100:
0101: // the following http request headers should NOT have their values
0102: // returned for security reasons.
0103: private static final String[] EXCLUDE_HEADERS = {
0104: "Proxy-Authorization", "Authorization" };
0105: protected HttpClient http;
0106: protected Handler handler;
0107:
0108: /* output stream to server */
0109: protected PrintStream ps = null;
0110:
0111: /* We only have a single static authenticator for now.
0112: * For backwards compatibility with JDK 1.1. Should be
0113: * eliminated for JDK 2.0.
0114: */
0115: private static HttpAuthenticator defaultAuth;
0116:
0117: /* all the headers we send
0118: * NOTE: do *NOT* dump out the content of 'requests' in the
0119: * output or stacktrace since it may contain security-sensitive
0120: * headers such as those defined in EXCLUDE_HEADERS.
0121: */
0122: private MessageHeader requests;
0123:
0124: /* The following two fields are only used with Digest Authentication */
0125: String domain; /* The list of authentication domains */
0126: DigestAuthentication.Parameters digestparams;
0127:
0128: /* Current credentials in use */
0129: AuthenticationInfo currentProxyCredentials = null;
0130: AuthenticationInfo currentServerCredentials = null;
0131: boolean needToCheck = true;
0132: private boolean doingNTLM2ndStage = false; /* doing the 2nd stage of an NTLM server authentication */
0133: private boolean doingNTLMp2ndStage = false; /* doing the 2nd stage of an NTLM proxy authentication */
0134: Object authObj;
0135:
0136: /* Progress entry */
0137: protected ProgressEntry pe;
0138:
0139: /* all the response headers we get back */
0140: private MessageHeader responses;
0141: /* the stream _from_ the server */
0142: private InputStream inputStream = null;
0143: /* post stream _to_ the server, if any */
0144: private PosterOutputStream poster = null;
0145:
0146: /* Indicates if the std. request headers have been set in requests. */
0147: private boolean setRequests = false;
0148:
0149: /* Indicates whether a request has already failed or not */
0150: private boolean failedOnce = false;
0151:
0152: /* Remembered Exception, we will throw it again if somebody
0153: calls getInputStream after disconnect */
0154: private Exception rememberedException = null;
0155:
0156: /* If we decide we want to reuse a client, we put it here */
0157: private HttpClient reuseClient = null;
0158:
0159: /*
0160: * privileged request password authentication
0161: *
0162: */
0163: private static PasswordAuthentication privilegedRequestPasswordAuthentication(
0164: final String host, final InetAddress addr, final int port,
0165: final String protocol, final String prompt,
0166: final String scheme) {
0167: return (PasswordAuthentication) java.security.AccessController
0168: .doPrivileged(new java.security.PrivilegedAction() {
0169: public Object run() {
0170: return Authenticator
0171: .requestPasswordAuthentication(host,
0172: addr, port, protocol, prompt,
0173: scheme);
0174: }
0175: });
0176: }
0177:
0178: /*
0179: * checks the validity of http message header and throws
0180: * IllegalArgumentException if invalid.
0181: */
0182: private void checkMessageHeader(String key, String value) {
0183: char LF = '\n';
0184: int index = key.indexOf(LF);
0185: if (index != -1) {
0186: throw new IllegalArgumentException(
0187: "Illegal character(s) in message header field: "
0188: + key);
0189: } else {
0190: if (value == null) {
0191: return;
0192: }
0193:
0194: index = value.indexOf(LF);
0195: while (index != -1) {
0196: index++;
0197: if (index < value.length()) {
0198: char c = value.charAt(index);
0199: if ((c == ' ') || (c == '\t')) {
0200: // ok, check the next occurrence
0201: index = value.indexOf(LF, index);
0202: continue;
0203: }
0204: }
0205: throw new IllegalArgumentException(
0206: "Illegal character(s) in message header value: "
0207: + value);
0208: }
0209: }
0210: }
0211:
0212: /* adds the standard key/val pairs to reqests if necessary & write to
0213: * given PrintStream
0214: */
0215: private void writeRequests() throws IOException {
0216:
0217: /* print all message headers in the MessageHeader
0218: * onto the wire - all the ones we've set and any
0219: * others that have been set
0220: */
0221: if (!setRequests) {
0222:
0223: /* We're very particular about the order in which we
0224: * set the request headers here. The order should not
0225: * matter, but some careless CGI programs have been
0226: * written to expect a very particular order of the
0227: * standard headers. To name names, the order in which
0228: * Navigator3.0 sends them. In particular, we make *sure*
0229: * to send Content-type: <> and Content-length:<> second
0230: * to last and last, respectively, in the case of a POST
0231: * request.
0232: */
0233: if (!failedOnce)
0234: requests.prepend(method + " " + http.getURLFile() + " "
0235: + httpVersion, null);
0236: if (!getUseCaches()) {
0237: requests.setIfNotSet("Cache-Control", "no-cache");
0238: requests.setIfNotSet("Pragma", "no-cache");
0239: }
0240: requests.setIfNotSet("User-Agent", userAgent);
0241: int port = url.getPort();
0242: String host = url.getHost();
0243: if (port != -1 && port != 80) {
0244: host += ":" + String.valueOf(port);
0245: }
0246: requests.setIfNotSet("Host", host);
0247: requests.setIfNotSet("Accept", acceptString);
0248:
0249: /*
0250: * For HTTP/1.1 the default behavior is to keep connections alive.
0251: * However, we may be talking to a 1.0 server so we should set
0252: * keep-alive just in case, except if we have encountered an error
0253: * or if keep alive is disabled via a system property
0254: */
0255:
0256: // Try keep-alive only on first attempt
0257: if (!failedOnce && http.getHttpKeepAliveSet()) {
0258: if (http.usingProxy) {
0259: requests.setIfNotSet("Proxy-Connection",
0260: "keep-alive");
0261: } else {
0262: requests.setIfNotSet("Connection", "keep-alive");
0263: }
0264: }
0265: // send any pre-emptive authentication
0266: if (http.usingProxy) {
0267: setPreemptiveProxyAuthentication(requests);
0268: }
0269: // Set modified since if necessary
0270: long modTime = getIfModifiedSince();
0271: if (modTime != 0) {
0272: Date date = new Date(modTime);
0273: //use the preferred date format according to RFC 2068(HTTP1.1),
0274: // RFC 822 and RFC 1123
0275: SimpleDateFormat fo = new SimpleDateFormat(
0276: "EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
0277: fo.setTimeZone(TimeZone.getTimeZone("GMT"));
0278: requests.setIfNotSet("If-Modified-Since", fo
0279: .format(date));
0280: }
0281: // check for preemptive authorization
0282: AuthenticationInfo sauth = AuthenticationInfo
0283: .getServerAuth(url);
0284: if (sauth != null
0285: && sauth.supportsPreemptiveAuthorization()) {
0286: // Sets "Authorization"
0287: requests.setIfNotSet(sauth.getHeaderName(), sauth
0288: .getHeaderValue(url, method));
0289: currentServerCredentials = sauth;
0290: }
0291:
0292: if (poster != null) {
0293: /* add Content-Length & POST/PUT data */
0294: synchronized (poster) {
0295: /* close it, so no more data can be added */
0296: poster.close();
0297: if (!method.equals("PUT")) {
0298: String type = "application/x-www-form-urlencoded";
0299: requests.setIfNotSet("Content-Type", type);
0300: }
0301: requests.set("Content-Length", String
0302: .valueOf(poster.size()));
0303: }
0304: }
0305: setRequests = true;
0306: }
0307: http.writeRequests(requests, poster);
0308: if (ps.checkError()) {
0309: String proxyHost = http.getProxyHostUsed();
0310: int proxyPort = http.getProxyPortUsed();
0311: disconnectInternal();
0312: if (failedOnce) {
0313: throw new IOException("Error writing to server");
0314: } else { // try once more
0315: failedOnce = true;
0316: if (proxyHost != null) {
0317: setProxiedClient(url, proxyHost, proxyPort);
0318: } else {
0319: setNewClient(url);
0320: }
0321: ps = (PrintStream) http.getOutputStream();
0322: connected = true;
0323: responses = new MessageHeader();
0324: setRequests = false;
0325: writeRequests();
0326: }
0327: }
0328: }
0329:
0330: /**
0331: * Create a new HttpClient object, bypassing the cache of
0332: * HTTP client objects/connections.
0333: *
0334: * @param url the URL being accessed
0335: */
0336: protected void setNewClient(URL url) throws IOException {
0337: setNewClient(url, false);
0338: }
0339:
0340: /**
0341: * Obtain a HttpsClient object. Use the cached copy if specified.
0342: *
0343: * @param url the URL being accessed
0344: * @param useCache whether the cached connection should be used
0345: * if present
0346: */
0347: protected void setNewClient(URL url, boolean useCache)
0348: throws IOException {
0349: http = HttpClient.New(url, useCache);
0350: }
0351:
0352: /**
0353: * Create a new HttpClient object, set up so that it uses
0354: * per-instance proxying to the given HTTP proxy. This
0355: * bypasses the cache of HTTP client objects/connections.
0356: *
0357: * @param url the URL being accessed
0358: * @param proxyHost the proxy host to use
0359: * @param proxyPort the proxy port to use
0360: */
0361: protected void setProxiedClient(URL url, String proxyHost,
0362: int proxyPort) throws IOException {
0363: setProxiedClient(url, proxyHost, proxyPort, false);
0364: }
0365:
0366: /**
0367: * Obtain a HttpClient object, set up so that it uses per-instance
0368: * proxying to the given HTTP proxy. Use the cached copy of HTTP
0369: * client objects/connections if specified.
0370: *
0371: * @param url the URL being accessed
0372: * @param proxyHost the proxy host to use
0373: * @param proxyPort the proxy port to use
0374: * @param useCache whether the cached connection should be used
0375: * if present
0376: */
0377: protected void setProxiedClient(URL url, String proxyHost,
0378: int proxyPort, boolean useCache) throws IOException {
0379: proxiedConnect(url, proxyHost, proxyPort, useCache);
0380: }
0381:
0382: protected void proxiedConnect(URL url, String proxyHost,
0383: int proxyPort, boolean useCache) throws IOException {
0384: SecurityManager security = System.getSecurityManager();
0385: if (security != null) {
0386: security.checkConnect(proxyHost, proxyPort);
0387: }
0388: http = HttpClient.New(url, proxyHost, proxyPort, useCache);
0389: }
0390:
0391: protected HttpURLConnection(URL u, Handler handler)
0392: throws IOException {
0393: super (u);
0394: requests = new MessageHeader();
0395: responses = new MessageHeader();
0396: this .handler = handler;
0397: }
0398:
0399: /** this constructor is used by other protocol handlers such as ftp
0400: that want to use http to fetch urls on their behalf. */
0401: public HttpURLConnection(URL u, String host, int port)
0402: throws IOException {
0403: this (u, new Handler(host, port));
0404: }
0405:
0406: /**
0407: * @deprecated. Use java.net.Authenticator.setDefault() instead.
0408: */
0409: public static void setDefaultAuthenticator(HttpAuthenticator a) {
0410: defaultAuth = a;
0411: }
0412:
0413: /**
0414: * opens a stream allowing redirects only to the same host.
0415: */
0416: public static InputStream openConnectionCheckRedirects(
0417: URLConnection c) throws IOException {
0418: boolean redir;
0419: int redirects = 0;
0420: InputStream in = null;
0421:
0422: do {
0423: if (c instanceof HttpURLConnection) {
0424: ((HttpURLConnection) c)
0425: .setInstanceFollowRedirects(false);
0426: }
0427:
0428: // We want to open the input stream before
0429: // getting headers, because getHeaderField()
0430: // et al swallow IOExceptions.
0431: in = c.getInputStream();
0432: redir = false;
0433:
0434: if (c instanceof HttpURLConnection) {
0435: HttpURLConnection http = (HttpURLConnection) c;
0436: int stat = http.getResponseCode();
0437: if (stat >= 300 && stat <= 307 && stat != 306
0438: && stat != HttpURLConnection.HTTP_NOT_MODIFIED) {
0439: URL base = http.getURL();
0440: String loc = http.getHeaderField("Location");
0441: URL target = null;
0442: if (loc != null) {
0443: target = new URL(base, loc);
0444: }
0445: http.disconnect();
0446: if (target == null
0447: || !base.getProtocol().equals(
0448: target.getProtocol())
0449: || base.getPort() != target.getPort()
0450: || !hostsEqual(base, target)
0451: || redirects >= 5) {
0452: throw new SecurityException(
0453: "illegal URL redirect");
0454: }
0455: redir = true;
0456: c = target.openConnection();
0457: redirects++;
0458: }
0459: }
0460: } while (redir);
0461: return in;
0462: }
0463:
0464: //
0465: // Same as java.net.URL.hostsEqual
0466: //
0467: private static boolean hostsEqual(URL u1, URL u2) {
0468: final String h1 = u1.getHost();
0469: final String h2 = u2.getHost();
0470:
0471: if (h1 == null) {
0472: return h2 == null;
0473: } else if (h2 == null) {
0474: return false;
0475: } else if (h1.equalsIgnoreCase(h2)) {
0476: return true;
0477: }
0478: // Have to resolve addresses before comparing, otherwise
0479: // names like tachyon and tachyon.eng would compare different
0480: final boolean result[] = { false };
0481:
0482: java.security.AccessController
0483: .doPrivileged(new java.security.PrivilegedAction() {
0484: public Object run() {
0485: try {
0486: InetAddress a1 = InetAddress.getByName(h1);
0487: InetAddress a2 = InetAddress.getByName(h2);
0488: result[0] = a1.equals(a2);
0489: } catch (UnknownHostException e) {
0490: } catch (SecurityException e) {
0491: }
0492: return null;
0493: }
0494: });
0495:
0496: return result[0];
0497: }
0498:
0499: // overridden in HTTPS subclass
0500:
0501: public void connect() throws IOException {
0502: plainConnect();
0503: }
0504:
0505: private boolean checkReuseConnection() {
0506: if (connected) {
0507: return true;
0508: }
0509: if (reuseClient != null) {
0510: http = reuseClient;
0511: http.reuse = false;
0512: reuseClient = null;
0513: connected = true;
0514: return true;
0515: }
0516: return false;
0517: }
0518:
0519: protected void plainConnect() throws IOException {
0520: if (connected) {
0521: return;
0522: }
0523: try {
0524: if ("http".equals(url.getProtocol()) && !failedOnce) {
0525: http = HttpClient.New(url);
0526: } else {
0527: // make sure to construct new connection if first
0528: // attempt failed
0529: http = new HttpClient(url, handler.proxy,
0530: handler.proxyPort);
0531: }
0532: ps = (PrintStream) http.getOutputStream();
0533: } catch (IOException e) {
0534: throw e;
0535: }
0536: // constructor to HTTP client calls openserver
0537: connected = true;
0538: }
0539:
0540: /*
0541: * Allowable input/output sequences:
0542: * [interpreted as POST/PUT]
0543: * - get output, [write output,] get input, [read input]
0544: * - get output, [write output]
0545: * [interpreted as GET]
0546: * - get input, [read input]
0547: * Disallowed:
0548: * - get input, [read input,] get output, [write output]
0549: */
0550:
0551: public synchronized OutputStream getOutputStream()
0552: throws IOException {
0553:
0554: try {
0555: if (!doOutput) {
0556: throw new ProtocolException(
0557: "cannot write to a URLConnection"
0558: + " if doOutput=false - call setDoOutput(true)");
0559: }
0560:
0561: if (method.equals("GET")) {
0562: method = "POST"; // Backward compatibility
0563: }
0564: if (!"POST".equals(method) && !"PUT".equals(method)
0565: && "http".equals(url.getProtocol())) {
0566: throw new ProtocolException("HTTP method " + method
0567: + " doesn't support output");
0568: }
0569:
0570: // if there's already an input stream open, throw an exception
0571: if (inputStream != null) {
0572: throw new ProtocolException(
0573: "Cannot write output after reading input.");
0574: }
0575:
0576: if (!checkReuseConnection())
0577: connect();
0578:
0579: /* This exists to fix the HttpsURLConnection subclass.
0580: * Hotjava needs to run on JDK1.1. Do proper fix in subclass
0581: * for 1.2 and remove this.
0582: */
0583: ps = (PrintStream) http.getOutputStream();
0584:
0585: if (poster == null)
0586: poster = new PosterOutputStream();
0587: return poster;
0588: } catch (RuntimeException e) {
0589: disconnectInternal();
0590: throw e;
0591: } catch (IOException e) {
0592: disconnectInternal();
0593: throw e;
0594: }
0595: }
0596:
0597: public synchronized InputStream getInputStream() throws IOException {
0598:
0599: if (!doInput) {
0600: throw new ProtocolException(
0601: "Cannot read from URLConnection"
0602: + " if doInput=false (call setDoInput(true))");
0603: }
0604:
0605: if (rememberedException != null) {
0606: if (rememberedException instanceof RuntimeException)
0607: throw new RuntimeException(rememberedException);
0608: else {
0609: IOException exception;
0610: try {
0611: exception = new IOException();
0612: exception.initCause(rememberedException);
0613: } catch (Exception t) {
0614: exception = (IOException) rememberedException;
0615: }
0616: throw exception;
0617: }
0618: }
0619:
0620: if (inputStream != null) {
0621: return inputStream;
0622: }
0623:
0624: int redirects = 0;
0625: int respCode = 0;
0626: AuthenticationInfo serverAuthentication = null;
0627: AuthenticationInfo proxyAuthentication = null;
0628: AuthenticationHeader srvHdr = null;
0629: try {
0630: do {
0631:
0632: pe = new ProgressEntry(url.getFile(), null);
0633: ProgressData.pdata.register(pe);
0634: if (!checkReuseConnection())
0635: connect();
0636:
0637: /* This exists to fix the HttpsURLConnection subclass.
0638: * Hotjava needs to run on JDK1.1. Do proper fix once a
0639: * proper solution for SSL can be found.
0640: */
0641: ps = (PrintStream) http.getOutputStream();
0642:
0643: writeRequests();
0644: http.parseHTTP(responses, pe);
0645: inputStream = new HttpInputStream(http.getInputStream());
0646:
0647: respCode = getResponseCode();
0648: if (respCode == HTTP_PROXY_AUTH) {
0649: AuthenticationHeader authhdr = new AuthenticationHeader(
0650: "Proxy-Authenticate", responses);
0651: if (!doingNTLMp2ndStage) {
0652: proxyAuthentication = resetProxyAuthentication(
0653: proxyAuthentication, authhdr);
0654: if (proxyAuthentication != null) {
0655: redirects++;
0656: disconnectInternal();
0657: continue;
0658: }
0659: } else {
0660: /* in this case, only one header field will be present */
0661: String raw = responses
0662: .findValue("Proxy-Authenticate");
0663: reset();
0664: if (!proxyAuthentication.setHeaders(this ,
0665: authhdr.headerParser(), raw)) {
0666: disconnectInternal();
0667: throw new IOException(
0668: "Authentication failure");
0669: }
0670: if (serverAuthentication != null
0671: && srvHdr != null
0672: && !serverAuthentication.setHeaders(
0673: this , srvHdr.headerParser(),
0674: raw)) {
0675: disconnectInternal();
0676: throw new IOException(
0677: "Authentication failure");
0678: }
0679: authObj = null;
0680: doingNTLMp2ndStage = false;
0681: continue;
0682: }
0683: }
0684:
0685: // cache proxy authentication info
0686: if (proxyAuthentication != null) {
0687: // cache auth info on success, domain header not relevant.
0688: proxyAuthentication.addToCache();
0689: }
0690:
0691: if (respCode == HTTP_UNAUTHORIZED) {
0692: srvHdr = new AuthenticationHeader(
0693: "WWW-Authenticate", responses);
0694: String raw = srvHdr.raw();
0695: if (!doingNTLM2ndStage) {
0696: if (serverAuthentication != null) {
0697: if (serverAuthentication
0698: .isAuthorizationStale(raw)) {
0699: /* we can retry with the current credentials */
0700: disconnectInternal();
0701: redirects++;
0702: requests.set(serverAuthentication
0703: .getHeaderName(),
0704: serverAuthentication
0705: .getHeaderValue(url,
0706: method));
0707: currentServerCredentials = serverAuthentication;
0708: continue;
0709: } else {
0710: serverAuthentication.removeFromCache();
0711: }
0712: }
0713: serverAuthentication = getServerAuthentication(srvHdr);
0714: currentServerCredentials = serverAuthentication;
0715:
0716: if (serverAuthentication != null) {
0717: disconnectInternal();
0718: redirects++; // don't let things loop ad nauseum
0719: continue;
0720: }
0721: } else {
0722: reset();
0723: /* header not used for ntlm */
0724: if (!serverAuthentication.setHeaders(this ,
0725: null, raw)) {
0726: disconnectInternal();
0727: throw new IOException(
0728: "Authentication failure");
0729: }
0730: doingNTLM2ndStage = false;
0731: authObj = null;
0732: continue;
0733: }
0734: }
0735: // cache server authentication info
0736: if (serverAuthentication != null) {
0737: // cache auth info on success
0738: if (!(serverAuthentication instanceof DigestAuthentication)
0739: || (domain == null)) {
0740: if (serverAuthentication instanceof BasicAuthentication) {
0741: // check if the path is shorter than the existing entry
0742: String npath = AuthenticationInfo
0743: .reducePath(url.getPath());
0744: String opath = serverAuthentication.path;
0745: if (!opath.startsWith(npath)
0746: || npath.length() >= opath.length()) {
0747: /* npath is longer, there must be a common root */
0748: npath = BasicAuthentication
0749: .getRootPath(opath, npath);
0750: }
0751: // remove the entry and create a new one
0752: BasicAuthentication a = (BasicAuthentication) serverAuthentication
0753: .clone();
0754: serverAuthentication.removeFromCache();
0755: a.path = npath;
0756: serverAuthentication = a;
0757: }
0758: serverAuthentication.addToCache();
0759: } else {
0760: // what we cache is based on the domain list in the request
0761: DigestAuthentication srv = (DigestAuthentication) serverAuthentication;
0762: StringTokenizer tok = new StringTokenizer(
0763: domain, " ");
0764: String realm = srv.realm;
0765: PasswordAuthentication pw = srv.pw;
0766: digestparams = srv.params;
0767: while (tok.hasMoreTokens()) {
0768: String path = tok.nextToken();
0769: try {
0770: /* path could be an abs_path or a complete URI */
0771: URL u = new URL(url, path);
0772: DigestAuthentication d = new DigestAuthentication(
0773: false, u, realm, "Digest", pw,
0774: digestparams);
0775: d.addToCache();
0776: } catch (Exception e) {
0777: }
0778: }
0779: }
0780: }
0781:
0782: if (respCode == HTTP_OK) {
0783: checkResponseCredentials(false);
0784: } else {
0785: needToCheck = false;
0786: }
0787:
0788: if (followRedirect()) {
0789: /* if we should follow a redirect, then the followRedirects()
0790: * method will disconnect() and re-connect us to the new
0791: * location
0792: */
0793: redirects++;
0794: continue;
0795: }
0796:
0797: int cl = -1;
0798: try {
0799: cl = Integer.parseInt(responses
0800: .findValue("content-length"));
0801: } catch (Exception exc) {
0802: }
0803: ;
0804:
0805: if (method.equals("HEAD") || method.equals("TRACE")
0806: || cl == 0 || respCode == HTTP_NOT_MODIFIED
0807: || respCode == HTTP_NO_CONTENT) {
0808:
0809: if (pe != null) {
0810: ProgressData.pdata.unregister(pe);
0811: }
0812: http.finished();
0813: http = null;
0814: inputStream = new EmptyInputStream();
0815: if (respCode < 400) {
0816: connected = false;
0817: return inputStream;
0818: }
0819: }
0820:
0821: if (respCode >= 400) {
0822: if (respCode == 404 || respCode == 410) {
0823: throw new FileNotFoundException(url.toString());
0824: } else {
0825: throw new java.io.IOException(
0826: "Server returned HTTP"
0827: + " response code: " + respCode
0828: + " for URL: " + url.toString());
0829: }
0830: }
0831:
0832: return inputStream;
0833: } while (redirects < maxRedirects);
0834:
0835: throw new ProtocolException("Server redirected too many "
0836: + " times (" + redirects + ")");
0837: } catch (RuntimeException e) {
0838: disconnectInternal();
0839: rememberedException = e;
0840: throw e;
0841: } catch (IOException e) {
0842: rememberedException = e;
0843: throw e;
0844: } finally {
0845: if (respCode == HTTP_PROXY_AUTH
0846: && proxyAuthentication != null) {
0847: proxyAuthentication.endAuthRequest();
0848: } else if (respCode == HTTP_UNAUTHORIZED
0849: && serverAuthentication != null) {
0850: serverAuthentication.endAuthRequest();
0851: }
0852: }
0853: }
0854:
0855: public InputStream getErrorStream() {
0856: if (connected && responseCode >= 400) {
0857: // Client Error 4xx and Server Error 5xx
0858: if (inputStream != null) {
0859: return inputStream;
0860: }
0861: }
0862: return null;
0863: }
0864:
0865: /**
0866: * set or reset proxy authentication info in request headers
0867: * after receiving a 407 error. In the case of NTLM however,
0868: * receiving a 407 is normal and we just skip the stale check
0869: * because ntlm does not support this feature.
0870: */
0871: private AuthenticationInfo resetProxyAuthentication(
0872: AuthenticationInfo proxyAuthentication,
0873: AuthenticationHeader auth) {
0874: if (proxyAuthentication != null) {
0875: String raw = auth.raw();
0876: if (proxyAuthentication.isAuthorizationStale(raw)) {
0877: /* we can retry with the current credentials */
0878: requests
0879: .set(proxyAuthentication.getHeaderName(),
0880: proxyAuthentication.getHeaderValue(url,
0881: method));
0882: currentProxyCredentials = proxyAuthentication;
0883: return proxyAuthentication;
0884: } else {
0885: proxyAuthentication.removeFromCache();
0886: }
0887: }
0888: proxyAuthentication = getHttpProxyAuthentication(auth);
0889: currentProxyCredentials = proxyAuthentication;
0890: return proxyAuthentication;
0891: }
0892:
0893: /**
0894: * establish a tunnel through proxy server
0895: */
0896: protected synchronized void doTunneling() throws IOException {
0897: int retryTunnel = 0;
0898: String statusLine = "";
0899: int respCode = 0;
0900: AuthenticationInfo proxyAuthentication = null;
0901: String proxyHost = null;
0902: int proxyPort = -1;
0903: try {
0904: do {
0905: if (!checkReuseConnection()) {
0906: proxiedConnect(url, proxyHost, proxyPort, false);
0907: }
0908: // send the "CONNECT" request to establish a tunnel
0909: // through proxy server
0910: sendCONNECTRequest();
0911: responses.reset();
0912: http.parseHTTP(responses, new ProgressEntry(url
0913: .getFile(), null));
0914: statusLine = responses.getValue(0);
0915: StringTokenizer st = new StringTokenizer(statusLine);
0916: st.nextToken();
0917: respCode = Integer.parseInt(st.nextToken().trim());
0918: if (respCode == HTTP_PROXY_AUTH) {
0919: AuthenticationHeader authhdr = new AuthenticationHeader(
0920: "Proxy-Authenticate", responses);
0921: if (!doingNTLMp2ndStage) {
0922: proxyAuthentication = resetProxyAuthentication(
0923: proxyAuthentication, authhdr);
0924: if (proxyAuthentication != null) {
0925: proxyHost = http.getProxyHostUsed();
0926: proxyPort = http.getProxyPortUsed();
0927: disconnectInternal();
0928: retryTunnel++;
0929: continue;
0930: }
0931: } else {
0932: String raw = responses
0933: .findValue("Proxy-Authenticate");
0934: reset();
0935: if (!proxyAuthentication.setHeaders(this ,
0936: authhdr.headerParser(), raw)) {
0937: proxyHost = http.getProxyHostUsed();
0938: proxyPort = http.getProxyPortUsed();
0939: disconnectInternal();
0940: throw new IOException(
0941: "Authentication failure");
0942: }
0943: authObj = null;
0944: doingNTLMp2ndStage = false;
0945: continue;
0946: }
0947: }
0948: // cache proxy authentication info
0949: if (proxyAuthentication != null) {
0950: // cache auth info on success, domain header not relevant.
0951: proxyAuthentication.addToCache();
0952: }
0953:
0954: if (respCode == HTTP_OK) {
0955: break;
0956: }
0957: // we don't know how to deal with other response code
0958: // so disconnect and report error
0959: disconnectInternal();
0960: break;
0961: } while (retryTunnel < maxRedirects);
0962:
0963: if (retryTunnel >= maxRedirects || (respCode != HTTP_OK)) {
0964: throw new IOException("Unable to tunnel through proxy."
0965: + " Proxy returns \"" + statusLine + "\"");
0966: }
0967: } finally {
0968: if (respCode == HTTP_PROXY_AUTH
0969: && proxyAuthentication != null) {
0970: proxyAuthentication.endAuthRequest();
0971: }
0972: }
0973: // remove tunneling related requests
0974: int i;
0975: if ((i = requests.getKey("Proxy-authorization")) >= 0)
0976: requests.set(i, null, null);
0977:
0978: // reset responses
0979: responses.reset();
0980: }
0981:
0982: /**
0983: * send a CONNECT request for establishing a tunnel to proxy server
0984: */
0985: private void sendCONNECTRequest() throws IOException {
0986: int port = url.getPort();
0987: if (port == -1) {
0988: port = url.getDefaultPort();
0989: }
0990: requests.prepend("CONNECT " + url.getHost() + ":" + port + " "
0991: + httpVersion, null);
0992: requests.setIfNotSet("User-Agent", userAgent);
0993:
0994: String host = url.getHost();
0995: if (port != -1 && port != 80) {
0996: host += ":" + String.valueOf(port);
0997: }
0998: requests.setIfNotSet("Host", host);
0999:
1000: // Not really necessary for a tunnel, but can't hurt
1001: requests.setIfNotSet("Accept", acceptString);
1002:
1003: setPreemptiveProxyAuthentication(requests);
1004: http.writeRequests(requests, null);
1005: // remove CONNECT header
1006: requests.set(0, null, null);
1007: }
1008:
1009: /**
1010: * Sets pre-emptive proxy authentication in header
1011: */
1012: private void setPreemptiveProxyAuthentication(MessageHeader requests) {
1013: AuthenticationInfo pauth = AuthenticationInfo.getProxyAuth(http
1014: .getProxyHostUsed(), http.getProxyPortUsed());
1015: if (pauth != null && pauth.supportsPreemptiveAuthorization()) {
1016: // Sets "Proxy-authorization"
1017: requests.setIfNotSet(pauth.getHeaderName(), pauth
1018: .getHeaderValue(url, method));
1019: currentProxyCredentials = pauth;
1020: }
1021: }
1022:
1023: /**
1024: * Gets the authentication for an HTTP proxy, and applies it to
1025: * the connection.
1026: */
1027: private AuthenticationInfo getHttpProxyAuthentication(
1028: AuthenticationHeader authhdr) {
1029: /* get authorization from authenticator */
1030: AuthenticationInfo ret = null;
1031: String raw = authhdr.raw();
1032: String host = http.getProxyHostUsed();
1033: int port = http.getProxyPortUsed();
1034: if (host != null && authhdr.isPresent()) {
1035: HeaderParser p = authhdr.headerParser();
1036: String realm = p.findValue("realm");
1037: String scheme = authhdr.scheme();
1038: char schemeID;
1039: if ("basic".equalsIgnoreCase(scheme)) {
1040: schemeID = BasicAuthentication.BASIC_AUTH;
1041: } else if ("digest".equalsIgnoreCase(scheme)) {
1042: schemeID = DigestAuthentication.DIGEST_AUTH;
1043:
1044: } else {
1045: schemeID = 0;
1046: }
1047: if (realm == null)
1048: realm = "";
1049: ret = AuthenticationInfo.getProxyAuth(host, port, realm,
1050: schemeID);
1051: if (ret == null) {
1052: if (schemeID == BasicAuthentication.BASIC_AUTH) {
1053: InetAddress addr = null;
1054: try {
1055: final String finalHost = host;
1056: addr = (InetAddress) java.security.AccessController
1057: .doPrivileged(new java.security.PrivilegedExceptionAction() {
1058: public Object run()
1059: throws java.net.UnknownHostException {
1060: return InetAddress
1061: .getByName(finalHost);
1062: }
1063: });
1064: } catch (java.security.PrivilegedActionException ignored) {
1065: // User will have an unknown host.
1066: }
1067: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1068: host, addr, port, "http", realm, scheme);
1069: if (a != null) {
1070: ret = new BasicAuthentication(true, host, port,
1071: realm, a);
1072: }
1073: } else if (schemeID == DigestAuthentication.DIGEST_AUTH) {
1074: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1075: host, null, port, url.getProtocol(), realm,
1076: scheme);
1077: if (a != null) {
1078: DigestAuthentication.Parameters params = new DigestAuthentication.Parameters();
1079: ret = new DigestAuthentication(true, host,
1080: port, realm, scheme, a, params);
1081: }
1082: }
1083: }
1084: // For backwards compatibility, we also try defaultAuth
1085:
1086: if (ret == null && defaultAuth != null
1087: && defaultAuth.schemeSupported(scheme)) {
1088: try {
1089: URL u = new URL("http", host, port, "/");
1090: String a = defaultAuth.authString(u, scheme, realm);
1091: if (a != null) {
1092: ret = new BasicAuthentication(true, host, port,
1093: realm, a);
1094: // not in cache by default - cache on success
1095: }
1096: } catch (java.net.MalformedURLException ignored) {
1097: }
1098: }
1099: if (ret != null) {
1100: if (!ret.setHeaders(this , p, raw)) {
1101: ret = null;
1102: }
1103: }
1104: }
1105: return ret;
1106: }
1107:
1108: /**
1109: * Gets the authentication for an HTTP server, and applies it to
1110: * the connection.
1111: */
1112: private AuthenticationInfo getServerAuthentication(
1113: AuthenticationHeader authhdr) {
1114: /* get authorization from authenticator */
1115: AuthenticationInfo ret = null;
1116: String raw = authhdr.raw();
1117: /* When we get an NTLM auth from cache, don't set any special headers */
1118: if (authhdr.isPresent()) {
1119: HeaderParser p = authhdr.headerParser();
1120: String realm = p.findValue("realm");
1121: String scheme = authhdr.scheme();
1122: char schemeID;
1123: if ("basic".equalsIgnoreCase(scheme)) {
1124: schemeID = BasicAuthentication.BASIC_AUTH;
1125: } else if ("digest".equalsIgnoreCase(scheme)) {
1126: schemeID = DigestAuthentication.DIGEST_AUTH;
1127:
1128: } else {
1129: schemeID = 0;
1130: }
1131: domain = p.findValue("domain");
1132: if (realm == null)
1133: realm = "";
1134: ret = AuthenticationInfo
1135: .getServerAuth(url, realm, schemeID);
1136: InetAddress addr = null;
1137: if (ret == null) {
1138: try {
1139: addr = InetAddress.getByName(url.getHost());
1140: } catch (java.net.UnknownHostException ignored) {
1141: // User will have addr = null
1142: }
1143: }
1144: // replacing -1 with default port for a protocol
1145: int port = url.getPort();
1146: if (port == -1) {
1147: port = url.getDefaultPort();
1148: }
1149: if (ret == null) {
1150: if (schemeID == BasicAuthentication.BASIC_AUTH) {
1151: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1152: url.getHost(), addr, port, url
1153: .getProtocol(), realm, scheme);
1154: if (a != null) {
1155: ret = new BasicAuthentication(false, url,
1156: realm, a);
1157: }
1158: }
1159:
1160: if (schemeID == DigestAuthentication.DIGEST_AUTH) {
1161: PasswordAuthentication a = privilegedRequestPasswordAuthentication(
1162: url.getHost(), addr, port, url
1163: .getProtocol(), realm, scheme);
1164: if (a != null) {
1165: digestparams = new DigestAuthentication.Parameters();
1166: ret = new DigestAuthentication(false, url,
1167: realm, scheme, a, digestparams);
1168: }
1169: }
1170: }
1171:
1172: // For backwards compatibility, we also try defaultAuth
1173:
1174: if (ret == null && defaultAuth != null
1175: && defaultAuth.schemeSupported(scheme)) {
1176: String a = defaultAuth.authString(url, scheme, realm);
1177: if (a != null) {
1178: ret = new BasicAuthentication(false, url, realm, a);
1179: // not in cache by default - cache on success
1180: }
1181: }
1182:
1183: if (ret != null) {
1184: if (!ret.setHeaders(this , p, raw)) {
1185: ret = null;
1186: }
1187: }
1188: }
1189: return ret;
1190: }
1191:
1192: /* inclose will be true if called from close(), in which case we
1193: * force the call to check because this is the last chance to do so.
1194: * If not in close(), then the authentication info could arrive in a trailer
1195: * field, which we have not read yet.
1196: */
1197: private void checkResponseCredentials(boolean inClose)
1198: throws IOException {
1199: try {
1200: if (!needToCheck)
1201: return;
1202: if (validateProxy && currentProxyCredentials != null) {
1203: String raw = responses
1204: .findValue("Proxy-Authentication-Info");
1205: if (inClose || (raw != null)) {
1206: currentProxyCredentials.checkResponse(raw, method,
1207: url);
1208: currentProxyCredentials = null;
1209: }
1210: }
1211: if (validateServer && currentServerCredentials != null) {
1212: String raw = responses.findValue("Authentication-Info");
1213: if (inClose || (raw != null)) {
1214: currentServerCredentials.checkResponse(raw, method,
1215: url);
1216: currentServerCredentials = null;
1217: }
1218: }
1219: if ((currentServerCredentials == null)
1220: && (currentProxyCredentials == null)) {
1221: needToCheck = false;
1222: }
1223: } catch (IOException e) {
1224: disconnectInternal();
1225: connected = false;
1226: throw e;
1227: }
1228: }
1229:
1230: /* Tells us whether to follow a redirect. If so, it
1231: * closes the connection (break any keep-alive) and
1232: * resets the url, re-connects, and resets the request
1233: * property.
1234: */
1235: private boolean followRedirect() throws IOException {
1236: if (!getInstanceFollowRedirects()) {
1237: return false;
1238: }
1239:
1240: int stat = getResponseCode();
1241: if (stat < 300 || stat > 307 || stat == 306
1242: || stat == HTTP_NOT_MODIFIED) {
1243: return false;
1244: }
1245: String loc = getHeaderField("Location");
1246: if (loc == null) {
1247: /* this should be present - if not, we have no choice
1248: * but to go forward w/ the response we got
1249: */
1250: return false;
1251: }
1252: URL locUrl;
1253: try {
1254: locUrl = new URL(loc);
1255: if (!url.getProtocol().equalsIgnoreCase(
1256: locUrl.getProtocol())) {
1257: return false;
1258: }
1259:
1260: } catch (MalformedURLException mue) {
1261: // treat loc as a relative URI to conform to popular browsers
1262: locUrl = new URL(url, loc);
1263: }
1264: disconnectInternal();
1265: // clear out old response headers!!!!
1266: responses = new MessageHeader();
1267: if (stat == HTTP_USE_PROXY) {
1268: /* This means we must re-request the resource through the
1269: * proxy denoted in the "Location:" field of the response.
1270: * Judging by the spec, the string in the Location header
1271: * _should_ denote a URL - let's hope for "http://my.proxy.org"
1272: * Make a new HttpClient to the proxy, using HttpClient's
1273: * Instance-specific proxy fields, but note we're still fetching
1274: * the same URL.
1275: */
1276: setProxiedClient(url, locUrl.getHost(), locUrl.getPort());
1277: requests.set(0, method + " " + http.getURLFile() + " "
1278: + httpVersion, null);
1279: connected = true;
1280: } else {
1281: // maintain previous headers, just change the name
1282: // of the file we're getting
1283: url = locUrl;
1284: if (method.equals("POST")
1285: && !Boolean.getBoolean("http.strictPostRedirect")
1286: && (stat != 307)) {
1287: /* The HTTP/1.1 spec says that a redirect from a POST
1288: * *should not* be immediately turned into a GET, and
1289: * that some HTTP/1.0 clients incorrectly did this.
1290: * Correct behavior redirects a POST to another POST.
1291: * Unfortunately, since most browsers have this incorrect
1292: * behavior, the web works this way now. Typical usage
1293: * seems to be:
1294: * POST a login code or passwd to a web page.
1295: * after validation, the server redirects to another
1296: * (welcome) page
1297: * The second request is (erroneously) expected to be GET
1298: *
1299: * We will do the incorrect thing (POST-->GET) by default.
1300: * We will provide the capability to do the "right" thing
1301: * (POST-->POST) by a system property, "http.strictPostRedirect=true"
1302: */
1303:
1304: requests = new MessageHeader();
1305: setRequests = false;
1306: setRequestMethod("GET");
1307: poster = null;
1308: if (!checkReuseConnection())
1309: connect();
1310: } else {
1311: if (!checkReuseConnection())
1312: connect();
1313: requests.set(0, method + " " + http.getURLFile() + " "
1314: + httpVersion, null);
1315: requests
1316: .set(
1317: "Host",
1318: url.getHost()
1319: + ((url.getPort() == -1 || url
1320: .getPort() == 80) ? ""
1321: : ":"
1322: + String
1323: .valueOf(url
1324: .getPort())));
1325: }
1326: }
1327: return true;
1328: }
1329:
1330: /* dummy byte buffer for reading off socket prior to closing */
1331: byte[] cdata = new byte[128];
1332:
1333: /**
1334: * Reset (without disconnecting the TCP conn) in order to do another transaction with this instance
1335: */
1336: private void reset() throws IOException {
1337: http.reuse = true;
1338: /* must save before calling close */
1339: reuseClient = http;
1340: InputStream is = http.getInputStream();
1341: try {
1342: /* we want to read the rest of the response without using the
1343: * hurry mechanism, because that would close the connection
1344: * if everything is not available immediately
1345: */
1346: if ((is instanceof ChunkedInputStream)
1347: || (is instanceof MeteredStream)) {
1348: /* reading until eof will not block */
1349: while (is.read(cdata) > 0) {
1350: }
1351: } else {
1352: /* raw stream, which will block on read, so only read
1353: * the expected number of bytes, probably 0
1354: */
1355: int cl = 0, n = 0;
1356: try {
1357: cl = Integer.parseInt(responses
1358: .findValue("Content-Length"));
1359: } catch (Exception e) {
1360: }
1361: for (int i = 0; i < cl;) {
1362: if ((n = is.read(cdata)) == -1) {
1363: break;
1364: } else {
1365: i += n;
1366: }
1367: }
1368: }
1369: } catch (IOException e) {
1370: http.reuse = false;
1371: reuseClient = null;
1372: disconnectInternal();
1373: return;
1374: }
1375: try {
1376: if (is instanceof MeteredStream) {
1377: is.close();
1378: }
1379: } catch (IOException e) {
1380: }
1381: responseCode = -1;
1382: responses = new MessageHeader();
1383: connected = false;
1384: }
1385:
1386: /**
1387: * Disconnect from the server (for internal use)
1388: */
1389: private void disconnectInternal() {
1390: responseCode = -1;
1391: if (pe != null) {
1392: ProgressData.pdata.unregister(pe);
1393: }
1394: if (http != null) {
1395: http.closeServer();
1396: http = null;
1397: connected = false;
1398: }
1399: }
1400:
1401: /**
1402: * Disconnect from the server (public API)
1403: */
1404: public void disconnect() {
1405:
1406: responseCode = -1;
1407: if (pe != null) {
1408: ProgressData.pdata.unregister(pe);
1409: }
1410: if (http != null) {
1411: /*
1412: * If we have an input stream this means we received a response
1413: * from the server. That stream may have been read to EOF and
1414: * dependening on the stream type may already be closed or the
1415: * the http client may be returned to the keep-alive cache.
1416: * If the http client has been returned to the keep-alive cache
1417: * it may be closed (idle timeout) or may be allocated to
1418: * another request.
1419: *
1420: * In other to avoid timing issues we close the input stream
1421: * which will either close the underlying connection or return
1422: * the client to the cache. If there's a possibility that the
1423: * client has been returned to the cache (ie: stream is a keep
1424: * alive stream or a chunked input stream) then we remove an
1425: * idle connection to the server. Note that this approach
1426: * can be considered an approximation in that we may close a
1427: * different idle connection to that used by the request.
1428: * Additionally it's possible that we close two connections
1429: * - the first becuase it wasn't an EOF (and couldn't be
1430: * hurried) - the second, another idle connection to the
1431: * same server. The is okay because "disconnect" is an
1432: * indication that the application doesn't intend to access
1433: * this http server for a while.
1434: */
1435:
1436: if (inputStream != null) {
1437: HttpClient hc = http;
1438:
1439: // un-synchronized
1440: boolean ka = hc.isKeepingAlive();
1441:
1442: try {
1443: inputStream.close();
1444: } catch (IOException ioe) {
1445: }
1446:
1447: // if the connection is persistent it may have been closed
1448: // or returned to the keep-alive cache. If it's been returned
1449: // to the keep-alive cache then we would like to close it
1450: // but it may have been allocated
1451:
1452: if (ka) {
1453: hc.closeIdleConnection();
1454: }
1455:
1456: } else {
1457: http.closeServer();
1458: }
1459:
1460: // poster = null;
1461: http = null;
1462: connected = false;
1463: }
1464: }
1465:
1466: public boolean usingProxy() {
1467: if (http != null) {
1468: return (http.getProxyHostUsed() != null);
1469: }
1470: return false;
1471: }
1472:
1473: /**
1474: * Gets a header field by name. Returns null if not known.
1475: * @param name the name of the header field
1476: */
1477: public String getHeaderField(String name) {
1478: try {
1479: getInputStream();
1480: } catch (IOException e) {
1481: }
1482: return responses.findValue(name);
1483: }
1484:
1485: /**
1486: * Returns an unmodifiable Map of the header fields.
1487: * The Map keys are Strings that represent the
1488: * response-header field names. Each Map value is an
1489: * unmodifiable List of Strings that represents
1490: * the corresponding field values.
1491: *
1492: * @return a Map of header fields
1493: * @since 1.4
1494: */
1495: public Map getHeaderFields() {
1496: try {
1497: getInputStream();
1498: } catch (IOException e) {
1499: }
1500: return responses.getHeaders();
1501: }
1502:
1503: /**
1504: * Gets a header field by index. Returns null if not known.
1505: * @param n the index of the header field
1506: */
1507: public String getHeaderField(int n) {
1508: try {
1509: getInputStream();
1510: } catch (IOException e) {
1511: }
1512: return responses.getValue(n);
1513: }
1514:
1515: /**
1516: * Gets a header field by index. Returns null if not known.
1517: * @param n the index of the header field
1518: */
1519: public String getHeaderFieldKey(int n) {
1520: try {
1521: getInputStream();
1522: } catch (IOException e) {
1523: }
1524: return responses.getKey(n);
1525: }
1526:
1527: /**
1528: * Sets request property. If a property with the key already
1529: * exists, overwrite its value with the new value.
1530: * @param value the value to be set
1531: */
1532: public void setRequestProperty(String key, String value) {
1533: super .setRequestProperty(key, value);
1534: checkMessageHeader(key, value);
1535: requests.set(key, value);
1536: }
1537:
1538: /**
1539: * Adds a general request property specified by a
1540: * key-value pair. This method will not overwrite
1541: * existing values associated with the same key.
1542: *
1543: * @param key the keyword by which the request is known
1544: * (e.g., "<code>accept</code>").
1545: * @param value the value associated with it.
1546: * @see #getRequestProperties(java.lang.String)
1547: * @since 1.4
1548: */
1549: public void addRequestProperty(String key, String value) {
1550: super .addRequestProperty(key, value);
1551: checkMessageHeader(key, value);
1552: requests.add(key, value);
1553: }
1554:
1555: //
1556: // Set a property for authentication. This can safely disregard
1557: // the connected test.
1558: //
1559: void setAuthenticationProperty(String key, String value) {
1560: checkMessageHeader(key, value);
1561: requests.set(key, value);
1562: }
1563:
1564: public String getRequestProperty(String key) {
1565: // don't return headers containing security sensitive information
1566: if (key != null) {
1567: for (int i = 0; i < EXCLUDE_HEADERS.length; i++) {
1568: if (key.equalsIgnoreCase(EXCLUDE_HEADERS[i])) {
1569: return null;
1570: }
1571: }
1572: }
1573: return requests.findValue(key);
1574: }
1575:
1576: /**
1577: * Returns an unmodifiable Map of general request
1578: * properties for this connection. The Map keys
1579: * are Strings that represent the request-header
1580: * field names. Each Map value is a unmodifiable List
1581: * of Strings that represents the corresponding
1582: * field values.
1583: *
1584: * @return a Map of the general request properties for this connection.
1585: * @throws IllegalStateException if already connected
1586: * @since 1.4
1587: */
1588: public Map getRequestProperties() {
1589: if (connected)
1590: throw new IllegalStateException("Already connected");
1591:
1592: // exclude headers containing security-sensitive info
1593: return requests.getHeaders(EXCLUDE_HEADERS);
1594: }
1595:
1596: public void finalize() {
1597: // this should do nothing. The stream finalizer will close
1598: // the fd
1599: }
1600:
1601: String getMethod() {
1602: return method;
1603: }
1604:
1605: /* The purpose of this wrapper is just to capture the close() call
1606: * so we can check authentication information that may have
1607: * arrived in a Trailer field
1608: */
1609: class HttpInputStream extends FilterInputStream {
1610:
1611: public HttpInputStream(InputStream is) {
1612: super (is);
1613: }
1614:
1615: public void close() throws IOException {
1616: try {
1617: super .close();
1618: } finally {
1619: HttpURLConnection.this .http = null;
1620: checkResponseCredentials(true);
1621: }
1622: }
1623: }
1624: }
1625:
1626: /** An input stream that just returns EOF. This is for
1627: * HTTP URLConnections that are KeepAlive && use the
1628: * HEAD method - i.e., stream not dead, but nothing to be read.
1629: */
1630:
1631: class EmptyInputStream extends InputStream {
1632:
1633: public int available() {
1634: return 0;
1635: }
1636:
1637: public int read() {
1638: return -1;
1639: }
1640: }
|