From 7f12408be78bc02aef61acccb19738e6a88301e3 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Sat, 19 May 2007 17:19:49 +0000 Subject: [PATCH] Implemented authentication support for tunneling requests via an authenticating proxy git-svn-id: https://svn.apache.org/repos/asf/jakarta/httpcomponents/httpclient/trunk@539780 13f79535-47bb-0310-9956-ffa450edef68 --- .../client/DefaultClientRequestDirector.java | 125 ++++++++++++++---- 1 file changed, 98 insertions(+), 27 deletions(-) diff --git a/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java b/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java index ba723415a..7ff870483 100644 --- a/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java +++ b/src/java/org/apache/http/impl/client/DefaultClientRequestDirector.java @@ -68,6 +68,7 @@ import org.apache.http.client.methods.AbortableHttpRequest; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.params.HttpClientParams; import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.client.protocol.RequestProxyAuthentication; import org.apache.http.conn.BasicManagedEntity; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.ConnectionPoolTimeoutException; @@ -80,11 +81,13 @@ import org.apache.http.message.BasicHttpRequest; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; +import org.apache.http.protocol.BasicHttpProcessor; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpExecutionContext; import org.apache.http.protocol.HttpProcessor; import org.apache.http.protocol.HttpRequestExecutor; +import org.apache.http.protocol.RequestUserAgent; import org.apache.http.util.CharArrayBuffer; /** @@ -267,6 +270,8 @@ public class DefaultClientRequestDirector orig.addHeader((Header) it.next()); } } + + long timeout = HttpClientParams.getConnectionManagerTimeout(params); int execCount = 0; @@ -279,7 +284,7 @@ public class DefaultClientRequestDirector // Allocate connection if needed if (managedConn == null) { - managedConn = allocateConnection(route); + managedConn = allocateConnection(route, timeout); } // Reopen connection if needed if (!managedConn.isOpen()) { @@ -438,10 +443,9 @@ public class DefaultClientRequestDirector * * @throws HttpException in case of a problem */ - protected ManagedClientConnection allocateConnection(HttpRoute route) + protected ManagedClientConnection allocateConnection(HttpRoute route, long timeout) throws HttpException, ConnectionPoolTimeoutException { - long timeout = HttpClientParams.getConnectionManagerTimeout(params); return connManager.getConnection(route, timeout); } // allocateConnection @@ -468,15 +472,11 @@ public class DefaultClientRequestDirector //@@@ probably not, because the parent is replaced //@@@ just make sure we don't link parameters to themselves - //System.out.println("@@@ planned: " + route); - RouteDirector rowdy = new RouteDirector(); int step; do { HttpRoute fact = managedConn.getRoute(); - //System.out.println("@@@ current: " + fact); step = rowdy.nextStep(route, fact); - //System.out.println("@@@ action => " + step); switch (step) { @@ -487,6 +487,7 @@ public class DefaultClientRequestDirector case RouteDirector.CREATE_TUNNEL: boolean secure = createTunnel(route, context); + LOG.debug("Tunnel created"); managedConn.tunnelCreated(secure, requestExec.getParams()); break; @@ -536,29 +537,106 @@ public class DefaultClientRequestDirector protected boolean createTunnel(HttpRoute route, HttpContext context) throws HttpException, IOException { - HttpRequest connect = createConnectRequest(route, context); - //@@@ authenticate here, in method above, or in request interceptor? + HttpHost proxy = route.getProxyHost(); + HttpResponse response = null; + + boolean done = false; + while (!done) { - HttpResponse response = - requestExec.execute(connect, managedConn, context); - managedConn.markReusable(); - int status = response.getStatusLine().getStatusCode(); + done = true; + + if (!this.managedConn.isOpen()) { + this.managedConn.open(route, context, this.params); + } + + HttpRequest connect = createConnectRequest(route, context); + + String agent = HttpProtocolParams.getUserAgent(params); + if (agent != null) { + connect.addHeader(HTTP.USER_AGENT, agent); + } + + AuthScheme authScheme = this.proxyAuthState.getAuthScheme(); + AuthScope authScope = this.proxyAuthState.getAuthScope(); + Credentials creds = this.proxyAuthState.getCredentials(); + if (creds != null) { + if (authScope != null || !authScheme.isConnectionBased()) { + try { + connect.addHeader(authScheme.authenticate(creds, connect)); + } catch (AuthenticationException ex) { + if (LOG.isErrorEnabled()) { + LOG.error("Proxy authentication error: " + ex.getMessage()); + } + } + } + } + + response = requestExec.execute(connect, this.managedConn, context); + + int status = response.getStatusLine().getStatusCode(); + if (status < 200) { + throw new HttpException("Unexpected response to CONNECT request: " + + response.getStatusLine()); + } + + HttpState state = (HttpState) context.getAttribute(HttpClientContext.HTTP_STATE); + + if (state != null && HttpClientParams.isAuthenticating(params)) { + if (this.authHandler.isProxyAuthenticationRequested(response, context)) { - if (status < 200) { - throw new HttpException("Unexpected response to CONNECT request: " + - response.getStatusLine()); + LOG.debug("Proxy requested authentication"); + Map challenges = this.authHandler.getProxyChallenges(response, context); + try { + processChallenges(challenges, this.proxyAuthState, response, context); + } catch (AuthenticationException ex) { + if (LOG.isWarnEnabled()) { + LOG.warn("Authentication error: " + ex.getMessage()); + break; + } + } + updateAuthState(this.proxyAuthState, proxy, state); + + if (this.proxyAuthState.getCredentials() != null) { + done = false; + + // Retry request + if (this.reuseStrategy.keepAlive(response, context)) { + LOG.debug("Connection kept alive"); + // Consume response content + HttpEntity entity = response.getEntity(); + if (entity != null) { + entity.consumeContent(); + } + } else { + this.managedConn.close(); + } + + } + + } else { + // Reset proxy auth scope + this.proxyAuthState.setAuthScope(null); + } + } } - // Buffer response - if (response.getEntity() != null) { - response.setEntity(new BufferedHttpEntity(response.getEntity())); - } + int status = response.getStatusLine().getStatusCode(); if (status > 299) { + + // Buffer response content + HttpEntity entity = response.getEntity(); + if (entity != null) { + response.setEntity(new BufferedHttpEntity(entity)); + } + + this.managedConn.close(); throw new TunnelRefusedException("CONNECT refused by proxy: " + response.getStatusLine(), response); } + this.managedConn.markReusable(); + // How to decide on security of the tunnelled connection? // The socket factory knows only about the segment to the proxy. // Even if that is secure, the hop to the target may be insecure. @@ -602,13 +680,6 @@ public class DefaultClientRequestDirector HttpRequest req = new BasicHttpRequest ("CONNECT", authority, ver); - String agent = HttpProtocolParams.getUserAgent(params); - if (agent != null) { - req.addHeader(HTTP.USER_AGENT, agent); - } - - //@@@ authenticate here, in caller, or in request interceptor? - return req; }