001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.client; 029 030import java.net.URI; 031import java.net.URISyntaxException; 032import java.util.Locale; 033 034import org.apache.commons.logging.Log; 035import org.apache.commons.logging.LogFactory; 036import org.apache.http.Header; 037import org.apache.http.HttpHost; 038import org.apache.http.HttpRequest; 039import org.apache.http.HttpResponse; 040import org.apache.http.HttpStatus; 041import org.apache.http.ProtocolException; 042import org.apache.http.annotation.Contract; 043import org.apache.http.annotation.ThreadingBehavior; 044import org.apache.http.client.CircularRedirectException; 045import org.apache.http.client.RedirectStrategy; 046import org.apache.http.client.config.RequestConfig; 047import org.apache.http.client.methods.HttpGet; 048import org.apache.http.client.methods.HttpHead; 049import org.apache.http.client.methods.HttpUriRequest; 050import org.apache.http.client.methods.RequestBuilder; 051import org.apache.http.client.protocol.HttpClientContext; 052import org.apache.http.client.utils.URIBuilder; 053import org.apache.http.client.utils.URIUtils; 054import org.apache.http.protocol.HttpContext; 055import org.apache.http.util.Args; 056import org.apache.http.util.Asserts; 057import org.apache.http.util.TextUtils; 058 059/** 060 * Default implementation of {@link RedirectStrategy}. This strategy honors the restrictions 061 * on automatic redirection of entity enclosing methods such as POST and PUT imposed by the 062 * HTTP specification. {@code 302 Moved Temporarily}, {@code 301 Moved Permanently} and 063 * {@code 307 Temporary Redirect} status codes will result in an automatic redirect of 064 * HEAD and GET methods only. POST and PUT methods will not be automatically redirected 065 * as requiring user confirmation. 066 * <p> 067 * The restriction on automatic redirection of POST methods can be relaxed by using 068 * {@link LaxRedirectStrategy} instead of {@link DefaultRedirectStrategy}. 069 * </p> 070 * 071 * @see LaxRedirectStrategy 072 * @since 4.1 073 */ 074@Contract(threading = ThreadingBehavior.IMMUTABLE) 075public class DefaultRedirectStrategy implements RedirectStrategy { 076 077 private final Log log = LogFactory.getLog(getClass()); 078 079 /** 080 * @deprecated (4.3) use {@link org.apache.http.client.protocol.HttpClientContext#REDIRECT_LOCATIONS}. 081 */ 082 @Deprecated 083 public static final String REDIRECT_LOCATIONS = "http.protocol.redirect-locations"; 084 085 public static final DefaultRedirectStrategy INSTANCE = new DefaultRedirectStrategy(); 086 087 /** 088 * Redirectable methods. 089 */ 090 private static final String[] REDIRECT_METHODS = new String[] { 091 HttpGet.METHOD_NAME, 092 HttpHead.METHOD_NAME 093 }; 094 095 public DefaultRedirectStrategy() { 096 super(); 097 } 098 099 @Override 100 public boolean isRedirected( 101 final HttpRequest request, 102 final HttpResponse response, 103 final HttpContext context) throws ProtocolException { 104 Args.notNull(request, "HTTP request"); 105 Args.notNull(response, "HTTP response"); 106 107 final int statusCode = response.getStatusLine().getStatusCode(); 108 final String method = request.getRequestLine().getMethod(); 109 final Header locationHeader = response.getFirstHeader("location"); 110 switch (statusCode) { 111 case HttpStatus.SC_MOVED_TEMPORARILY: 112 return isRedirectable(method) && locationHeader != null; 113 case HttpStatus.SC_MOVED_PERMANENTLY: 114 case HttpStatus.SC_TEMPORARY_REDIRECT: 115 return isRedirectable(method); 116 case HttpStatus.SC_SEE_OTHER: 117 return true; 118 default: 119 return false; 120 } //end of switch 121 } 122 123 public URI getLocationURI( 124 final HttpRequest request, 125 final HttpResponse response, 126 final HttpContext context) throws ProtocolException { 127 Args.notNull(request, "HTTP request"); 128 Args.notNull(response, "HTTP response"); 129 Args.notNull(context, "HTTP context"); 130 131 final HttpClientContext clientContext = HttpClientContext.adapt(context); 132 133 //get the location header to find out where to redirect to 134 final Header locationHeader = response.getFirstHeader("location"); 135 if (locationHeader == null) { 136 // got a redirect response, but no location header 137 throw new ProtocolException( 138 "Received redirect response " + response.getStatusLine() 139 + " but no location header"); 140 } 141 final String location = locationHeader.getValue(); 142 if (this.log.isDebugEnabled()) { 143 this.log.debug("Redirect requested to location '" + location + "'"); 144 } 145 146 final RequestConfig config = clientContext.getRequestConfig(); 147 148 URI uri = createLocationURI(location); 149 150 // rfc2616 demands the location value be a complete URI 151 // Location = "Location" ":" absoluteURI 152 try { 153 if (!uri.isAbsolute()) { 154 if (!config.isRelativeRedirectsAllowed()) { 155 throw new ProtocolException("Relative redirect location '" 156 + uri + "' not allowed"); 157 } 158 // Adjust location URI 159 final HttpHost target = clientContext.getTargetHost(); 160 Asserts.notNull(target, "Target host"); 161 final URI requestURI = new URI(request.getRequestLine().getUri()); 162 final URI absoluteRequestURI = URIUtils.rewriteURI(requestURI, target, false); 163 uri = URIUtils.resolve(absoluteRequestURI, uri); 164 } 165 } catch (final URISyntaxException ex) { 166 throw new ProtocolException(ex.getMessage(), ex); 167 } 168 169 RedirectLocations redirectLocations = (RedirectLocations) clientContext.getAttribute( 170 HttpClientContext.REDIRECT_LOCATIONS); 171 if (redirectLocations == null) { 172 redirectLocations = new RedirectLocations(); 173 context.setAttribute(HttpClientContext.REDIRECT_LOCATIONS, redirectLocations); 174 } 175 if (!config.isCircularRedirectsAllowed()) { 176 if (redirectLocations.contains(uri)) { 177 throw new CircularRedirectException("Circular redirect to '" + uri + "'"); 178 } 179 } 180 redirectLocations.add(uri); 181 return uri; 182 } 183 184 /** 185 * @since 4.1 186 */ 187 protected URI createLocationURI(final String location) throws ProtocolException { 188 try { 189 final URIBuilder b = new URIBuilder(new URI(location).normalize()); 190 final String host = b.getHost(); 191 if (host != null) { 192 b.setHost(host.toLowerCase(Locale.ROOT)); 193 } 194 final String path = b.getPath(); 195 if (TextUtils.isEmpty(path)) { 196 b.setPath("/"); 197 } 198 return b.build(); 199 } catch (final URISyntaxException ex) { 200 throw new ProtocolException("Invalid redirect URI: " + location, ex); 201 } 202 } 203 204 /** 205 * @since 4.2 206 */ 207 protected boolean isRedirectable(final String method) { 208 for (final String m: REDIRECT_METHODS) { 209 if (m.equalsIgnoreCase(method)) { 210 return true; 211 } 212 } 213 return false; 214 } 215 216 @Override 217 public HttpUriRequest getRedirect( 218 final HttpRequest request, 219 final HttpResponse response, 220 final HttpContext context) throws ProtocolException { 221 final URI uri = getLocationURI(request, response, context); 222 final String method = request.getRequestLine().getMethod(); 223 if (method.equalsIgnoreCase(HttpHead.METHOD_NAME)) { 224 return new HttpHead(uri); 225 } else if (method.equalsIgnoreCase(HttpGet.METHOD_NAME)) { 226 return new HttpGet(uri); 227 } else { 228 final int status = response.getStatusLine().getStatusCode(); 229 if (status == HttpStatus.SC_TEMPORARY_REDIRECT) { 230 return RequestBuilder.copy(request).setUri(uri).build(); 231 } else { 232 return new HttpGet(uri); 233 } 234 } 235 } 236 237}