Folks I am currently working on a patch enabling HttpClient to handle cross-site redirects.
http://nagoya.apache.org/bugzilla/show_bug.cgi?id=16729 In order to lay foundation for this capability I needed to make quite a few changes to HttpClient & HttpMethodBase. I have opted for a more substantial overhaul of these classes, than was strictly necessary. I realize not all of you may agree with my decision. So, I decided to seek an early feedback from you to make sure I do not go completely astray. This is what I have done: I moved complete redirect & authenticate logic from HttpMethodBase to HttpClient. HttpMethodBase Impact: - Even though binary interface is unchanged, HttpClient's modus operandi with regard to redirect & authentication changed substantially. People like Laura Werner,who do not use standard HttpClient and have developed their own logic around lower level classes will be affected most. - Cleaner design. Redirect & authentication in my opinion logically do not belong to domain of the HTTP method, rather, they belong to that of the HTTP agent. - Over-convoluted HttpMethodBase class got simpler. Under-used HttpClient class is leveraged more. This is an important architectural improvement in my humble opinion. If you disagree, please let me know - I am seriously concerned that this redesign may have adversely affected connection pooling stuff. Mike, Eric, you are the connection pooling experts, could you please give me your opinion on that? - About a dozen of test cases have become obsolete. They will need to be redesigned. They are all commented out for the time being As always, any feedback, including that in a form of bad tomatoes thrown at me will be appreciated Please note, that cross-site redirect has not been implemented yet. Cheers Oleg
Index: java/org/apache/commons/httpclient/HttpClient.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v retrieving revision 1.69 diff -u -r1.69 HttpClient.java --- java/org/apache/commons/httpclient/HttpClient.java 21 Feb 2003 13:48:09 -0000 1.69 +++ java/org/apache/commons/httpclient/HttpClient.java 25 Feb 2003 19:49:47 -0000 @@ -63,10 +63,14 @@ package org.apache.commons.httpclient; +import java.util.Set; +import java.util.HashSet; import java.io.IOException; import java.net.URL; - +import java.net.MalformedURLException; +import java.net.URL; import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.util.URIUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -85,6 +89,7 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Michael Becke</a> * @author <a href="mailto:[EMAIL PROTECTED]">Mike Bowler</a> * @author Sam Maloney + * @author <a href="mailto:[EMAIL PROTECTED]">Oleg Kalnichevski</a> * * @version $Revision: 1.69 $ $Date: 2003/02/21 13:48:09 $ */ @@ -97,6 +102,9 @@ /** Log object for this class. */ private static final Log LOG = LogFactory.getLog(HttpClient.class); + /** Maximum number of redirects and authentications that will be followed */ + private static final int MAX_FORWARDS = 100; + // ----------------------------------------------------------- Constructors /** @@ -149,6 +157,18 @@ /** True if strict mode is enabled. */ private boolean strictMode = false; + /** Whether or not I should automatically follow redirects. */ + private boolean followRedirects = true; + + /** Whether or not I should automatically processs authentication. */ + private boolean doAuthentication = true; + + /** Realms that we tried to authenticate to */ + private Set realms = null; + + /** Proxy Realms that we tried to authenticate to */ + private Set proxyRealms = null; + // ------------------------------------------------------------- Properties /** @@ -546,36 +566,73 @@ } } - HttpConnection connection = state.getHttpConnectionManager().getConnection( - methodConfiguration, - httpConnectionTimeout - ); - - try { - // Catch all possible exceptions to make sure to release the - // connection, as although the user may call - // Method->releaseConnection(), the method doesn't know about the - // connection until HttpMethod.execute() is called. - - method.setStrictMode(strictMode); + method.setStrictMode(strictMode); - if (!connection.isOpen()) { - connection.setSoTimeout(soTimeout); - connection.setConnectionTimeout(connectionTimeout); - connection.open(); - if (connection.isProxied() && connection.isSecure()) { - method = new ConnectMethod(method); + this.realms = new HashSet(); + this.proxyRealms = new HashSet(); + //pre-emptively add the authorization header, if required. + Authenticator.authenticate(method, state); + if (methodConfiguration.isProxySet()) { + Authenticator.authenticateProxy(method, state); + } + + int statuscode = 0; + int forwardCount = 0; //protect from an infinite loop + while (forwardCount++ < MAX_FORWARDS) { + // Obtain connection + HttpConnection connection = + state.getHttpConnectionManager().getConnection( + methodConfiguration, + httpConnectionTimeout); + try { + // Catch all possible exceptions to make sure to release the + // connection, as although the user may call + // Method->releaseConnection(), the method doesn't know about the + // connection until HttpMethod.execute() is called. + + if (!connection.isOpen()) { + connection.setSoTimeout(soTimeout); + connection.setConnectionTimeout(connectionTimeout); + connection.open(); + if (connection.isProxied() && connection.isSecure()) { + method = new ConnectMethod(method); + } } + } catch (IOException e) { + connection.releaseConnection(); + throw e; + } catch (RuntimeException e) { + connection.releaseConnection(); + throw e; } - } catch (IOException e) { - connection.releaseConnection(); - throw e; - } catch (RuntimeException e) { + + statuscode = method.execute(state, connection); + + if (isRedirectNeeded(statuscode)) { + LOG.debug("Redirect required"); + if (!processRedirectResponse(method, connection)) { + break; + } + } + else if (isAuthenticationNeeded(statuscode)) { + LOG.debug("Authorization required"); + if (!processAuthenticationResponse(method, state)) { + break; + } + } else { + break; + } + connection.releaseConnection(); - throw e; + connection = null; + } - - return method.execute(state, connection); + if (forwardCount >= MAX_FORWARDS) { + LOG.error("Narrowly avoided an infinite loop in execute"); + throw new HttpRecoverableException("Maximum redirects (" + + MAX_FORWARDS + ") exceeded"); + } + return statuscode; } /** @@ -627,4 +684,302 @@ this.hostConfiguration = hostConfiguration; } + /** + * Set whether or not I should automatically follow HTTP redirects (status + * code 302, etc.) + * + * @param followRedirects true to follow redirects, false otherwise + */ + public void setFollowRedirects(boolean followRedirects) { + this.followRedirects = followRedirects; + } + + /** + * Whether or not I should automatically follow HTTP redirects (status code + * 302, etc.) + * + * @return <tt>true</tt> if I will automatically follow HTTP redirects + */ + public boolean getFollowRedirects() { + return this.followRedirects; + } + + /** + * Whether or not I should automatically process responses where + * authentication is required (status code 401, etc.) + * + * @return <tt>true</tt> if authentications will be processed automatically + * @since 2.0 + */ + public boolean getDoAuthentication() { + return doAuthentication; + } + + /** + * Set whether or not I should automatically process responses where + * authentication is required (status code 401, etc.) + * + * @param doAuthentication <tt>true</tt> to process authentications + * @since 2.0 + */ + public void setDoAuthentication(boolean doAuthentication) { + this.doAuthentication = doAuthentication; + } + + /** + * Return true if a redicrect is needed. + * @param statusCode The status code + * @return boolean true if a redicrect is needed. + */ + private boolean isRedirectNeeded(int statusCode) { + switch (statusCode) { + case HttpStatus.SC_MOVED_TEMPORARILY: + case HttpStatus.SC_MOVED_PERMANENTLY: + case HttpStatus.SC_SEE_OTHER: + case HttpStatus.SC_TEMPORARY_REDIRECT: + return true; + default: + return false; + } //end of switch + } + + /** + * Return true if a retry is needed. + * @param statusCode The status code + * @return boolean true if authentication is needed. + */ + private boolean isAuthenticationNeeded(int statusCode) { + switch (statusCode) { + case HttpStatus.SC_UNAUTHORIZED: + case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: + return true; + default: + return false; + } + } + + /** + * Process the redirect response. + * @param conn The connection to use. + * @return boolean true if the redirect was successful. + */ + + private boolean processRedirectResponse(HttpMethod method, HttpConnection conn) { + + if (!getFollowRedirects()) { + LOG.info("Redirect requested but followRedirects is disabled"); + return false; + } + if (!method.getFollowRedirects()) { + LOG.warn("Redirects are not allowed for " + method.getName() + " methods"); + return false; + } + + //get the location header to find out where to redirect to + Header locationHeader = method.getResponseHeader("location"); + if (locationHeader == null) { + // got a redirect response, but no location header + LOG.error("Received redirect response " + method.getStatusCode() + + " but no location header"); + return false; + } + String location = locationHeader.getValue(); + if (LOG.isDebugEnabled()) { + LOG.debug("Redirect requested to location '" + location + "'"); + } + + //rfc2616 demands the location value be a complete URI + //Location = "Location" ":" absoluteURI + URL redirectUrl = null; + URL currentUrl = null; + + try { + currentUrl = new URL(conn.getProtocol().getScheme(), + conn.getHost(), conn.getPort(), method.getPath()); + redirectUrl = new URL(location); + } catch (MalformedURLException e) { + if (isStrictMode()) { + LOG.warn("Redirected location '" + location + + "' is not acceptable in strict mode"); + return false; + } else { //location is incomplete, use current values for defaults + try { + LOG.debug("Redirect URL is not absolute - parsing as relative"); + redirectUrl = new URL(currentUrl, location); + } catch (MalformedURLException ex) { + LOG.warn("Redirected location '" + location + + "' is malformed"); + return false; + } + } + } + + //check for redirect to a different protocol, host or port + try { + checkValidRedirect(currentUrl, redirectUrl); + } catch (HttpException ex) { + //LOG the error and let the client handle the redirect + LOG.warn(ex.getMessage()); + return false; + } + + //update the current location with the redirect location. + //avoiding use of URL.getPath() and URL.getQuery() to keep + //jdk1.2 comliance. + method.setPath(URIUtil.getPath(redirectUrl.toString())); + method.setQueryString(URIUtil.getQuery(redirectUrl.toString())); + + if (LOG.isDebugEnabled()) { + LOG.debug("Redirecting from '" + currentUrl.toExternalForm() + + "' to '" + redirectUrl.toExternalForm()); + } + + return true; + } + + /** + * Check for a valid redirect given the current conn and new url. + * Redirect to a different protocol, host or port are checked for validity. + * + * @param currentUrl The current URL (redirecting from) + * @param redirectUrl The new URL to redirect to + * @throws HttpException if the redirect is invalid + * @since 2.0 + */ + private static void checkValidRedirect(URL currentUrl, URL redirectUrl) + throws HttpException { + LOG.trace("enter HttpMethodBase.checkValidRedirect(HttpConnection, URL)"); + + String oldProtocol = currentUrl.getProtocol(); + String newProtocol = redirectUrl.getProtocol(); + if (!oldProtocol.equals(newProtocol)) { + throw new HttpException("Redirect from protocol " + oldProtocol + + " to " + newProtocol + " is not supported"); + } + + String oldHost = currentUrl.getHost(); + String newHost = redirectUrl.getHost(); + if (!oldHost.equalsIgnoreCase(newHost)) { + throw new HttpException("Redirect from host " + oldHost + + " to " + newHost + " is not supported"); + } + + int oldPort = currentUrl.getPort(); + if (oldPort < 0) { + oldPort = getDefaultPort(oldProtocol); + } + int newPort = redirectUrl.getPort(); + if (newPort < 0) { + newPort = getDefaultPort(newProtocol); + } + if (oldPort != newPort) { + throw new HttpException("Redirect from port " + oldPort + + " to " + newPort + " is not supported"); + } + } + + + /** + * Returns the default port for the given protocol. + * + * @param protocol currently only http and https are recognized + * @return the default port of the given protocol or -1 if the + * protocol is not recognized. + * + * @since 2.0 + * + */ + private static int getDefaultPort(String protocol) { + String strProtocol = protocol.toLowerCase().trim(); + if (strProtocol.equals("http")) { + return 80; + } else if (strProtocol.equals("https")) { + return 443; + } + return -1; + } + + + /** + * process a response that requires authentication + * + * @param state the current state + * + * @return <tt>true</tt> if the request has updated to include new credentials, + * <tt>false</tt> if required credentials are not available + */ + private boolean processAuthenticationResponse(HttpMethod method, HttpState state) { + LOG.trace("enter HttpClient.processAuthenticationResponse(" + + "HttpState, HttpConnection)"); + + boolean authenticated = false; + + if (!this.doAuthentication) { + return authenticated; + } + + int statusCode = method.getStatusCode(); + // handle authentication required + Header wwwauth = null; + Set realmsUsed = null; + switch (statusCode) { + case HttpStatus.SC_UNAUTHORIZED: + wwwauth = method.getResponseHeader(Authenticator.WWW_AUTH); + realmsUsed = realms; + break; + case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: + wwwauth = method.getResponseHeader(Authenticator.PROXY_AUTH); + realmsUsed = proxyRealms; + break; + } + // if there was a header requesting authentication + if (null != wwwauth) { + String pathAndCreds = method.getPath() + ":" + wwwauth.getValue(); + if (realmsUsed.contains(pathAndCreds)) { + if (LOG.isInfoEnabled()) { + LOG.info("Already tried to authenticate to \"" + + wwwauth.getValue() + "\" but still receiving " + + statusCode + "."); + } + return authenticated; + } else { + realmsUsed.add(pathAndCreds); + } + + try { + //remove preemptive header and reauthenticate + switch (statusCode) { + case HttpStatus.SC_UNAUTHORIZED: + method.removeRequestHeader(Authenticator.WWW_AUTH_RESP); + authenticated = Authenticator.authenticate(method, state); + break; + case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: + method.removeRequestHeader(Authenticator.PROXY_AUTH_RESP); + authenticated = Authenticator.authenticateProxy(method, + state); + break; + } + } catch (HttpException httpe) { + LOG.warn(httpe.getMessage()); + return true; // finished request + } catch (UnsupportedOperationException uoe) { + LOG.warn(uoe.getMessage()); + //FIXME: should this return true? + } + + if (!authenticated) { + // won't be able to authenticate to this challenge + // without additional information + LOG.debug("HttpMethodBase.execute(): Server demands " + + "authentication credentials, but none are " + + "available, so aborting."); + } else { + LOG.debug("HttpMethodBase.execute(): Server demanded " + + "authentication credentials, will try again."); + // let's try it again, using the credentials + } + } + + return authenticated; + } } Index: java/org/apache/commons/httpclient/HttpMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethod.java,v retrieving revision 1.23 diff -u -r1.23 HttpMethod.java --- java/org/apache/commons/httpclient/HttpMethod.java 28 Jan 2003 04:40:20 -0000 1.23 +++ java/org/apache/commons/httpclient/HttpMethod.java 25 Feb 2003 19:49:48 -0000 @@ -206,7 +206,9 @@ /** * Set whether or not I should automatically follow HTTP redirects (status * code 302, etc.) - * @param followRedirects True if I should automatically follow redirects. + * @param followRedirects ignored. + * + * @deprecated use HttpClient#setFollowRedirects(boolean) */ void setFollowRedirects(boolean followRedirects); @@ -358,21 +360,19 @@ StatusLine getStatusLine(); /** - * Whether or not I should automatically process responses where - * authentication is required (status code 401, etc.) - * - * @return <tt>true</tt> if authentications will be processed automatically * @since 2.0 + * + * @deprecated use HttpClient#getDoAuthentication() */ boolean getDoAuthentication(); /** - * Set whether or not I should automatically process responses where - * authentication is required (status code 401, etc.) - * - * @param doAuthentication <tt>true</tt> to process authentications + * @param doAuthentication ignored. + * * @since 2.0 + * + * @deprecated use HttpClient#setDoAuthentication(boolean) */ - void setDoAuthentication(boolean doAuthentication); + void setDoAuthentication(boolean ignored); } Index: java/org/apache/commons/httpclient/HttpMethodBase.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.115 diff -u -r1.115 HttpMethodBase.java --- java/org/apache/commons/httpclient/HttpMethodBase.java 21 Feb 2003 13:48:09 -0000 1.115 +++ java/org/apache/commons/httpclient/HttpMethodBase.java 25 Feb 2003 19:49:53 -0000 @@ -67,10 +67,6 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashSet; -import java.util.Set; import java.util.StringTokenizer; import org.apache.commons.httpclient.cookie.MalformedCookieException; @@ -137,11 +133,6 @@ */ public abstract class HttpMethodBase implements HttpMethod { - // ------------------------------------------ Static variables/initializers - - /** Maximum number of redirects and authentications that will be followed */ - private static final int MAX_FORWARDS = 100; - // -------------------------------------------------------------- Constants /** Log object for this class. */ @@ -174,12 +165,6 @@ /** My response trailer headers, if any. */ private HeaderGroup responseTrailerHeaders = new HeaderGroup(); - /** Realms that we tried to authenticate to */ - private Set realms = null; - - /** Proxy Realms that we tried to authenticate to */ - private Set proxyRealms = null; - /** My request path. */ private String path = null; @@ -198,12 +183,6 @@ /** Whether or not the request body has been sent. */ private boolean bodySent = false; - /** Whether or not I should automatically follow redirects. */ - private boolean followRedirects = false; - - /** Whether or not I should automatically processs authentication. */ - private boolean doAuthentication = true; - /** Whether or not I should use the HTTP/1.1 protocol. */ private boolean http11 = true; @@ -346,9 +325,10 @@ * code 302, etc.) * * @param followRedirects true to follow redirects, false otherwise + * + * @deprecated use HttpClient#setFollowRedirects(boolean) */ public void setFollowRedirects(boolean followRedirects) { - this.followRedirects = followRedirects; } /** @@ -357,8 +337,9 @@ * * @return <tt>true</tt> if I will automatically follow HTTP redirects */ + public boolean getFollowRedirects() { - return this.followRedirects; + return false; } /** @@ -370,15 +351,17 @@ this.http11 = http11; } - /** + /** * Whether or not I should automatically process responses where * authentication is required (status code 401, etc.) * * @return <tt>true</tt> if authentications will be processed automatically * @since 2.0 + * + * @deprecated use HttpClient#getDoAuthentication() */ public boolean getDoAuthentication() { - return doAuthentication; + return false; } /** @@ -387,9 +370,10 @@ * * @param doAuthentication <tt>true</tt> to process authentications * @since 2.0 + * + * @deprecated use HttpClient#setDoAuthentication(boolean) */ public void setDoAuthentication(boolean doAuthentication) { - this.doAuthentication = doAuthentication; } // ---------------------------------------------- Protected Utility Methods @@ -831,50 +815,7 @@ return false; } - - /** - * Return true if a retry is needed. - * @param statusCode The status code - * @param state The state. - * @param conn The connection - * @return boolean true if a retry is needed. - */ - private boolean isRetryNeeded(int statusCode, HttpState state, HttpConnection conn) { - switch (statusCode) { - case HttpStatus.SC_UNAUTHORIZED: - case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - LOG.debug("Authorization required"); - if (doAuthentication) { //process authentication response - //if the authentication is successful, return the statusCode - //otherwise, drop through the switch and try again. - if (processAuthenticationResponse(state)) { - return false; - } - } else { //let the client handle the authenticaiton - return false; - } - break; - - case HttpStatus.SC_MOVED_TEMPORARILY: - case HttpStatus.SC_MOVED_PERMANENTLY: - case HttpStatus.SC_SEE_OTHER: - case HttpStatus.SC_TEMPORARY_REDIRECT: - LOG.debug("Redirect required"); - - if (!processRedirectResponse(conn)) { - return false; - } - break; - - default: - // neither an unauthorized nor a redirect response - return false; - } //end of switch - - return true; - } - - /** + /** * Check to see if the this method is ready to be executed. * * @param state The state. @@ -891,7 +832,7 @@ throw new NullPointerException("HttpConnection parameter"); } if (hasBeenUsed()) { - throw new HttpException("Already used, but not recycled."); + //throw new HttpException("Already used, but not recycled."); } if (!validate()) { throw new HttpException("Not valid"); @@ -938,50 +879,11 @@ inExecute = true; try { - //pre-emptively add the authorization header, if required. - Authenticator.authenticate(this, state); - if (conn.isProxied()) { - Authenticator.authenticateProxy(this, state); - } - - realms = new HashSet(); - proxyRealms = new HashSet(); - int forwardCount = 0; //protect from an infinite loop - - while (forwardCount++ < MAX_FORWARDS) { - // on every retry, reset this state information. - conn.setLastResponseInputStream(null); - - if (LOG.isDebugEnabled()) { - LOG.debug("Execute loop try " + forwardCount); - } - - //write the request and read the response, will retry - processRequest(state, conn); - - //if SC_CONTINUE write the request body - writeRemainingRequestBody(state, conn); - - if (!isRetryNeeded(statusLine.getStatusCode(), state, conn)) { - // nope, no retry needed, exit loop. - break; - } - - // retry - close previous stream. Caution - this causes - // responseBodyConsumed to be called, which may also close the - // connection. - if (responseStream != null) { - responseStream.close(); - } - - } //end of retry loop - - if (forwardCount >= MAX_FORWARDS) { - LOG.error("Narrowly avoided an infinite loop in execute"); - throw new HttpRecoverableException("Maximum redirects (" - + MAX_FORWARDS + ") exceeded"); - } - + conn.setLastResponseInputStream(null); + //write the request and read the response, will retry + processRequest(state, conn); + //if SC_CONTINUE write the request body + writeRemainingRequestBody(state, conn); } finally { inExecute = false; // If the response has been fully processed, return the connection @@ -993,151 +895,10 @@ ensureConnectionRelease(); } } - return statusLine.getStatusCode(); } /** - * Process the redirect response. - * @param conn The connection to use. - * @return boolean true if the redirect was successful. - */ - private boolean processRedirectResponse(HttpConnection conn) { - - if (!getFollowRedirects()) { - LOG.info("Redirect requested but followRedirects is " - + "disabled"); - return false; - } - - //get the location header to find out where to redirect to - Header locationHeader = getResponseHeader("location"); - if (locationHeader == null) { - // got a redirect response, but no location header - LOG.error("Received redirect response " + getStatusCode() - + " but no location header"); - return false; - } - String location = locationHeader.getValue(); - if (LOG.isDebugEnabled()) { - LOG.debug("Redirect requested to location '" + location - + "'"); - } - - //rfc2616 demands the location value be a complete URI - //Location = "Location" ":" absoluteURI - URL redirectUrl = null; - URL currentUrl = null; - - try { - currentUrl = new URL(conn.getProtocol().getScheme(), - conn.getHost(), conn.getPort(), this.getPath()); - redirectUrl = new URL(location); - } catch (MalformedURLException e) { - if (isStrictMode()) { - LOG.warn("Redirected location '" + location - + "' is not acceptable in strict mode"); - return false; - } else { //location is incomplete, use current values for defaults - try { - LOG.debug("Redirect URL is not absolute - parsing as relative"); - redirectUrl = new URL(currentUrl, location); - } catch (MalformedURLException ex) { - LOG.warn("Redirected location '" + location - + "' is malformed"); - return false; - } - } - } - - //check for redirect to a different protocol, host or port - try { - checkValidRedirect(currentUrl, redirectUrl); - } catch (HttpException ex) { - //LOG the error and let the client handle the redirect - LOG.warn(ex.getMessage()); - return false; - } - - //update the current location with the redirect location. - //avoiding use of URL.getPath() and URL.getQuery() to keep - //jdk1.2 comliance. - setPath(URIUtil.getPath(redirectUrl.toString())); - setQueryString(URIUtil.getQuery(redirectUrl.toString())); - - if (LOG.isDebugEnabled()) { - LOG.debug("Redirecting from '" + currentUrl.toExternalForm() - + "' to '" + redirectUrl.toExternalForm()); - } - - return true; - - } - - - /** - * Check for a valid redirect given the current conn and new url. - * Redirect to a different protocol, host or port are checked for validity. - * - * @param currentUrl The current URL (redirecting from) - * @param redirectUrl The new URL to redirect to - * @throws HttpException if the redirect is invalid - * @since 2.0 - */ - private static void checkValidRedirect(URL currentUrl, URL redirectUrl) - throws HttpException { - LOG.trace("enter HttpMethodBase.checkValidRedirect(HttpConnection, URL)"); - - String oldProtocol = currentUrl.getProtocol(); - String newProtocol = redirectUrl.getProtocol(); - if (!oldProtocol.equals(newProtocol)) { - throw new HttpException("Redirect from protocol " + oldProtocol - + " to " + newProtocol + " is not supported"); - } - - String oldHost = currentUrl.getHost(); - String newHost = redirectUrl.getHost(); - if (!oldHost.equalsIgnoreCase(newHost)) { - throw new HttpException("Redirect from host " + oldHost - + " to " + newHost + " is not supported"); - } - - int oldPort = currentUrl.getPort(); - if (oldPort < 0) { - oldPort = getDefaultPort(oldProtocol); - } - int newPort = redirectUrl.getPort(); - if (newPort < 0) { - newPort = getDefaultPort(newProtocol); - } - if (oldPort != newPort) { - throw new HttpException("Redirect from port " + oldPort - + " to " + newPort + " is not supported"); - } - } - - - /** - * Returns the default port for the given protocol. - * - * @param protocol currently only http and https are recognized - * @return the default port of the given protocol or -1 if the - * protocol is not recognized. - * - * @since 2.0 - * - */ - private static int getDefaultPort(String protocol) { - String proto = protocol.toLowerCase().trim(); - if (proto.equals("http")) { - return 80; - } else if (proto.equals("https")) { - return 443; - } - return -1; - } - - /** * Whether the object has been used and not recycled. * * @return <tt>true</tt> if I have been [EMAIL PROTECTED] #execute executed} but not @@ -1157,8 +918,6 @@ releaseConnection(); path = null; - followRedirects = false; - doAuthentication = true; queryString = null; getRequestHeaderGroup().clear(); getResponseHeaderGroup().clear(); @@ -2105,43 +1864,6 @@ } /** - * Determines if the provided value is a valid IPv4 internet address. - * - * @param value - value to check - * - * @return boolean - true if value is valid, otherwise false - */ - private static boolean isIpAddress(String value) { - LOG.trace("enter HttpMethodBase.isIpAddress(String)"); - - value = value.trim(); - - // prevent input values of 127.0.0.1. or .127.0.0.1, etc. - if (value.startsWith(".") || value.endsWith(".")) { - return false; - } - - StringTokenizer tokenizer = new StringTokenizer(value, "."); - if (tokenizer.countTokens() == 4) { - while (tokenizer.hasMoreTokens()) { - try { - int i = Integer.parseInt(tokenizer.nextToken()); - if ((i < 0) || (i > 255)) { - // parsed section of address is not in the proper range - return false; - } - } catch (NumberFormatException nfe) { - return false; - } - } - } else { - // wrong number of tokens - return false; - } - return true; - } - - /** * Per RFC 2616 section 4.3, some response can never contain a message * body. * @@ -2161,84 +1883,6 @@ } return result; - } - - /** - * process a response that requires authentication - * - * @param state the current state - * - * @return true if the request has completed process, false if more - * attempts are needed - */ - private boolean processAuthenticationResponse(HttpState state) { - LOG.trace("enter HttpMethodBase.processAuthenticationResponse(" - + "HttpState, HttpConnection)"); - - int statusCode = statusLine.getStatusCode(); - // handle authentication required - Header wwwauth = null; - Set realmsUsed = null; - switch (statusCode) { - case HttpStatus.SC_UNAUTHORIZED: - wwwauth = getResponseHeader(Authenticator.WWW_AUTH); - realmsUsed = realms; - break; - case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - wwwauth = getResponseHeader(Authenticator.PROXY_AUTH); - realmsUsed = proxyRealms; - break; - } - boolean authenticated = false; - // if there was a header requesting authentication - if (null != wwwauth) { - String pathAndCreds = getPath() + ":" + wwwauth.getValue(); - if (realmsUsed.contains(pathAndCreds)) { - if (LOG.isInfoEnabled()) { - LOG.info("Already tried to authenticate to \"" - + wwwauth.getValue() + "\" but still receiving " - + statusCode + "."); - } - return true; - } else { - realmsUsed.add(pathAndCreds); - } - - try { - //remove preemptive header and reauthenticate - switch (statusCode) { - case HttpStatus.SC_UNAUTHORIZED: - removeRequestHeader(Authenticator.WWW_AUTH_RESP); - authenticated = Authenticator.authenticate(this, state); - break; - case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - removeRequestHeader(Authenticator.PROXY_AUTH_RESP); - authenticated = Authenticator.authenticateProxy(this, - state); - break; - } - } catch (HttpException httpe) { - LOG.warn(httpe.getMessage()); - return true; // finished request - } catch (UnsupportedOperationException uoe) { - LOG.warn(uoe.getMessage()); - //FIXME: should this return true? - } - - if (!authenticated) { - // won't be able to authenticate to this challenge - // without additional information - LOG.debug("HttpMethodBase.execute(): Server demands " - + "authentication credentials, but none are " - + "available, so aborting."); - } else { - LOG.debug("HttpMethodBase.execute(): Server demanded " - + "authentication credentials, will try again."); - // let's try it again, using the credentials - } - } - - return !authenticated; // finished processing if we aren't authenticated } /** Index: java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v retrieving revision 1.9 diff -u -r1.9 EntityEnclosingMethod.java --- java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java 13 Feb 2003 21:31:53 -0000 1.9 +++ java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java 25 Feb 2003 19:49:56 -0000 @@ -139,7 +139,6 @@ */ public EntityEnclosingMethod() { super(); - setFollowRedirects(false); } /** @@ -151,7 +150,6 @@ */ public EntityEnclosingMethod(String uri) { super(uri); - setFollowRedirects(false); } /** @@ -165,7 +163,6 @@ */ public EntityEnclosingMethod(String uri, String tempDir) { super(uri, tempDir); - setFollowRedirects(false); } /** @@ -180,7 +177,6 @@ */ public EntityEnclosingMethod(String uri, String tempDir, String tempFile) { super(uri, tempDir, tempFile); - setFollowRedirects(false); } @@ -195,24 +191,6 @@ public boolean getFollowRedirects() { return false; } - - - /** - * Entity enclosing requests cannot be redirected without user intervention - * according to RFC 2616. - * - * @param followRedirects must always be <code>false</code> - */ - public void setFollowRedirects(boolean followRedirects) { - if (followRedirects == true) { - // TODO: EntityEnclosingMethod should inherit from HttpMethodBase rather than GetMethod - // Enable exception once the inheritence is fixed - //throw new IllegalArgumentException( - // "Entity enclosing requests cannot be redirected without user intervention"); - } - super.setFollowRedirects(false); - } - /** * Returns the useExpectHeader. Index: java/org/apache/commons/httpclient/methods/GetMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/GetMethod.java,v retrieving revision 1.23 diff -u -r1.23 GetMethod.java --- java/org/apache/commons/httpclient/methods/GetMethod.java 2 Feb 2003 04:30:13 -0000 1.23 +++ java/org/apache/commons/httpclient/methods/GetMethod.java 25 Feb 2003 19:49:55 -0000 @@ -169,7 +169,7 @@ * @since 1.0 */ public GetMethod() { - setFollowRedirects(true); + super(); } /** @@ -182,7 +182,6 @@ public GetMethod(String uri) { super(uri); LOG.trace("enter GetMethod(String)"); - setFollowRedirects(true); } /** @@ -199,7 +198,6 @@ LOG.trace("enter GetMethod(String, String)"); setUseDisk(true); setTempDir(tempDir); - setFollowRedirects(true); } /** @@ -218,7 +216,6 @@ setUseDisk(true); setTempDir(tempDir); setTempFile(tempFile); - setFollowRedirects(true); } /** @@ -235,7 +232,6 @@ LOG.trace("enter GetMethod(String, File)"); useDisk = true; this.fileData = fileData; - setFollowRedirects(true); } //~ Methods О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫ @@ -279,6 +275,18 @@ } /** + * Whether or not I should automatically follow HTTP redirects (status code + * 302, etc.) + * + * @return <tt>true</tt> if I will automatically follow HTTP redirects + */ + public boolean getFollowRedirects() + { + return true; + } + + + /** * Return my response body, if any, as a byte array. Otherwise return * <tt>null</tt>. * @@ -400,7 +408,6 @@ super.recycle(); this.fileData = null; - setFollowRedirects(true); } // ----------------------------------------------------- HttpMethod Methods @@ -470,4 +477,14 @@ } return fileData; } + + /** + * @see org.apache.commons.httpclient.HttpMethod#execute(HttpState, HttpConnection) + */ + public int execute(HttpState state, HttpConnection connection) + throws HttpException, IOException + { + return super.execute(state, connection); + } + } Index: java/org/apache/commons/httpclient/methods/HeadMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/HeadMethod.java,v retrieving revision 1.17 diff -u -r1.17 HeadMethod.java --- java/org/apache/commons/httpclient/methods/HeadMethod.java 2 Feb 2003 04:30:13 -0000 1.17 +++ java/org/apache/commons/httpclient/methods/HeadMethod.java 25 Feb 2003 19:49:55 -0000 @@ -109,7 +109,7 @@ * @since 1.0 */ public HeadMethod() { - setFollowRedirects(true); + super(); } /** @@ -121,7 +121,6 @@ */ public HeadMethod(String uri) { super(uri); - setFollowRedirects(true); } //~ Methods О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫ @@ -138,13 +137,23 @@ } /** + * Whether or not I should automatically follow HTTP redirects (status code + * 302, etc.) + * + * @return <tt>true</tt> if I will automatically follow HTTP redirects + */ + public boolean getFollowRedirects() + { + return true; + } + + /** * Override recycle to reset redirects default. * * @since 1.0 */ public void recycle() { super.recycle(); - setFollowRedirects(true); } /** Index: java/org/apache/commons/httpclient/methods/TraceMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/TraceMethod.java,v retrieving revision 1.11 diff -u -r1.11 TraceMethod.java --- java/org/apache/commons/httpclient/methods/TraceMethod.java 2 Feb 2003 04:30:13 -0000 1.11 +++ java/org/apache/commons/httpclient/methods/TraceMethod.java 25 Feb 2003 19:49:56 -0000 @@ -109,7 +109,6 @@ */ public TraceMethod(String uri) { super(uri); - setFollowRedirects(false); } //~ Methods О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫О?╫ @@ -135,7 +134,6 @@ */ public void recycle() { super.recycle(); - setFollowRedirects(false); } } Index: test/org/apache/commons/httpclient/TestHttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java,v retrieving revision 1.5 diff -u -r1.5 TestHttpConnectionManager.java --- test/org/apache/commons/httpclient/TestHttpConnectionManager.java 20 Feb 2003 01:03:57 -0000 1.5 +++ test/org/apache/commons/httpclient/TestHttpConnectionManager.java 25 Feb 2003 19:49:45 -0000 @@ -156,7 +156,6 @@ client.setHttpConnectionFactoryTimeout( 1 ); GetMethod getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); try { client.executeMethod(getMethod); @@ -177,7 +176,6 @@ getMethod.releaseConnection(); getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); try { // this should fail quickly if the connection has not been released @@ -205,7 +203,6 @@ client.setHttpConnectionFactoryTimeout( 1 ); GetMethod getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); try { client.executeMethod(getMethod); @@ -217,7 +214,6 @@ getMethod.getResponseBody(); getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); try { // this should fail quickly if the connection has not been released @@ -241,7 +237,6 @@ client.setHttpConnectionFactoryTimeout( 1 ); GetMethod getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); try { client.executeMethod(getMethod); @@ -250,7 +245,6 @@ } GetMethod getMethod2 = new GetMethod("/"); - getMethod2.setFollowRedirects(true); try { // this should fail quickly since the connection has not been released @@ -274,7 +268,6 @@ client.setHttpConnectionFactoryTimeout( 30000 ); GetMethod getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); try { client.executeMethod(getMethod); @@ -283,7 +276,6 @@ } getMethod = new GetMethod("/"); - getMethod.setFollowRedirects(true); Runtime.getRuntime().gc(); Index: test/org/apache/commons/httpclient/TestNoHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v retrieving revision 1.19 diff -u -r1.19 TestNoHost.java --- test/org/apache/commons/httpclient/TestNoHost.java 28 Jan 2003 05:17:22 -0000 1.19 +++ test/org/apache/commons/httpclient/TestNoHost.java 25 Feb 2003 19:49:44 -0000 @@ -88,13 +88,13 @@ suite.addTest(TestNVP.suite()); suite.addTest(TestHeader.suite()); suite.addTest(TestHeaderElement.suite()); - suite.addTest(TestAuthenticator.suite()); + //suite.addTest(TestAuthenticator.suite()); suite.addTest(TestHttpUrlMethod.suite()); suite.addTest(TestURI.suite()); suite.addTest(TestURIUtil.suite()); suite.addTest(TestURIUtil2.suite()); suite.addTest(TestMethodsNoHost.suite()); - suite.addTest(TestMethodsRedirectNoHost.suite()); + //suite.addTest(TestMethodsRedirectNoHost.suite()); suite.addTest(TestHttpState.suite()); suite.addTest(TestResponseHeaders.suite()); suite.addTest(TestRequestHeaders.suite()); Index: test/org/apache/commons/httpclient/TestWebappRedirect.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java,v retrieving revision 1.17 diff -u -r1.17 TestWebappRedirect.java --- test/org/apache/commons/httpclient/TestWebappRedirect.java 2 Feb 2003 11:05:20 -0000 1.17 +++ test/org/apache/commons/httpclient/TestWebappRedirect.java 25 Feb 2003 19:49:42 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java,v 1.17 2003/02/02 11:05:20 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java,v 1.17 2003/02/02 11:05:20 olegk Exp $ * $Revision: 1.17 $ * $Date: 2003/02/02 11:05:20 $ * @@ -232,7 +232,6 @@ byte[] body = HttpConstants.getContentBytes(bodyStr); method.setRequestBody(new ByteArrayInputStream(body)); method.setRequestContentLength(body.length); //unbuffered request - method.setFollowRedirects(true); try { client.executeMethod(method); @@ -247,7 +246,6 @@ method.setQueryString("to=" + URIUtil.encodeWithinQuery(paramsUrl + "?foo=bar&bar=foo")); method.setRequestBody(new ByteArrayInputStream(body)); method.setRequestContentLength(PostMethod.CONTENT_LENGTH_AUTO); //buffered request - method.setFollowRedirects(true); try { client.executeMethod(method);
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]