Index: examples/ProxyTunnelDemo.java
===================================================================
RCS file: examples/ProxyTunnelDemo.java
diff -N examples/ProxyTunnelDemo.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ examples/ProxyTunnelDemo.java 4 Apr 2004 10:29:22 -0000
@@ -0,0 +1,76 @@
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.Socket;
+
+import org.apache.commons.httpclient.ProxyClient;
+import org.apache.commons.httpclient.UsernamePasswordCredentials;
+import org.apache.commons.httpclient.auth.HttpAuthRealm;
+
+/*
+ * $Header$
+ * $Revision$
+ * $Date$
+ * ====================================================================
+ *
+ * Copyright 2002-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
+ * .
+ *
+ * [Additional notices, if required by prior licensing conditions]
+ *
+ */
+
+/**
+ * @author Oleg Kalnichevski
+ */
+public class ProxyTunnelDemo {
+
+ public static void main(String[] args) throws Exception {
+
+ ProxyClient proxyclient = new ProxyClient();
+ proxyclient.getHostConfiguration().setHost("www.yahoo.com");
+ proxyclient.getHostConfiguration().setProxy("localhost", 8888);
+ proxyclient.getState().setProxyCredentials(
+ new HttpAuthRealm("localhost", 8888, "squid"),
+ new UsernamePasswordCredentials("squid", "squid"));
+ Socket socket = proxyclient.connect();
+ if (socket != null) {
+ try {
+ Writer out = new OutputStreamWriter(
+ socket.getOutputStream(), "ISO-8859-1");
+ out.write("GET http://www.yahoo.com/ HTTP/1.1\r\n");
+ out.write("Host: www.yahoo.com\r\n");
+ out.write("Agent: whatever\r\n");
+ out.write("\r\n");
+ out.flush();
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
+ String line = null;
+ while ((line = in.readLine()) != null) {
+ System.out.println(line);
+ }
+ } finally {
+ socket.close();
+ }
+ }
+ }
+
+}
Index: java/org/apache/commons/httpclient/HttpConnection.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v
retrieving revision 1.85
diff -u -r1.85 HttpConnection.java
--- java/org/apache/commons/httpclient/HttpConnection.java 22 Feb 2004 18:08:45 -0000 1.85
+++ java/org/apache/commons/httpclient/HttpConnection.java 4 Apr 2004 10:29:26 -0000
@@ -1,5 +1,5 @@
/*
- * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v 1.85 2004/02/22 18:08:45 olegk Exp $
+ * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v 1.85 2004/02/22 18:08:45 olegk Exp $
* $Revision: 1.85 $
* $Date: 2004/02/22 18:08:45 $
*
@@ -199,6 +199,15 @@
}
// ------------------------------------------ Attribute Setters and Getters
+
+ /**
+ * Returns the connection socket.
+ *
+ * @return the socket.
+ */
+ protected Socket getSocket() {
+ return this.socket;
+ }
/**
* Returns the host.
Index: java/org/apache/commons/httpclient/HttpMethodDirector.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v
retrieving revision 1.19
diff -u -r1.19 HttpMethodDirector.java
--- java/org/apache/commons/httpclient/HttpMethodDirector.java 25 Mar 2004 20:37:19 -0000 1.19
+++ java/org/apache/commons/httpclient/HttpMethodDirector.java 4 Apr 2004 10:29:28 -0000
@@ -1,5 +1,5 @@
/*
- * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.19 2004/03/25 20:37:19 olegk Exp $
+ * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.19 2004/03/25 20:37:19 olegk Exp $
* $Revision: 1.19 $
* $Date: 2004/03/25 20:37:19 $
*
@@ -278,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
Index: java/org/apache/commons/httpclient/ProxyClient.java
===================================================================
RCS file: java/org/apache/commons/httpclient/ProxyClient.java
diff -N java/org/apache/commons/httpclient/ProxyClient.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ java/org/apache/commons/httpclient/ProxyClient.java 4 Apr 2004 10:29:29 -0000
@@ -0,0 +1,410 @@
+/*
+ * $Header$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ *
+ * 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
+ * .
+ *
+ * [Additional notices, if required by prior licensing conditions]
+ *
+ */
+
+package org.apache.commons.httpclient;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.Map;
+
+import org.apache.commons.httpclient.auth.AuthChallengeException;
+import org.apache.commons.httpclient.auth.AuthChallengeParser;
+import org.apache.commons.httpclient.auth.AuthChallengeProcessor;
+import org.apache.commons.httpclient.auth.AuthScheme;
+import org.apache.commons.httpclient.auth.AuthState;
+import org.apache.commons.httpclient.auth.AuthenticationException;
+import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
+import org.apache.commons.httpclient.auth.CredentialsProvider;
+import org.apache.commons.httpclient.auth.HttpAuthRealm;
+import org.apache.commons.httpclient.params.HttpClientParams;
+import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.apache.commons.httpclient.params.HttpParams;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * @author Oleg Kalnichevski
+ *
+ * @version $Revision$
+ */
+public class ProxyClient {
+
+
+ // -------------------------------------------------------------- Constants
+
+ /** Log object for this class. */
+ private static final Log LOG = LogFactory.getLog(ProxyClient.class);
+
+ // ----------------------------------------------------- Instance Variables
+
+ /** The proxy authenticate response header. */
+ public static final String PROXY_AUTH_RESP = "Proxy-Authorization";
+
+ /** The proxy authenticate challange header. */
+ public static final String PROXY_AUTH_CHALLENGE = "Proxy-Authenticate";
+
+ /** Maximum number of authentications that will be followed */
+ private static final int MAX_FORWARDS = 100;
+
+ /**
+ * The {@link HttpState HTTP state} associated with this ProxyClient.
+ */
+ private HttpState state = new HttpState();
+
+ /**
+ * The {@link HttpClientParams collection of parameters} associated with this ProxyClient.
+ */
+ private HttpClientParams params = null;
+
+ /**
+ * The {@link HostConfiguration host configuration} associated with
+ * the ProxyClient
+ */
+ private HostConfiguration hostConfiguration = new HostConfiguration();
+
+ /** Authentication processor */
+ private AuthChallengeProcessor authProcessor = null;
+
+ private static final int AUTH_UNINITIATED = 0;
+ private static final int AUTH_PREEMPTIVE = 1;
+ private static final int AUTH_PROXY_REQUIRED = 3;
+ private static final int AUTH_NOT_REQUIRED = Integer.MAX_VALUE;
+
+ /** Actual state of authentication process */
+ private int authProcess = AUTH_UNINITIATED;
+ /**
+ * Creates an instance of ProxyClient using default {@link HttpClientParams parameter set}.
+ *
+ * @see HttpClientParams
+ */
+ public ProxyClient() {
+ this(new HttpClientParams());
+ }
+
+ /**
+ * Creates an instance of ProxyClient using the given
+ * {@link HttpClientParams parameter set}.
+ *
+ * @param params The {@link HttpClientParams parameters} to use.
+ *
+ * @see HttpClientParams
+ *
+ * @since 2.1
+ */
+ public ProxyClient(HttpClientParams params) {
+ super();
+ if (params == null) {
+ throw new IllegalArgumentException("Params may not be null");
+ }
+ this.params = params;
+ this.authProcessor = new AuthChallengeProcessor(this.params);
+ }
+
+ // ------------------------------------------------------------- Properties
+
+ /**
+ * Returns {@link HttpState HTTP state} associated with the ProxyClient.
+ *
+ * @see #setState(HttpState)
+ * @return the shared client state
+ */
+ public synchronized HttpState getState() {
+ return state;
+ }
+
+ /**
+ * Assigns {@link HttpState HTTP state} for the ProxyClient.
+ *
+ * @see #getState()
+ * @param state the new {@link HttpState HTTP state} for the client
+ */
+ public synchronized void setState(HttpState state) {
+ this.state = state;
+ }
+
+ /**
+ * Returns the {@link HostConfiguration host configuration} associated with the
+ * ProxyClient.
+ *
+ * @return {@link HostConfiguration host configuration}
+ *
+ * @since 2.0
+ */
+ public synchronized HostConfiguration getHostConfiguration() {
+ return hostConfiguration;
+ }
+
+ /**
+ * Assigns the {@link HostConfiguration host configuration} to use with the
+ * ProxyClient.
+ *
+ * @param hostConfiguration The {@link HostConfiguration host configuration} to set
+ *
+ * @since 2.0
+ */
+ public synchronized void setHostConfiguration(HostConfiguration hostConfiguration) {
+ this.hostConfiguration = hostConfiguration;
+ }
+
+ /**
+ * Returns {@link HttpClientParams HTTP protocol parameters} associated with this ProxyClient.
+ *
+ * @since 2.1
+ *
+ * @see HttpClientParams
+ */
+ public synchronized HttpClientParams getParams() {
+ return this.params;
+ }
+
+ /**
+ * Assigns {@link HttpClientParams HTTP protocol parameters} for this ProxyClient.
+ *
+ * @since 2.1
+ *
+ * @see HttpClientParams
+ */
+ public synchronized void setParams(final HttpClientParams params) {
+ if (params == null) {
+ throw new IllegalArgumentException("Parameters may not be null");
+ }
+ this.params = params;
+ }
+
+
+ private void authenticateProxy(final HttpMethod method, final HttpConnection conn) throws AuthenticationException {
+ method.removeRequestHeader(PROXY_AUTH_RESP);
+ AuthScheme authscheme = method.getProxyAuthState().getAuthScheme();
+ if (authscheme == null) {
+ return;
+ }
+ if ((this.authProcess == AUTH_PROXY_REQUIRED) || (!authscheme.isConnectionBased())) {
+ HttpAuthRealm realm = new HttpAuthRealm(
+ conn.getProxyHost(), conn.getProxyPort(),
+ authscheme.getRealm(),
+ authscheme.getSchemeName());
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Authenticating with " + realm);
+ }
+ Credentials credentials = this.state.getProxyCredentials(realm);
+ if (credentials != null) {
+ String authstring = authscheme.authenticate(credentials, method);
+ if (authstring != null) {
+ method.addRequestHeader(new Header(PROXY_AUTH_RESP, authstring, true));
+ }
+ } else {
+ LOG.warn("Required credentials not available");
+ }
+ }
+ }
+
+
+ private Credentials promptForProxyCredentials(
+ final AuthScheme authScheme,
+ final HttpParams params,
+ final HttpAuthRealm realm)
+ {
+ Credentials creds = null;
+ CredentialsProvider credProvider =
+ (CredentialsProvider)params.getParameter(CredentialsProvider.PROVIDER);
+ if (credProvider != null) {
+ try {
+ creds = credProvider.getCredentials(
+ authScheme, realm.getHost(), realm.getPort(), true);
+ } catch (CredentialsNotAvailableException e) {
+ LOG.warn(e.getMessage());
+ }
+ if (creds != null) {
+ this.state.setProxyCredentials(realm, creds);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("New proxy credentials for " + realm);
+ }
+ }
+ }
+ return creds;
+ }
+
+ private boolean processAuthenticationResponse(final HttpMethod method, final HttpConnection conn) {
+ try {
+ AuthState authstate = method.getProxyAuthState();
+ if (authstate.isPreemptive()) {
+ authstate.invalidate();
+ }
+ Map proxyChallenges = AuthChallengeParser.parseChallenges(
+ method.getResponseHeaders(PROXY_AUTH_CHALLENGE));
+ if (proxyChallenges.isEmpty()) {
+ return false;
+ }
+ AuthScheme authscheme = null;
+ try {
+ authscheme = this.authProcessor.processChallenge(authstate, proxyChallenges);
+ } catch (AuthChallengeException e) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn(e.getMessage());
+ }
+ }
+ if (authscheme == null) {
+ return false;
+ }
+ HttpAuthRealm realm = new HttpAuthRealm(
+ conn.getProxyHost(), conn.getProxyPort(),
+ authscheme.getRealm(),
+ authscheme.getSchemeName());
+
+ if ((this.authProcess == AUTH_PROXY_REQUIRED) && (authscheme.isComplete())) {
+ // Already tried and failed
+ Credentials credentials = promptForProxyCredentials(
+ authscheme, method.getParams(), realm);
+ if (credentials == null) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info("Failure authenticating with " + realm);
+ }
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ this.authProcess = AUTH_PROXY_REQUIRED;
+
+ Credentials credentials = this.state.getProxyCredentials(realm);
+ if (credentials == null) {
+ credentials = promptForProxyCredentials(
+ authscheme, method.getParams(), realm);
+ }
+ if (credentials == null) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info("No proxy credentials available for the " + realm);
+ }
+ return false;
+ } else {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error(e.getMessage(), e);
+ }
+ return false;
+ }
+ }
+
+ public synchronized Socket connect() throws IOException, HttpException {
+ ConnectMethod method = new ConnectMethod();
+ method.getParams().setDefaults(this.params);
+ HttpConnection conn = new HttpConnection(this.hostConfiguration);
+ conn.getParams().setDefaults(this.params);
+ // Just to keep the connection happy
+ conn.setHttpConnectionManager(new DummyConnectionManager());
+
+ if (!conn.isProxied()) {
+ throw new IllegalStateException("Connection is not proxied");
+ }
+
+ if (this.params.isAuthenticationPreemptive()) {
+ LOG.debug("Preemptively sending default basic credentials");
+ method.getProxyAuthState().setPreemptive();
+ this.authProcess = AUTH_PREEMPTIVE;
+ }
+
+ int code = -1;
+ int forwardCount = 0; //protect from an infinite loop
+ while (forwardCount++ < MAX_FORWARDS) {
+ // on every retry, reset this state information.
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Execute loop try " + forwardCount);
+ }
+ try {
+ authenticateProxy(method, conn);
+ } catch (AuthenticationException e) {
+ LOG.error(e.getMessage(), e);
+ }
+
+ if (!conn.isOpen) {
+ conn.open();
+ }
+ method.execute(this.state, conn);
+ code = method.getStatusCode();
+ boolean retry = false;
+ if (code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
+ if (processAuthenticationResponse(method, conn)) {
+ retry = true;
+ }
+ } else {
+ this.authProcess = AUTH_NOT_REQUIRED;
+ }
+ if (!retry) {
+ break;
+ }
+ if (method.getResponseBodyAsStream() != null) {
+ method.getResponseBodyAsStream().close();
+ }
+ }
+ if ((code >= 200) && (code < 300)) {
+ return conn.getSocket();
+ } else {
+ conn.close();
+ return null;
+ }
+ }
+
+ class DummyConnectionManager implements HttpConnectionManager {
+
+ /**
+ * @deprecated
+ */
+ public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout)
+ throws HttpException
+ {
+ return null;
+ }
+
+ public HttpConnection getConnection(HostConfiguration hostConfiguration) {
+ return null;
+ }
+
+ public HttpConnection getConnectionWithTimeout(HostConfiguration hostConfiguration, long timeout)
+ throws ConnectTimeoutException
+ {
+ return null;
+ }
+
+ public HttpConnectionManagerParams getParams() {
+ return null;
+ }
+
+ public void releaseConnection(HttpConnection conn) {
+ }
+
+ public void setParams(HttpConnectionManagerParams params) {
+ }
+ }
+}