1 /*
2  * ====================================================================
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  * ====================================================================
20  *
21  * This software consists of voluntary contributions made by many
22  * individuals on behalf of the Apache Software Foundation.  For more
23  * information on the Apache Software Foundation, please see
24  * <http://www.apache.org/>.
25  *
26  */

27
28 package org.apache.http.impl.client;
29
30 import java.net.URI;
31 import java.net.URISyntaxException;
32 import java.util.Arrays;
33
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36 import org.apache.http.Header;
37 import org.apache.http.HttpHost;
38 import org.apache.http.HttpRequest;
39 import org.apache.http.HttpResponse;
40 import org.apache.http.HttpStatus;
41 import org.apache.http.ProtocolException;
42 import org.apache.http.annotation.Contract;
43 import org.apache.http.annotation.ThreadingBehavior;
44 import org.apache.http.client.CircularRedirectException;
45 import org.apache.http.client.RedirectStrategy;
46 import org.apache.http.client.config.RequestConfig;
47 import org.apache.http.client.methods.HttpGet;
48 import org.apache.http.client.methods.HttpHead;
49 import org.apache.http.client.methods.HttpUriRequest;
50 import org.apache.http.client.methods.RequestBuilder;
51 import org.apache.http.client.protocol.HttpClientContext;
52 import org.apache.http.client.utils.URIUtils;
53 import org.apache.http.protocol.HttpContext;
54 import org.apache.http.util.Args;
55 import org.apache.http.util.Asserts;
56
57 /**
58  * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions
59  * on automatic redirection of entity enclosing methods such as POST and PUT imposed by the
60  * HTTP specification. {@code 302 Moved Temporarily}, {@code 301 Moved Permanently} and
61  * {@code 307 Temporary Redirect} status codes will result in an automatic redirect of
62  * HEAD and GET methods only. POST and PUT methods will not be automatically redirected
63  * as requiring user confirmation.
64  * <p>
65  * The restriction on automatic redirection of POST methods can be relaxed by using
66  * {@link LaxRedirectStrategy} instead of {@link DefaultRedirectStrategy}.
67  * </p>
68  *
69  * @see LaxRedirectStrategy
70  * @since 4.1
71  */

