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]

Reply via email to