HTTPCLIENT-1344: Userinfo credentials in URI should not default to preemptive BASIC authentication

HTTPCLIENT-1345: Useinfo credentials ignored in redirect location header

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1483383 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2013-05-16 14:15:51 +00:00
parent 4add562f6c
commit 013d53a64f
8 changed files with 116 additions and 66 deletions

View File

@ -1,6 +1,13 @@
Changes since release 4.3 BETA1
-------------------
* [HTTPCLIENT-1344] Userinfo credentials in URI should not default to preemptive BASIC
authentication.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-1345] Useinfo credentials ignored in redirect location header.
Contributed by Oleg Kalnichevski <olegk at apache.org>
* [HTTPCLIENT-1294] HttpClient to rewrite host name of the redirect location URI in order
to avoid circular redirect exception due to host name case mismatch.
Contributed by Oleg Kalnichevski <olegk at apache.org>

View File

@ -131,6 +131,9 @@ public class URIUtils {
final HttpHost target,
final boolean dropFragment) throws URISyntaxException {
Args.notNull(uri, "URI");
if (uri.isOpaque()) {
return uri;
}
final URIBuilder uribuilder = new URIBuilder(uri);
if (target != null) {
uribuilder.setScheme(target.getSchemeName());
@ -174,14 +177,20 @@ public class URIUtils {
*/
public static URI rewriteURI(final URI uri) throws URISyntaxException {
Args.notNull(uri, "URI");
if (uri.isOpaque()) {
return uri;
}
final URIBuilder uribuilder = new URIBuilder(uri);
uribuilder.setFragment(null).setUserInfo(null);
if (uribuilder.getUserInfo() != null) {
uribuilder.setUserInfo(null);
}
if (TextUtils.isEmpty(uribuilder.getPath())) {
uribuilder.setPath("/");
}
if (uribuilder.getHost() != null) {
uribuilder.setHost(uribuilder.getHost().toLowerCase(Locale.ENGLISH));
}
uribuilder.setFragment(null);
return uribuilder.build();
}

View File

@ -144,8 +144,6 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
// rfc2616 demands the location value be a complete URI
// Location = "Location" ":" absoluteURI
try {
// Drop fragment
uri = URIUtils.rewriteURI(uri);
if (!uri.isAbsolute()) {
if (!config.isRelativeRedirectsAllowed()) {
throw new ProtocolException("Relative redirect location '"
@ -191,6 +189,7 @@ public class DefaultRedirectStrategy implements RedirectStrategy {
if (TextUtils.isEmpty(path)) {
b.setPath("/");
}
b.setFragment(null);
return b.build();
} catch (final URISyntaxException ex) {
throw new ProtocolException("Invalid redirect URI: " + location, ex);

View File

@ -50,7 +50,6 @@ import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.HttpClientParamConfig;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Lookup;
import org.apache.http.conn.ClientConnectionManager;
@ -126,26 +125,26 @@ class InternalHttpClient extends CloseableHttpClient {
}
private void setupContext(final HttpClientContext context) {
if (context.getAttribute(ClientContext.TARGET_AUTH_STATE) == null) {
context.setAttribute(ClientContext.TARGET_AUTH_STATE, new AuthState());
if (context.getAttribute(HttpClientContext.TARGET_AUTH_STATE) == null) {
context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, new AuthState());
}
if (context.getAttribute(ClientContext.PROXY_AUTH_STATE) == null) {
context.setAttribute(ClientContext.PROXY_AUTH_STATE, new AuthState());
if (context.getAttribute(HttpClientContext.PROXY_AUTH_STATE) == null) {
context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, new AuthState());
}
if (context.getAttribute(ClientContext.AUTHSCHEME_REGISTRY) == null) {
context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
if (context.getAttribute(HttpClientContext.AUTHSCHEME_REGISTRY) == null) {
context.setAttribute(HttpClientContext.AUTHSCHEME_REGISTRY, this.authSchemeRegistry);
}
if (context.getAttribute(ClientContext.COOKIESPEC_REGISTRY) == null) {
context.setAttribute(ClientContext.COOKIESPEC_REGISTRY, this.cookieSpecRegistry);
if (context.getAttribute(HttpClientContext.COOKIESPEC_REGISTRY) == null) {
context.setAttribute(HttpClientContext.COOKIESPEC_REGISTRY, this.cookieSpecRegistry);
}
if (context.getAttribute(ClientContext.COOKIE_STORE) == null) {
context.setAttribute(ClientContext.COOKIE_STORE, this.cookieStore);
if (context.getAttribute(HttpClientContext.COOKIE_STORE) == null) {
context.setAttribute(HttpClientContext.COOKIE_STORE, this.cookieStore);
}
if (context.getAttribute(ClientContext.CREDS_PROVIDER) == null) {
context.setAttribute(ClientContext.CREDS_PROVIDER, this.credentialsProvider);
if (context.getAttribute(HttpClientContext.CREDS_PROVIDER) == null) {
context.setAttribute(HttpClientContext.CREDS_PROVIDER, this.credentialsProvider);
}
if (context.getAttribute(ClientContext.REQUEST_CONFIG) == null) {
context.setAttribute(ClientContext.REQUEST_CONFIG, this.defaultConfig);
if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
context.setAttribute(HttpClientContext.REQUEST_CONFIG, this.defaultConfig);
}
}

View File

@ -38,8 +38,9 @@ import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.ProtocolException;
import org.apache.http.annotation.Immutable;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
@ -48,7 +49,6 @@ import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.util.Args;
@ -107,18 +107,23 @@ public class ProtocolExec implements ClientExecChain {
Args.notNull(request, "HTTP request");
Args.notNull(context, "HTTP context");
// Get user info from the URI
final AuthState targetAuthState = context.getTargetAuthState();
if (targetAuthState != null) {
final URI requestURI = request.getURI();
if (requestURI != null) {
final String userinfo = requestURI.getUserInfo();
if (userinfo != null) {
targetAuthState.update(new BasicScheme(), new UsernamePasswordCredentials(
userinfo));
final HttpRequest original = request.getOriginal();
URI uri = null;
if (original instanceof HttpUriRequest) {
uri = ((HttpUriRequest) original).getURI();
} else {
final String uriString = original.getRequestLine().getUri();
try {
uri = URI.create(uriString);
} catch (final IllegalArgumentException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug("Unable to parse '" + uriString + "' as a valid URI; " +
"request URI and Host header may be inconsistent", ex);
}
}
}
request.setURI(uri);
// Re-write request URI if needed
rewriteRequestURI(request, route);
@ -141,22 +146,6 @@ public class ProtocolExec implements ClientExecChain {
if (virtualHost != null) {
target = virtualHost;
} else {
final HttpRequest original = request.getOriginal();
URI uri = null;
if (original instanceof HttpUriRequest) {
uri = ((HttpUriRequest) original).getURI();
} else {
final String uriString = original.getRequestLine().getUri();
try {
uri = URI.create(uriString);
} catch (final IllegalArgumentException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug("Unable to parse '" + uriString + "' as a valid URI; " +
"request URI and Host header may be inconsistent", ex);
}
}
}
if (uri != null && uri.isAbsolute() && uri.getHost() != null) {
target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
}
@ -165,6 +154,17 @@ public class ProtocolExec implements ClientExecChain {
target = route.getTargetHost();
}
// Get user info from the URI
if (uri != null) {
final String userinfo = uri.getUserInfo();
if (userinfo != null) {
final CredentialsProvider credsProvider = context.getCredentialsProvider();
credsProvider.setCredentials(
new AuthScope(target),
new UsernamePasswordCredentials(userinfo));
}
}
// Run request protocol interceptors
context.setAttribute(HttpClientContext.HTTP_TARGET_HOST, target);
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);

View File

@ -71,6 +71,8 @@ public class TestURIUtils {
URI.create("http://thathost")).toString());
Assert.assertEquals("http://thathost/", URIUtils.rewriteURI(
URI.create("http://ThatHost")).toString());
Assert.assertEquals("http://That_Host/", URIUtils.rewriteURI(
URI.create("http://That_Host")).toString());
}
@Test

View File

@ -34,6 +34,7 @@ import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpInetConnection;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
@ -60,8 +61,10 @@ import org.apache.http.localserver.BasicAuthTokenExtractor;
import org.apache.http.localserver.LocalTestServer;
import org.apache.http.localserver.RequestBasicAuth;
import org.apache.http.localserver.ResponseBasicUnauthorized;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpProcessorBuilder;
@ -422,6 +425,43 @@ public class TestClientAuthentication extends IntegrationTestBase {
EntityUtils.consume(entity);
}
private static class RedirectHandler implements HttpRequestHandler {
public RedirectHandler() {
super();
}
public void handle(
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
final HttpInetConnection conn = (HttpInetConnection) context.getAttribute(HttpCoreContext.HTTP_CONNECTION);
final String localhost = conn.getLocalAddress().getHostName();
final int port = conn.getLocalPort();
response.setStatusCode(HttpStatus.SC_MOVED_PERMANENTLY);
response.addHeader(new BasicHeader("Location",
"http://test:test@" + localhost + ":" + port + "/"));
}
}
@Test
public void testAuthenticationUserinfoInRedirectSuccess() throws Exception {
this.localServer.register("*", new AuthHandler());
this.localServer.register("/thatway", new RedirectHandler());
final HttpHost target = getServerHttp();
final HttpGet httpget = new HttpGet("http://" + target.toHostString() + "/thatway");
this.httpclient = HttpClients.custom().build();
final HttpResponse response = this.httpclient.execute(getServerHttp(), httpget);
final HttpEntity entity = response.getEntity();
Assert.assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
Assert.assertNotNull(entity);
EntityUtils.consume(entity);
}
static class CountingAuthHandler implements HttpRequestHandler {
private final AtomicLong count;

View File

@ -37,7 +37,6 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolException;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.CircularRedirectException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
@ -92,19 +91,18 @@ public class TestRedirects extends IntegrationTestBase {
final HttpInetConnection conn = (HttpInetConnection) context.getAttribute(HttpCoreContext.HTTP_CONNECTION);
final String localhost = conn.getLocalAddress().getHostName();
final int port = conn.getLocalPort();
final ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
final String uri = request.getRequestLine().getUri();
if (uri.equals("/oldlocation/")) {
response.setStatusLine(ver, this.statuscode);
response.setStatusCode(this.statuscode);
response.addHeader(new BasicHeader("Location",
"http://" + localhost + ":" + port + "/newlocation/"));
response.addHeader(new BasicHeader("Connection", "close"));
} else if (uri.equals("/newlocation/")) {
response.setStatusLine(ver, HttpStatus.SC_OK);
response.setStatusCode(HttpStatus.SC_OK);
final StringEntity entity = new StringEntity("Successful redirect");
response.setEntity(entity);
} else {
response.setStatusLine(ver, HttpStatus.SC_NOT_FOUND);
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
}
}
@ -120,16 +118,15 @@ public class TestRedirects extends IntegrationTestBase {
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
final ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
final String uri = request.getRequestLine().getUri();
if (uri.startsWith("/circular-oldlocation")) {
response.setStatusLine(ver, HttpStatus.SC_MOVED_TEMPORARILY);
response.setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY);
response.addHeader(new BasicHeader("Location", "/circular-location2"));
} else if (uri.startsWith("/circular-location2")) {
response.setStatusLine(ver, HttpStatus.SC_MOVED_TEMPORARILY);
response.setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY);
response.addHeader(new BasicHeader("Location", "/circular-oldlocation"));
} else {
response.setStatusLine(ver, HttpStatus.SC_NOT_FOUND);
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
}
}
}
@ -144,17 +141,16 @@ public class TestRedirects extends IntegrationTestBase {
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
final ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
final String uri = request.getRequestLine().getUri();
if (uri.equals("/oldlocation/")) {
response.setStatusLine(ver, HttpStatus.SC_MOVED_TEMPORARILY);
response.setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY);
response.addHeader(new BasicHeader("Location", "/relativelocation/"));
} else if (uri.equals("/relativelocation/")) {
response.setStatusLine(ver, HttpStatus.SC_OK);
response.setStatusCode(HttpStatus.SC_OK);
final StringEntity entity = new StringEntity("Successful redirect");
response.setEntity(entity);
} else {
response.setStatusLine(ver, HttpStatus.SC_NOT_FOUND);
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
}
}
}
@ -169,17 +165,16 @@ public class TestRedirects extends IntegrationTestBase {
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
final ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
final String uri = request.getRequestLine().getUri();
if (uri.equals("/test/oldlocation")) {
response.setStatusLine(ver, HttpStatus.SC_MOVED_TEMPORARILY);
response.setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY);
response.addHeader(new BasicHeader("Location", "relativelocation"));
} else if (uri.equals("/test/relativelocation")) {
response.setStatusLine(ver, HttpStatus.SC_OK);
response.setStatusCode(HttpStatus.SC_OK);
final StringEntity entity = new StringEntity("Successful redirect");
response.setEntity(entity);
} else {
response.setStatusLine(ver, HttpStatus.SC_NOT_FOUND);
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
}
}
}
@ -196,17 +191,16 @@ public class TestRedirects extends IntegrationTestBase {
final HttpRequest request,
final HttpResponse response,
final HttpContext context) throws HttpException, IOException {
final ProtocolVersion ver = request.getRequestLine().getProtocolVersion();
final String uri = request.getRequestLine().getUri();
if (uri.equals("/oldlocation/")) {
response.setStatusLine(ver, HttpStatus.SC_MOVED_TEMPORARILY);
response.setStatusCode(HttpStatus.SC_MOVED_TEMPORARILY);
response.addHeader(new BasicHeader("Location", url));
} else if (uri.equals("/relativelocation/")) {
response.setStatusLine(ver, HttpStatus.SC_OK);
response.setStatusCode(HttpStatus.SC_OK);
final StringEntity entity = new StringEntity("Successful redirect");
response.setEntity(entity);
} else {
response.setStatusLine(ver, HttpStatus.SC_NOT_FOUND);
response.setStatusCode(HttpStatus.SC_NOT_FOUND);
}
}
}