72 @Contract(threading = ThreadingBehavior.IMMUTABLE)
73 public class DefaultRedirectStrategy implements RedirectStrategy {
74
75     private final Log log = LogFactory.getLog(getClass());
76
77     public static final int SC_PERMANENT_REDIRECT = 308;
78
79     /**
80      * @deprecated (4.3) use {@link org.apache.http.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}.
81      */

82     @Deprecated
83     public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations";
84
85     public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy();
86
87     private final String[] redirectMethods;
88
89     public DefaultRedirectStrategy() {
90         this(new String[] {
91             HttpGet.METHOD_NAME,
92             HttpHead.METHOD_NAME
93         });
94     }
95
96     /**
97      * Constructs a new instance to redirect the given HTTP methods.
98      *
99      * @param redirectMethods The methods to redirect.
100      * @since 4.5.10
101      */

102     public DefaultRedirectStrategy(final String[] redirectMethods) {
103         super();
104         final String[] tmp = redirectMethods.clone();
105         Arrays.sort(tmp);
106         this.redirectMethods = tmp;
107     }
108
109     @Override
110     public boolean isRedirected(
111             final HttpRequest request,
112             final HttpResponse response,
113             final HttpContext context) throws ProtocolException {
114         Args.notNull(request, "HTTP request");
115         Args.notNull(response, "HTTP response");
116
117         final int statusCode = response.getStatusLine().getStatusCode();
118         final String method = request.getRequestLine().getMethod();
119         final Header locationHeader = response.getFirstHeader("location");
120         switch (statusCode) {
121         case HttpStatus.SC_MOVED_TEMPORARILY:
122             return isRedirectable(method) && locationHeader != null;
123         case HttpStatus.SC_MOVED_PERMANENTLY:
124         case HttpStatus.SC_TEMPORARY_REDIRECT:
125         case SC_PERMANENT_REDIRECT:
126             return isRedirectable(method);
127         case HttpStatus.SC_SEE_OTHER:
128             return true;
129         default:
130             return false;
131         } //end of switch
132     }
133
134     public URI getLocationURI(
135             final HttpRequest request,
136             final HttpResponse response,
137             final HttpContext context) throws ProtocolException {
138         Args.notNull(request, "HTTP request");
139         Args.notNull(response, "HTTP response");
140         Args.notNull(context, "HTTP context");
141
142         final HttpClientContext clientContext = HttpClientContext.adapt(context);
143
144         //get the location header to find out where to redirect to
145         final Header locationHeader = response.getFirstHeader("location");
146         if (locationHeader == null) {
147             // got a redirect response, but no location header
148             throw new ProtocolException(
149                     "Received redirect response " + response.getStatusLine()
150                     + " but no location header");
151         }
152         final String location = locationHeader.getValue();
153         if (this.log.isDebugEnabled()) {
154             this.log.debug("Redirect requested to location '" + location + "'");
155         }
156
157         final RequestConfig config = clientContext.getRequestConfig();
158
159         URI uri = createLocationURI(location);
160
161         try {
162             if (config.isNormalizeUri()) {
163                 uri = URIUtils.normalizeSyntax(uri);
164             }
165
166             // rfc2616 demands the location value be a complete URI
167             // Location       = "Location" ":" absoluteURI
168             if (!uri.isAbsolute()) {
169                 if (!config.isRelativeRedirectsAllowed()) {
170                     throw new ProtocolException("Relative redirect location '"
171                             + uri + "' not allowed");
172                 }
173                 // Adjust location URI
174                 final HttpHost target = clientContext.getTargetHost();
175                 Asserts.notNull(target, "Target host");
176                 final URI requestURI = new URI(request.getRequestLine().getUri());
177                 final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target,
178                     config.isNormalizeUri() ? URIUtils.NORMALIZE : URIUtils.NO_FLAGS);
179                 uri = URIUtils.resolve(absoluteRequestURI, uri);
180             }
181         } catch (final URISyntaxException ex) {
182             throw new ProtocolException(ex.getMessage(), ex);
183         }
184
185         RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute(
186                 HttpClientContext.REDIRECT_LOCATIONS);
187         if (redirectLocations == null) {
188             redirectLocations = new RedirectLocations();
189             context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations);
190         }
191         if (!config.isCircularRedirectsAllowed()) {
192             if (redirectLocations.contains(uri)) {
193                 throw new CircularRedirectException("Circular redirect to '" + uri + "'");
194             }
195         }
196         redirectLocations.add(uri);
197         return uri;
198     }
199
200     /**
201      * @since 4.1
202      */

203     protected URI createLocationURI(final String location) throws ProtocolException {
204         try {
205             return new URI(location);
206         } catch (final URISyntaxException ex) {
207             throw new ProtocolException("Invalid redirect URI: " + location, ex);
208         }
209     }
210
211     /**
212      * @since 4.2
213      */

214     protected boolean isRedirectable(final String method) {
215         return Arrays.binarySearch(redirectMethods, method) >= 0;
216     }
217
218     @Override
219     public HttpUriRequest getRedirect(
220             final HttpRequest request,
221             final HttpResponse response,
222             final HttpContext context) throws ProtocolException {
223         final URI uri = getLocationURI(request, response, context);
224         final String method = request.getRequestLine().getMethod();
225         if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) {
226             return new HttpHead(uri);
227         } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) {
228             return new HttpGet(uri);
229         } else {
230             final int status = response.getStatusLine().getStatusCode();
231             return (status == HttpStatus.SC_TEMPORARY_REDIRECT || status == SC_PERMANENT_REDIRECT)
232                             ? RequestBuilder.copy(request).setUri(uri).build()
233                             : new HttpGet(uri);
234         }
235     }
236
237 }
238