You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ol...@apache.org on 2004/04/12 12:30:47 UTC

cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestRedirects.java TestNoHost.java TestWebappRedirect.java

olegk       2004/04/12 03:30:46

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        HttpMethodDirector.java
               httpclient/src/java/org/apache/commons/httpclient/params
                        HttpClientParams.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestNoHost.java TestWebappRedirect.java
  Added:       httpclient/src/java/org/apache/commons/httpclient
                        RedirectException.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestRedirects.java
  Log:
  PR #21216 (Redirect 302 to the same URL causes max redirects exception)
  
  Changelog:
  * Circular redirect check added
  * Maximum redirect attempts can now be changed by setting a parameter in the HttpParams collection
  * The circular redirect check is on per default and in lenient mode, is off in
  strict mode
  * Authentication attempts excluded from the redirect count and do not affect the
  max redirect check.
  
  Contributed by Oleg Kalnichevski
  Reviewed by Michael Becke
  
  Revision  Changes    Path
  1.20      +34 -28    jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java
  
  Index: HttpMethodDirector.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v
  retrieving revision 1.19
  retrieving revision 1.20
  diff -u -r1.19 -r1.20
  --- HttpMethodDirector.java	25 Mar 2004 20:37:19 -0000	1.19
  +++ HttpMethodDirector.java	12 Apr 2004 10:30:46 -0000	1.20
  @@ -32,7 +32,9 @@
   package org.apache.commons.httpclient;
   
   import java.io.IOException;
  +import java.util.HashSet;
   import java.util.Map;
  +import java.util.Set;
   
   import org.apache.commons.httpclient.auth.AuthChallengeException;
   import org.apache.commons.httpclient.auth.AuthChallengeParser;
  @@ -66,9 +68,6 @@
       /** The proxy authenticate response header. */
       public static final String PROXY_AUTH_RESP = "Proxy-Authorization";
   
  -    /** Maximum number of redirects and authentications that will be followed */
  -    private static final int MAX_FORWARDS = 100;
  -
       private static final Log LOG = LogFactory.getLog(HttpMethodDirector.class);
   
       private ConnectMethod connectMethod;
  @@ -98,6 +97,8 @@
       /** Authentication processor */
       private AuthChallengeProcessor authProcessor = null;
   
  +    private Set redirectLocations = null; 
  +    
       public HttpMethodDirector(
           final HttpConnectionManager connectionManager,
           final HostConfiguration hostConfiguration,
  @@ -125,13 +126,9 @@
           }
           method.getParams().setDefaults(this.params);
           try {
  -            int forwardCount = 0; //protect from an infinite loop
  +            int maxRedirects = this.params.getIntParameter(HttpClientParams.MAX_REDIRECTS, 100);
   
  -            while (forwardCount++ < MAX_FORWARDS) {
  -                // on every retry, reset this state information.
  -                if (LOG.isDebugEnabled()) {
  -                    LOG.debug("Execute loop try " + forwardCount);
  -                }
  +            for (int redirectCount = 0;;) {
   
                   // make sure the connection we have is appropriate
                   if (this.conn != null && !hostConfiguration.hostEquals(this.conn)) {
  @@ -169,6 +166,15 @@
                   if (isRedirectNeeded(method)) {
                       if (processRedirectResponse(method)) {
                           retry = true;
  +                        ++redirectCount;
  +                        if (redirectCount >= maxRedirects) {
  +                            LOG.error("Narrowly avoided an infinite loop in execute");
  +                            throw new RedirectException("Maximum redirects ("
  +                                + maxRedirects + ") exceeded");
  +                        }
  +                        if (LOG.isDebugEnabled()) {
  +                            LOG.debug("Execute redirect " + redirectCount + " of " + maxRedirects);
  +                        }
                       }
                   }
                   if (isAuthenticationNeeded(method)) {
  @@ -189,13 +195,6 @@
                   }
   
               } //end of retry loop
  -
  -            if (forwardCount >= MAX_FORWARDS) {
  -                LOG.error("Narrowly avoided an infinite loop in execute");
  -                throw new ProtocolException("Maximum redirects ("
  -                    + MAX_FORWARDS + ") exceeded");
  -            }
  -
           } finally {
               if (this.conn != null) {
                   this.conn.setLocked(false);
  @@ -279,7 +278,6 @@
   
       private void authenticateProxy(final HttpMethod method) throws AuthenticationException {
           // Clean up existing authentication headers
  -        // Clean up existing authentication headers
           if (!cleanAuthHeaders(method, PROXY_AUTH_RESP)) {
               // User defined authentication header(s) present
               return;
  @@ -494,8 +492,9 @@
        * 
   	 * @return <code>true</code> if the redirect was successful
   	 */
  -	private boolean processRedirectResponse(final HttpMethod method) {
  -
  +	private boolean processRedirectResponse(final HttpMethod method)
  +     throws RedirectException  
  +    {
   		//get the location header to find out where to redirect to
   		Header locationHeader = method.getResponseHeader("location");
   		if (locationHeader == null) {
  @@ -506,10 +505,9 @@
   		}
   		String location = locationHeader.getValue();
   		if (LOG.isDebugEnabled()) {
  -			LOG.debug("Redirect requested to location '" + location
  -					+ "'");
  +			LOG.debug("Redirect requested to location '" + location + "'");
   		}
  -
  +        
   		//rfc2616 demands the location value be a complete URI
   		//Location       = "Location" ":" absoluteURI
   		URI redirectUri = null;
  @@ -538,9 +536,17 @@
   			LOG.warn("Redirected location '" + location + "' is malformed");
   			return false;
   		}
  -		//update the current location with the redirect location.
  -		//avoiding use of URL.getPath() and URL.getQuery() to keep
  -		//jdk1.2 comliance.
  +
  +        if (this.params.isParameterFalse(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS)) {
  +            if (this.redirectLocations == null) {
  +                this.redirectLocations = new HashSet();
  +            }
  +            this.redirectLocations.add(currentUri);
  +            if (this.redirectLocations.contains(redirectUri)) {
  +                throw new RedirectException("Circular redirect to '" +
  +                    redirectUri + "'");   
  +            }
  +        }
   		method.setPath(redirectUri.getEscapedPath());
   		method.setQueryString(redirectUri.getEscapedQuery());
           hostConfiguration.setHost(redirectUri);
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/RedirectException.java
  
  Index: RedirectException.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/RedirectException.java,v 1.1 2004/04/12 10:30:46 olegk Exp $
   * $Revision: 1.1 $
   * $Date: 2004/04/12 10:30:46 $
   *
   * ====================================================================
   *
   *  Copyright 1999-2004 The Apache Software Foundation
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   */
  package org.apache.commons.httpclient;
  
  /**
   * Signals violation of HTTP specification caused by an invalid redirect
   * 
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   * 
   * @since 3.0
   */
  public class RedirectException extends ProtocolException {
  
      /**
       * Creates a new RedirectException with a <tt>null</tt> detail message. 
       */
      public RedirectException() {
          super();
      }
  
      /**
       * Creates a new RedirectException with the specified detail message.
       * 
       * @param message The exception detail message
       */
      public RedirectException(String message) {
          super(message);
      }
  
      /**
       * Creates a new RedirectException with the specified detail message and cause.
       * 
       * @param message the exception detail message
       * @param cause the <tt>Throwable</tt> that caused this exception, or <tt>null</tt>
       * if the cause is unavailable, unknown, or not a <tt>Throwable</tt>
       */
      public RedirectException(String message, Throwable cause) {
          super(message, cause);
      }
  }
  
  
  
  1.5       +33 -5     jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpClientParams.java
  
  Index: HttpClientParams.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpClientParams.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- HttpClientParams.java	22 Feb 2004 18:08:48 -0000	1.4
  +++ HttpClientParams.java	12 Apr 2004 10:30:46 -0000	1.5
  @@ -54,7 +54,9 @@
        * Sets the timeout in milliseconds used when retrieving an 
        * {@link org.apache.commons.httpclient.HttpConnection HTTP connection} from the
        * {@link org.apache.commons.httpclient.HttpConnectionManager HTTP connection manager}.
  +     * <p>
        * This parameter expects a value of type {@link Long}.
  +     * </p>
        */ 
       public static final String CONNECTION_MANAGER_TIMEOUT = "http.connection-manager.timeout"; 
   
  @@ -62,22 +64,47 @@
        * Defines the default 
        * {@link org.apache.commons.httpclient.HttpConnectionManager HTTP connection manager}
        * class.
  +     * <p>
        * This parameter expects a value of type {@link Class}.
  +     * </p>
        */ 
       public static final String CONNECTION_MANAGER_CLASS = "http.connection-manager.class"; 
   
       /**
        * Defines whether authentication should be attempted preemptively.
  +     * <p>
        * This parameter expects a value of type {@link Boolean}.
  +     * </p>
        */
       public static final String PREEMPTIVE_AUTHENTICATION = "http.authentication.preemptive";
   
       /**
        * Defines whether relative redirects should be rejected.
  +     * <p>
        * This parameter expects a value of type {@link Boolean}.
  +     * </p>
        */
       public static final String REJECT_RELATIVE_REDIRECT = "http.protocol.reject-relative-redirect"; 
   
  +    /** 
  +     * Defines the maximum number of redirects to be followed. 
  +     * The limit on number of redirects is intended to prevent infinite loops. 
  +     * <p>
  +     * This parameter expects a value of type {@link Integer}.
  +     * </p>
  +     */
  +    public static final String MAX_REDIRECTS = "http.protocol.max-redirects";
  +
  +    /** 
  +     * Defines whether circular redirects (redirects to the same location) should be allowed. 
  +     * The HTTP spec is not sufficiently clear whether circular redirects are permitted, 
  +     * therefore optionally they can be enabled
  +     * <p>
  +     * This parameter expects a value of type {@link Boolean}.
  +     * </p>
  +     */
  +    public static final String ALLOW_CIRCULAR_REDIRECTS = "http.protocol.allow-circular-redirects";
  +
       /**
        * Creates a new collection of parameters with the collection returned
        * by {@link #getDefaultParams()} as a parent. The collection will defer
  @@ -171,7 +198,8 @@
       }
   
       private static final String[] PROTOCOL_STRICTNESS_PARAMETERS = {
  -        REJECT_RELATIVE_REDIRECT
  +        REJECT_RELATIVE_REDIRECT,
  +        ALLOW_CIRCULAR_REDIRECTS
       };
   
   
  
  
  
  1.34      +5 -4      jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java
  
  Index: TestNoHost.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v
  retrieving revision 1.33
  retrieving revision 1.34
  diff -u -r1.33 -r1.34
  --- TestNoHost.java	25 Mar 2004 20:37:20 -0000	1.33
  +++ TestNoHost.java	12 Apr 2004 10:30:46 -0000	1.34
  @@ -66,6 +66,7 @@
           suite.addTest(TestChallengeProcessor.suite());
           suite.addTest(TestAuthenticator.suite());
           suite.addTest(TestBasicAuth.suite());
  +        suite.addTest(TestRedirects.suite());
           suite.addTest(TestHttpUrlMethod.suite());
           suite.addTest(TestURI.suite());
           suite.addTest(TestURIUtil.suite());
  
  
  
  1.22      +5 -5      jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java
  
  Index: TestWebappRedirect.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebappRedirect.java,v
  retrieving revision 1.21
  retrieving revision 1.22
  diff -u -r1.21 -r1.22
  --- TestWebappRedirect.java	22 Feb 2004 18:08:50 -0000	1.21
  +++ TestWebappRedirect.java	12 Apr 2004 10:30:46 -0000	1.22
  @@ -179,7 +179,7 @@
           try {
               client.executeMethod(method);
               fail("Expected HTTPException");
  -        } catch (HttpException t) {
  +        } catch (ProtocolException t) {
               // expected
           }
           assertEquals(302,method.getStatusCode());
  
  
  
  1.1                  jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestRedirects.java
  
  Index: TestRedirects.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestRedirects.java,v 1.1 2004/04/12 10:30:46 olegk Exp $
   * $Revision: 1.1 $
   * $Date: 2004/04/12 10:30:46 $
   * ====================================================================
   *
   *  Copyright 1999-2004 The Apache Software Foundation
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient;
  
  import java.io.IOException;
  
  import junit.framework.Test;
  import junit.framework.TestSuite;
  
  import org.apache.commons.httpclient.methods.GetMethod;
  import org.apache.commons.httpclient.params.HttpClientParams;
  import org.apache.commons.httpclient.server.HttpService;
  import org.apache.commons.httpclient.server.RequestLine;
  import org.apache.commons.httpclient.server.SimpleRequest;
  import org.apache.commons.httpclient.server.SimpleResponse;
  
  /**
   * Basic authentication test cases.
   *
   * @author Oleg Kalnichevski
   * 
   * @version $Id: TestRedirects.java,v 1.1 2004/04/12 10:30:46 olegk Exp $
   */
  public class TestRedirects extends HttpClientTestBase {
  
      // ------------------------------------------------------------ Constructor
      public TestRedirects(String testName) {
          super(testName);
      }
  
      // ------------------------------------------------------------------- Main
      public static void main(String args[]) {
          String[] testCaseName = { TestRedirects.class.getName() };
          junit.textui.TestRunner.main(testCaseName);
      }
  
      // ------------------------------------------------------- TestCase Methods
  
      public static Test suite() {
          return new TestSuite(TestRedirects.class);
      }
  
      private class RedirectService implements HttpService {
  
          public RedirectService() {
              super();
          }
  
          public boolean process(final SimpleRequest request, final SimpleResponse response)
              throws IOException
          {
              RequestLine reqline = request.getRequestLine();
              if (reqline.getUri().equals("/circular-location1/")) {
                  response.setStatusLine("HTTP/1.1 302 Object moved");
                  response.addHeader(new Header("Location", "/circular-location2/"));
              } else if (reqline.getUri().equals("/circular-location2/")) {
                  response.setStatusLine("HTTP/1.1 302 Object moved");
                  response.addHeader(new Header("Location", "/circular-location1/"));
              } else if (reqline.getUri().equals("/location1/")) {
                  response.setStatusLine("HTTP/1.1 302 Object moved");
                  response.addHeader(new Header("Location", "/location2/"));
              } else if (reqline.getUri().equals("/location2/")) {
                  response.setStatusLine("HTTP/1.1 200 OK");
                  response.setBodyString("Successful redirect");
              } else {
                  response.setStatusLine("HTTP/1.1 404 Not Found");
              }
              return true;
          }
      }
  
      public void testMaxRedirectCheck() throws IOException {
          this.server.setHttpService(new RedirectService());
          GetMethod httpget = new GetMethod("/circular-location1/");
          try {
              this.client.getParams().setBooleanParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true);
              this.client.getParams().setIntParameter(HttpClientParams.MAX_REDIRECTS, 5);
              this.client.executeMethod(httpget);
              fail("RedirectException exception should have been thrown");
          }
          catch (RedirectException e) {
              // expected
          } finally {
              httpget.releaseConnection();
          }
      }
  
      public void testCircularRedirect() throws IOException {
          this.server.setHttpService(new RedirectService());
          GetMethod httpget = new GetMethod("/circular-location1/");
          try {
              this.client.getParams().setBooleanParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, false);
              this.client.executeMethod(httpget);
              fail("RedirectException exception should have been thrown");
          }
          catch (RedirectException e) {
              // expected
          } finally {
              httpget.releaseConnection();
          }
      }
  
      public void testRedirectLocation() throws IOException {
          this.server.setHttpService(new RedirectService());
          GetMethod httpget = new GetMethod("/location1/");
          try {
              this.client.executeMethod(httpget);
              assertEquals("/location2/", httpget.getPath());
              assertEquals(new URI("/location2/", false), httpget.getURI());
              System.out.println();
          } finally {
              httpget.releaseConnection();
          }
      }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org