Index: java/org/apache/commons/httpclient/ConnectMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v retrieving revision 1.24 diff -u -r1.24 ConnectMethod.java --- java/org/apache/commons/httpclient/ConnectMethod.java 11 Dec 2003 01:19:32 -0000 1.24 +++ java/org/apache/commons/httpclient/ConnectMethod.java 15 Dec 2003 11:12:25 -0000 @@ -113,24 +113,6 @@ /** * This method does nothing. CONNECT request is not supposed - * to contain Authorization request header. - * - * @param state current state of http requests - * @param conn the connection to use for I/O - * - * @throws IOException when errors occur reading or writing to/from the - * connection - * @throws HttpException when a recoverable error occurs - * - * @see HttpMethodBase#addAuthorizationRequestHeader(HttpState, HttpConnection) - */ - protected void addAuthorizationRequestHeader(HttpState state, HttpConnection conn) - throws IOException, HttpException { - // Do nothing. Not applicable to CONNECT method - } - - /** - * This method does nothing. CONNECT request is not supposed * to contain Cookie request header. * * @param state current state of http requests Index: java/org/apache/commons/httpclient/HttpClient.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v retrieving revision 1.89 diff -u -r1.89 HttpClient.java --- java/org/apache/commons/httpclient/HttpClient.java 10 Nov 2003 23:19:49 -0000 1.89 +++ java/org/apache/commons/httpclient/HttpClient.java 15 Dec 2003 11:12:26 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v 1.89 2003/11/10 23:19:49 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v 1.89 2003/11/10 23:19:49 olegk Exp $ * $Revision: 1.89 $ * $Date: 2003/11/10 23:19:49 $ * @@ -67,6 +67,7 @@ import java.security.Security; import java.security.Provider; +import org.apache.commons.httpclient.auth.AuthScheme; import org.apache.commons.httpclient.params.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; 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.11 diff -u -r1.11 HttpMethodDirector.java --- java/org/apache/commons/httpclient/HttpMethodDirector.java 10 Dec 2003 21:04:13 -0000 1.11 +++ java/org/apache/commons/httpclient/HttpMethodDirector.java 15 Dec 2003 11:12:27 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.11 2003/12/10 21:04:13 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodDirector.java,v 1.11 2003/12/10 21:04:13 olegk Exp $ * $Revision: 1.11 $ * $Date: 2003/12/10 21:04:13 $ * @@ -65,18 +65,14 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import org.apache.commons.httpclient.auth.AuthChallengeParser; import org.apache.commons.httpclient.auth.AuthPolicy; import org.apache.commons.httpclient.auth.AuthScheme; import org.apache.commons.httpclient.auth.AuthenticationException; -import org.apache.commons.httpclient.auth.CredentialsNotAvailableException; -import org.apache.commons.httpclient.auth.HttpAuthenticator; import org.apache.commons.httpclient.auth.MalformedChallengeException; import org.apache.commons.httpclient.params.HttpClientParams; import org.apache.commons.httpclient.params.HttpParams; @@ -88,6 +84,18 @@ */ class HttpMethodDirector { + /** The www authenticate challange header. */ + public static final String WWW_AUTH_CHALLENGE = "WWW-Authenticate"; + + /** The www authenticate response header. */ + public static final String WWW_AUTH_RESP = "Authorization"; + + /** The proxy authenticate challange header. */ + public static final String PROXY_AUTH_CHALLENGE = "Proxy-Authenticate"; + + /** The proxy authenticate response header. */ + public static final String PROXY_AUTH_RESP = "Proxy-Authorization"; + /** Maximum number of redirects and authentications that will be followed */ private static final int MAX_FORWARDS = 100; @@ -108,18 +116,27 @@ /** A flag to indicate if the connection should be released after the method is executed. */ private boolean releaseConnection = false; - /** Realms that we tried to authenticate to */ - private Set realms = null; - - /** Proxy Realms that we tried to authenticate to */ - private Set proxyRealms = null; + private static final int AUTH_UNINITIATED = 0; + private static final int AUTH_PREEMPTIVE = 1; + private static final int AUTH_WWW_REQUIRED = 2; + 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 authState = AUTH_UNINITIATED; + /** Actual authentication scheme */ private AuthScheme authScheme = null; + /** Whether preemtive authentication is attempted */ + private boolean authPreemptive = false; + /** Actual proxy authentication scheme */ private AuthScheme proxyAuthScheme = null; + /** Whether preemtive proxy authentication is attempted */ + private boolean proxyAuthPreemptive = false; + //TODO: to be parameterized private static final List AUTH_PREFERENCES = new ArrayList(3); static { @@ -178,34 +195,41 @@ this.params.getConnectionManagerTimeout() ); this.conn.setLocked(true); + if (this.params.isAuthenticationPreemptive()) { - realms = new HashSet(); - proxyRealms = new HashSet(); - - addPreemtiveAuthenticationHeaders(method); + LOG.debug("Preemptively sending default basic credentials"); + this.authState = AUTH_PREEMPTIVE; + this.authScheme = AuthPolicy.getAuthScheme("basic"); + this.authPreemptive = true; + if (this.conn.isProxied()) { + this.proxyAuthPreemptive = true; + this.proxyAuthScheme = AuthPolicy.getAuthScheme("basic"); + } + } } + authenticate(method); executeWithRetry(method); if (this.connectMethod != null) { - //CONNECT method failed. Bow out. fakeResponse(method); break; - } else if (isRedirectNeeded(method)) { - //if the redirect is unsuccessful, return the statusCode - //otherwise, drop through the switch and try again. - if (!processRedirectResponse(method)) { - break; + } + + boolean retry = false; + if (isRedirectNeeded(method)) { + if (processRedirectResponse(method)) { + retry = true; } - } else if (isAuthenticationNeeded(method)) { - //if the authentication is unsuccessful, return the statusCode - //otherwise, drop through the switch and try again. - if (!processAuthenticationResponse(method)) { - break; + } + if (isAuthenticationNeeded(method)) { + if (processAuthenticationResponse(method)) { + retry = true; } } else { - // nope, no retry needed, exit loop. + this.authState = AUTH_NOT_REQUIRED; + } + if (!retry) { break; } - // retry - close previous stream. Caution - this causes // responseBodyConsumed to be called, which may also close the // connection. @@ -239,47 +263,93 @@ } } + - private void applyDefaultCredentials(final HttpMethod method) { + private void authenticate(final HttpMethod method) { try { - if (HttpAuthenticator.authenticateDefault(method, this.conn, state)) { - LOG.debug("Default basic credentials applied"); - } + authenticateProxy(method); + authenticateHost(method); } catch (AuthenticationException e) { - // Log error and move on LOG.error(e.getMessage(), e); } } - private void applyDefaultProxyCredentials(final HttpMethod method) { - try { - if (HttpAuthenticator.authenticateProxyDefault(method, this.conn, state)) { - LOG.debug("Default basic proxy credentials applied"); + + private void authenticateHost(final HttpMethod method) throws AuthenticationException { + // Clean up existing authentication headers + method.removeRequestHeader(WWW_AUTH_RESP); + if ((this.authScheme != null) + && ((this.authState == AUTH_WWW_REQUIRED) + || (!this.authScheme.isConnectionBased()))) + { + String host = conn.getVirtualHost(); + if (host == null) { + host = conn.getHost(); + } + String realm = this.authScheme.getRealm(); + if (LOG.isDebugEnabled()) { + StringBuffer buffer = new StringBuffer(); + buffer.append("Authenticating with "); + if (realm == null) { + buffer.append("default"); + } else { + buffer.append('\''); + buffer.append(realm); + buffer.append('\''); + } + buffer.append(" authentication realm at "); + buffer.append(host); + LOG.info(buffer.toString()); + } + Credentials credentials = this.state.getCredentials(realm, host); + if (credentials != null) { + String authstring = this.authScheme.authenticate(credentials, method); + if (authstring != null) { + method.addRequestHeader(new Header(WWW_AUTH_RESP, authstring, true)); + } + } else { + LOG.warn("Required credentials not available"); } - } catch (AuthenticationException e) { - // Log error and move on - LOG.error(e.getMessage(), e); } } - - /** - * Adds authentication headers if authenticationPreemtive has been set. - * - * @see HttpClientParams#isAuthenticationPreemptive() - */ - private void addPreemtiveAuthenticationHeaders(final HttpMethod method) { - //pre-emptively add the authorization header, if required. - if (this.params.isAuthenticationPreemptive()) { - LOG.debug("Preemptively sending default basic credentials"); - applyDefaultCredentials(method); - if (this.conn.isProxied()) { - applyDefaultProxyCredentials(method); + private void authenticateProxy(final HttpMethod method) throws AuthenticationException { + // Clean up existing authentication headers + method.removeRequestHeader(PROXY_AUTH_RESP); + if ((this.proxyAuthScheme != null) + && ((this.authState == AUTH_PROXY_REQUIRED) + || (!this.proxyAuthScheme.isConnectionBased()))) + { + String host = conn.getProxyHost(); + String realm = this.proxyAuthScheme.getRealm(); + if (LOG.isDebugEnabled()) { + StringBuffer buffer = new StringBuffer(); + buffer.append("Authenticating with "); + if (realm == null) { + buffer.append("default"); + } else { + buffer.append('\''); + buffer.append(realm); + buffer.append('\''); + } + buffer.append(" proxy authentication realm at "); + buffer.append(host); + LOG.info(buffer.toString()); + } + Credentials credentials = this.state.getProxyCredentials(realm, host); + if (credentials != null) { + String authstring = this.proxyAuthScheme.authenticate(credentials, method); + if (authstring != null) { + method.addRequestHeader(new Header(PROXY_AUTH_RESP, authstring, true)); + } + } else { + LOG.warn("Required credentials not available"); } } } + /** * Executes a method with the current hostConfiguration. * @@ -379,17 +449,23 @@ this.connectMethod.getParams().setDefaults(this.params); int code; - if (this.params.isAuthenticationPreemptive()) { - applyDefaultProxyCredentials(this.connectMethod); - } for (;;) { + try { + authenticateProxy(this.connectMethod); + } catch (AuthenticationException e) { + LOG.error(e.getMessage(), e); + } executeWithRetry(this.connectMethod); code = this.connectMethod.getStatusCode(); + boolean retry = false; if (code == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) { - if (!processAuthenticationResponse(this.connectMethod)) { - break; + if (processAuthenticationResponse(this.connectMethod)) { + retry = true; } } else { + this.authState = AUTH_NOT_REQUIRED; + } + if (!retry) { break; } if (this.connectMethod.getResponseBodyAsStream() != null) { @@ -495,16 +571,6 @@ LOG.warn("Redirected location '" + location + "' is malformed"); return false; } - - //invalidate the list of authentication attempts - this.realms.clear(); - //remove exisitng authentication headers - if ((this.proxyAuthScheme != null) && (this.proxyAuthScheme.isConnectionBased())) { - method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH); - } - method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); - //Invalidate present authentication scheme - this.authScheme = null; //update the current location with the redirect location. //avoiding use of URL.getPath() and URL.getQuery() to keep //jdk1.2 comliance. @@ -517,14 +583,15 @@ + "' to '" + redirectUri.getEscapedURI()); } + //And finally invalidate the actual authentication scheme + this.authScheme = null; return true; } /** * Processes a response that requires authentication * - * @param state the current state - * @param conn The connection + * @param method the current {@link HttpMethod HTTP method} * * @return true if the authentication challenge can be responsed to, * (that is, at least one of the requested authentication scheme is supported, @@ -534,92 +601,149 @@ LOG.trace("enter HttpMethodBase.processAuthenticationResponse(" + "HttpState, HttpConnection)"); - if ((this.proxyAuthScheme != null) && (this.proxyAuthScheme.isConnectionBased())) { - method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH); - } - if ((this.authScheme != null) && (this.authScheme.isConnectionBased())) { - method.removeRequestHeader(HttpAuthenticator.WWW_AUTH); - } - boolean authenticated = false; try { switch (method.getStatusCode()) { case HttpStatus.SC_UNAUTHORIZED: - Map challenges = AuthChallengeParser.parseChallenges( - method.getResponseHeaders(HttpAuthenticator.WWW_AUTH)); - if (challenges.isEmpty()) { - return false; - } - if (this.authScheme != null) { - processChallenge(this.authScheme, challenges); - } else { - this.authScheme = processChallenge(challenges); - if (this.authScheme == null) { - return false; - } - } - String host = this.conn.getVirtualHost(); - if (host == null) { - host = this.conn.getHost(); - } - if (previousAttemptFailed(this.realms, host, - this.authScheme)) { - return false; - } - method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); - authenticated = HttpAuthenticator.authenticate( - this.authScheme, method, this.conn, this.state); - break; + return processWWWAuthChallenge(method); case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: - Map proxyChallenges = AuthChallengeParser.parseChallenges( - method.getResponseHeaders(HttpAuthenticator.PROXY_AUTH)); - if (proxyChallenges.isEmpty()) { - return false; - } - if (this.proxyAuthScheme != null) { - processChallenge(this.proxyAuthScheme, proxyChallenges); - } else { - this.proxyAuthScheme = processChallenge(proxyChallenges); - if (this.proxyAuthScheme == null) { - return false; - } - } - if (previousAttemptFailed(this.proxyRealms, this.conn.getProxyHost(), - this.proxyAuthScheme)) { - return false; - } - method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); - authenticated = HttpAuthenticator.authenticateProxy( - proxyAuthScheme, method, this.conn, this.state); - break; + return processProxyAuthChallenge(method); + default: + return false; } - } catch (MalformedChallengeException e) { + } catch (Exception e) { if (LOG.isErrorEnabled()) { LOG.error(e.getMessage(), e); } return false; - } catch (CredentialsNotAvailableException e) { - if (LOG.isWarnEnabled()) { - LOG.warn(e.getMessage()); + } + } + + private boolean processWWWAuthChallenge(final HttpMethod method) + throws MalformedChallengeException, AuthenticationException + { + if (this.authPreemptive) { + this.authScheme = null; + this.authPreemptive = false; + } + Map challenges = AuthChallengeParser.parseChallenges( + method.getResponseHeaders(WWW_AUTH_CHALLENGE)); + if (challenges.isEmpty()) { + return false; + } + if (this.authScheme != null) { + processChallenge(this.authScheme, challenges); + } else { + this.authScheme = processChallenge(challenges); + } + if (this.authScheme == null) { + return false; + } + if ((this.authState == AUTH_WWW_REQUIRED) + && (this.authScheme.isComplete())) { + // Already tried and failed + if (LOG.isInfoEnabled()) { + StringBuffer buffer = new StringBuffer(); + buffer.append("Attempt to authenticate with '"); + buffer.append(this.authScheme.getRealm()); + buffer.append("' authentication realm at "); + String host = this.conn.getVirtualHost(); + if (host == null) { + host = this.conn.getHost(); + } + buffer.append(host); + buffer.append(" failed"); + LOG.info(buffer.toString()); } return false; - } catch (AuthenticationException e) { - if (LOG.isErrorEnabled()) { - LOG.error(e.getMessage(), e); + } else { + this.authState = AUTH_WWW_REQUIRED; + + String host = conn.getVirtualHost(); + if (host == null) { + host = conn.getHost(); + } + String realm = this.authScheme.getRealm(); + Credentials credentials = this.state.getCredentials(realm, host); + if (credentials == null) { + if (LOG.isInfoEnabled()) { + StringBuffer buffer = new StringBuffer(); + buffer.append("No credentials available for the "); + if (realm == null) { + buffer.append("default"); + } else { + buffer.append('\''); + buffer.append(realm); + buffer.append('\''); + } + buffer.append(" authentication realm at "); + buffer.append(host); + } + return false; + } else { + return true; } + } + } + + private boolean processProxyAuthChallenge(final HttpMethod method) + throws MalformedChallengeException, AuthenticationException + { + if (this.proxyAuthPreemptive) { + this.proxyAuthScheme = null; + this.proxyAuthPreemptive = false; + } + Map proxyChallenges = AuthChallengeParser.parseChallenges( + method.getResponseHeaders(PROXY_AUTH_CHALLENGE)); + if (proxyChallenges.isEmpty()) { + return false; + } + if (this.proxyAuthScheme != null) { + processChallenge(this.proxyAuthScheme, proxyChallenges); + } else { + this.proxyAuthScheme = processChallenge(proxyChallenges); + } + if (this.proxyAuthScheme == null) { return false; - } - if (!authenticated) { - // won't be able to authenticate to this challenge - // without additional information - LOG.debug("Credentials required to respond to authorization" + - " challenge are not available"); - } else { - LOG.debug("Credentials required to respond to authorization" + - " challenge have been provided"); - // let's try it again, using the credentials - } - return authenticated; - } + } + if ((this.authState == AUTH_PROXY_REQUIRED) + && (this.proxyAuthScheme.isComplete())) { + // Already tried and failed + if (LOG.isInfoEnabled()) { + StringBuffer buffer = new StringBuffer(); + buffer.append("Attempt to authenticate with '"); + buffer.append(this.authScheme.getRealm()); + buffer.append("' proxy authentication realm at "); + buffer.append(this.conn.getProxyHost()); + buffer.append(" failed"); + LOG.info(buffer.toString()); + } + return false; + } else { + this.authState = AUTH_PROXY_REQUIRED; + + String host = conn.getProxyHost(); + String realm = this.proxyAuthScheme.getRealm(); + Credentials credentials = this.state.getProxyCredentials(realm, host); + if (credentials == null) { + if (LOG.isInfoEnabled()) { + StringBuffer buffer = new StringBuffer(); + buffer.append("No credentials available for the "); + if (realm == null) { + buffer.append("default"); + } else { + buffer.append('\''); + buffer.append(realm); + buffer.append('\''); + } + buffer.append(" proxy authentication realm at "); + buffer.append(host); + } + return false; + } else { + return true; + } + } + } private void processChallenge(final AuthScheme authscheme, final Map challenges) throws MalformedChallengeException, AuthenticationException @@ -679,34 +803,6 @@ return authscheme; } - - private boolean previousAttemptFailed(final Set realms, final String host, final AuthScheme authscheme) { - // See if authentication against the given realm & host has - // already been attempted - StringBuffer buffer = new StringBuffer(); - buffer.append(host); - buffer.append('#'); - buffer.append(authscheme.getID()); - String realm = buffer.toString(); - - if (realms.contains(realm)) { - // Already tried. Give up - if (LOG.isInfoEnabled()) { - buffer = new StringBuffer(); - buffer.append("Attempt to authenticate with '"); - buffer.append(authscheme.getRealm()); - buffer.append("' authentication realm at "); - buffer.append(host); - buffer.append(" failed"); - LOG.info(buffer.toString()); - } - return true; - } else { - realms.add(realm); - return false; - } - - } /** * Tests if the {@link HttpMethod method} requires a redirect to another location. * @@ -748,7 +844,7 @@ if (method.getDoAuthentication()) { //process authentication response return true; } else { //let the client handle the authenticaiton - LOG.info("Redirect requested but doAuthentication is " + LOG.info("Authentication requested but doAuthentication is " + "disabled"); return false; } @@ -785,4 +881,31 @@ return this.params; } + /** + * Returns the authentication scheme used to authenticate with the + * target host. + * + * @return the authentication scheme used to authenticate with the + * target host. Null is returned if target host authentication + * was not required. + * + * @since 2.1 + */ + public AuthScheme getAuthScheme() { + return authScheme; + } + + /** + * Returns the authentication scheme used to authenticate with the + * proxy host. + * + * @return the authentication scheme used to authenticate with the + * proxy host. Null is returned if proxy authentication was not + * required. + * + * @since 2.1 + */ + public AuthScheme getProxyAuthScheme() { + return proxyAuthScheme; + } } Index: java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java,v retrieving revision 1.29 diff -u -r1.29 MultiThreadedHttpConnectionManager.java --- java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 11 Dec 2003 01:19:32 -0000 1.29 +++ java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 15 Dec 2003 11:12:29 -0000 @@ -1114,6 +1114,9 @@ } } + /** + * @deprecated + */ public void print(String data) throws IOException, IllegalStateException, HttpRecoverableException { if (hasConnection()) { @@ -1132,6 +1135,9 @@ } } + /** + * @deprecated + */ public void printLine(String data) throws IOException, IllegalStateException, HttpRecoverableException { if (hasConnection()) { Index: java/org/apache/commons/httpclient/auth/AuthScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/AuthScheme.java,v retrieving revision 1.6 diff -u -r1.6 AuthScheme.java --- java/org/apache/commons/httpclient/auth/AuthScheme.java 11 Dec 2003 01:19:32 -0000 1.6 +++ java/org/apache/commons/httpclient/auth/AuthScheme.java 15 Dec 2003 11:12:29 -0000 @@ -152,6 +152,8 @@ * * @return String a String identifying the authentication challenge. The * returned value may be null. + * + * @deprecated no longer used */ String getID(); @@ -165,7 +167,18 @@ boolean isConnectionBased(); /** - * @deprecated Use {@link #authenticate(Credentials, String, String, HttpMethodParams)} + * Authentication process may involve a series of challenge-response exchanges. + * This method tests if the authorization process has been completed, either + * successfully or unsuccessfully, that is, all the required authorization + * challenges have been processed in their entirety. + * + * @return true if the authentication process has been completed, + * false otherwise. + */ + boolean isComplete(); + + /** + * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} * * Produces an authorization string for the given set of {@link Credentials}, * method name and URI using the given authentication scheme in response to Index: java/org/apache/commons/httpclient/auth/BasicScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/BasicScheme.java,v retrieving revision 1.9 diff -u -r1.9 BasicScheme.java --- java/org/apache/commons/httpclient/auth/BasicScheme.java 11 Dec 2003 01:19:32 -0000 1.9 +++ java/org/apache/commons/httpclient/auth/BasicScheme.java 15 Dec 2003 11:12:30 -0000 @@ -92,12 +92,16 @@ /** Log object for this class. */ private static final Log LOG = LogFactory.getLog(BasicScheme.class); + /** Whether the basic authentication process is complete */ + private boolean complete; + /** * Default constructor for the basic authetication scheme. * */ public BasicScheme() { super(); + this.complete = false; } /** @@ -113,9 +117,9 @@ */ public BasicScheme(final String challenge) throws MalformedChallengeException { super(challenge); + this.complete = true; } - /** * Returns textual designation of the basic authentication scheme. * @@ -126,6 +130,31 @@ } /** + * Processes the Basic challenge. + * + * @param the challenge string + * + * @throws MalformedChallengeException is thrown if the authentication challenge + * is malformed + */ + public void processChallenge(String challenge) + throws MalformedChallengeException + { + super.processChallenge(challenge); + this.complete = true; + } + + /** + * Tests if the Basic authentication process has been completed. + * + * @return true if Basic authorization has been processed, + * false otherwise. + */ + public boolean isComplete() { + return this.complete; + } + + /** * Produces basic authorization string for the given set of * {@link Credentials}. * @@ -138,6 +167,8 @@ * be generated due to an authentication failure * * @return a basic authorization string + * + * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} */ public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { @@ -235,5 +266,4 @@ return "Basic " + HttpConstants.getAsciiString( Base64.encode(EncodingUtil.getBytes(buffer.toString(), charset))); } - } Index: java/org/apache/commons/httpclient/auth/DigestScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/DigestScheme.java,v retrieving revision 1.13 diff -u -r1.13 DigestScheme.java --- java/org/apache/commons/httpclient/auth/DigestScheme.java 11 Dec 2003 01:19:32 -0000 1.13 +++ java/org/apache/commons/httpclient/auth/DigestScheme.java 15 Dec 2003 11:12:30 -0000 @@ -120,6 +120,9 @@ 'e', 'f' }; + /** Whether the digest authentication process is complete */ + private boolean complete; + //TODO: supply a real nonce-count, currently a server will interprete a repeated request as a replay private static final String NC = "00000001"; //nonce-count is always 1 private static final int QOP_MISSING = 0; @@ -135,12 +138,15 @@ */ public DigestScheme() { super(); + this.complete = false; } /** * Gets an ID based upon the realm and the nonce value. This ensures that requests * to the same realm with different nonce values will succeed. This differentiation * allows servers to request re-authentication using a fresh nonce value. + * + * @deprecated no longer used */ public String getID() { @@ -167,35 +173,7 @@ public DigestScheme(final String challenge) throws MalformedChallengeException { super(challenge); - - if (getParameter("nonce") == null) { - throw new MalformedChallengeException("missing nonce in challange"); - } - - boolean unsupportedQop = false; - // qop parsing - String qop = getParameter("qop"); - if (qop != null) { - StringTokenizer tok = new StringTokenizer(qop,","); - while (tok.hasMoreTokens()) { - String variant = tok.nextToken().trim(); - if (variant.equals("auth")) { - qopVariant = QOP_AUTH; - break; //that's our favourite, because auth-int is unsupported - } else if (variant.equals("auth-int")) { - qopVariant = QOP_AUTH_INT; - } else { - unsupportedQop = true; - LOG.warn("Unsupported qop detected: "+ variant); - } - } - } - - if (unsupportedQop && (qopVariant == QOP_MISSING)) { - throw new MalformedChallengeException("None of the qop methods is supported"); - } - - cnonce = createCnonce(); + this.complete = true; } /** @@ -238,8 +216,23 @@ } cnonce = createCnonce(); + this.complete = true; } + /** + * Tests if the Digest authentication process has been completed. + * + * @return true if Digest authorization has been processed, + * false otherwise. + */ + public boolean isComplete() { + String s = getParameter("stale"); + if ("true".equalsIgnoreCase(s)) { + return false; + } else { + return this.complete; + } + } /** * Returns textual designation of the digest authentication scheme. @@ -276,6 +269,8 @@ * * @see org.apache.commons.httpclient.HttpMethod#getName() * @see org.apache.commons.httpclient.HttpMethod#getPath() + * + * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} */ public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { Index: java/org/apache/commons/httpclient/auth/HttpAuthenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/HttpAuthenticator.java,v retrieving revision 1.15 diff -u -r1.15 HttpAuthenticator.java --- java/org/apache/commons/httpclient/auth/HttpAuthenticator.java 11 Dec 2003 01:19:32 -0000 1.15 +++ java/org/apache/commons/httpclient/auth/HttpAuthenticator.java 15 Dec 2003 11:12:31 -0000 @@ -100,6 +100,8 @@ * @author Adrian Sutton * @author Mike Bowler * @author Oleg Kalnichevski + * + * @deprecated no longer used */ public final class HttpAuthenticator { @@ -238,6 +240,8 @@ * @throws AuthenticationException when a parsing or other error occurs * * @see HttpState#setCredentials(String,String,Credentials) + * + * @deprecated use AuthScheme */ public static boolean authenticateDefault( HttpMethod method, @@ -269,6 +273,8 @@ * @throws AuthenticationException when a parsing or other error occurs * @see HttpState#setCredentials(String,String,Credentials) + * + * @deprecated use AuthScheme */ public static boolean authenticateProxyDefault( HttpMethod method, @@ -310,7 +316,7 @@ String realm = authscheme.getRealm(); if (LOG.isDebugEnabled()) { StringBuffer buffer = new StringBuffer(); - buffer.append("Authenticating with the "); + buffer.append("Using credentials for "); if (realm == null) { buffer.append("default"); } else { @@ -326,9 +332,18 @@ ? state.getProxyCredentials(realm, host) : state.getCredentials(realm, host); if (credentials == null) { - throw new CredentialsNotAvailableException( - "No credentials available for the '" + realm - + "' authentication realm at " + host); + StringBuffer buffer = new StringBuffer(); + buffer.append("No credentials available for the "); + if (realm == null) { + buffer.append("default"); + } else { + buffer.append('\''); + buffer.append(realm); + buffer.append('\''); + } + buffer.append(" authentication realm at "); + buffer.append(host); + throw new CredentialsNotAvailableException(buffer.toString()); } String auth = authscheme.authenticate(credentials, method); if (auth != null) { @@ -360,6 +375,8 @@ * @throws AuthenticationException when a parsing or other error occurs * @see HttpState#setCredentials(String,String,Credentials) + * + * @deprecated use AuthScheme */ public static boolean authenticate( AuthScheme authscheme, @@ -394,6 +411,8 @@ * @throws AuthenticationException when a parsing or other error occurs * @see HttpState#setCredentials(String,String,Credentials) + * + * @deprecated use AuthScheme */ public static boolean authenticateProxy( AuthScheme authscheme, Index: java/org/apache/commons/httpclient/auth/NTLM.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/NTLM.java,v retrieving revision 1.5 diff -u -r1.5 NTLM.java --- java/org/apache/commons/httpclient/auth/NTLM.java 11 Dec 2003 01:19:32 -0000 1.5 +++ java/org/apache/commons/httpclient/auth/NTLM.java 15 Dec 2003 11:12:32 -0000 @@ -263,7 +263,7 @@ * @param domain The domain to authenticate with. * @return String the message to add to the HTTP request header. */ - private String getType1Message(String host, String domain) { + public String getType1Message(String host, String domain) { host = host.toUpperCase(); domain = domain.toUpperCase(); byte[] hostBytes = EncodingUtil.getBytes(host, DEFAULT_CHARSET); @@ -338,7 +338,7 @@ * @return an array of 8 bytes that the server sent to be used when * hashing the password. */ - private byte[] parseType2Message(String message) { + public byte[] parseType2Message(String message) { // Decode the message first. byte[] msg = Base64.decode(EncodingUtil.getBytes(message, DEFAULT_CHARSET)); byte[] nonce = new byte[8]; @@ -362,7 +362,7 @@ * @return The type 3 message. * @throws AuthenticationException If {@encrypt(byte[],byte[])} fails. */ - private String getType3Message(String user, String password, + public String getType3Message(String user, String password, String host, String domain, byte[] nonce) throws AuthenticationException { Index: java/org/apache/commons/httpclient/auth/NTLMScheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/NTLMScheme.java,v retrieving revision 1.13 diff -u -r1.13 NTLMScheme.java --- java/org/apache/commons/httpclient/auth/NTLMScheme.java 11 Dec 2003 01:19:32 -0000 1.13 +++ java/org/apache/commons/httpclient/auth/NTLMScheme.java 15 Dec 2003 11:12:32 -0000 @@ -90,12 +90,21 @@ /** NTLM challenge string. */ private String ntlmchallenge = null; + private static final int UNINITIATED = 0; + private static final int INITIATED = 1; + private static final int TYPE1_MSG_GENERATED = 2; + private static final int TYPE2_MSG_RECEIVED = 3; + private static final int TYPE3_MSG_GENERATED = 4; + + /** Authentication process state */ + private int state; /** * Default constructor for the NTLM authentication scheme. * */ public NTLMScheme() { super(); + this.state = UNINITIATED; } /** @@ -128,12 +137,24 @@ if (i != -1) { s = challenge.substring(i, challenge.length()); this.ntlmchallenge = s.trim(); + this.state = TYPE2_MSG_RECEIVED; } else { this.ntlmchallenge = ""; + this.state = INITIATED; } } /** + * Tests if the NTLM authentication process has been completed. + * + * @return true if Basic authorization has been processed, + * false otherwise. + */ + public boolean isComplete() { + return this.state == TYPE3_MSG_GENERATED; + } + + /** * Returns textual designation of the NTLM authentication scheme. * * @return ntlm @@ -167,12 +188,13 @@ * * @return String a String identifying the authentication challenge. The * returned value may be null. + * + * @deprecated no longer used */ public String getID() { return ntlmchallenge; } - /** * Returns the authentication parameter with the given name, if available. * @@ -208,6 +230,8 @@ * * @return a ntlm authorization string * @throws AuthenticationException is thrown if authentication fails + * + * @deprecated Use non-static {@link #authenticate(Credentials, HttpMethod)} */ public static String authenticate( final NTCredentials credentials, final String challenge) @@ -236,6 +260,8 @@ * * @return a ntlm authorization string * @throws AuthenticationException is thrown if authentication fails + * + * @deprecated Use non-static {@link #authenticate(Credentials, HttpMethod)} */ public static String authenticate( final NTCredentials credentials, @@ -273,6 +299,8 @@ * be generated due to an authentication failure * * @return an NTLM authorization string + * + * @deprecated Use {@link #authenticate(Credentials, HttpMethod)} */ public String authenticate(Credentials credentials, String method, String uri) throws AuthenticationException { @@ -309,6 +337,10 @@ ) throws AuthenticationException { LOG.trace("enter NTLMScheme.authenticate(Credentials, HttpMethod)"); + if (this.state == UNINITIATED) { + throw new IllegalStateException("NTLM authentication process has not been initiated"); + } + NTCredentials ntcredentials = null; try { ntcredentials = (NTCredentials) credentials; @@ -317,9 +349,22 @@ "Credentials cannot be used for NTLM authentication: " + credentials.getClass().getName()); } - return NTLMScheme.authenticate( - ntcredentials, - this.ntlmchallenge, - method.getParams().getCredentialCharset()); + NTLM ntlm = new NTLM(); + String response = null; + if (this.state == INITIATED) { + response = ntlm.getType1Message( + ntcredentials.getHost(), + ntcredentials.getDomain()); + this.state = TYPE1_MSG_GENERATED; + } else { + response = ntlm.getType3Message( + ntcredentials.getUserName(), + ntcredentials.getPassword(), + ntcredentials.getHost(), + ntcredentials.getDomain(), + ntlm.parseType2Message(this.ntlmchallenge)); + this.state = TYPE3_MSG_GENERATED; + } + return "NTLM " + response; } } Index: java/org/apache/commons/httpclient/auth/RFC2617Scheme.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/RFC2617Scheme.java,v retrieving revision 1.5 diff -u -r1.5 RFC2617Scheme.java --- java/org/apache/commons/httpclient/auth/RFC2617Scheme.java 10 Dec 2003 21:04:13 -0000 1.5 +++ java/org/apache/commons/httpclient/auth/RFC2617Scheme.java 15 Dec 2003 11:12:33 -0000 @@ -1,5 +1,5 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/RFC2617Scheme.java,v 1.5 2003/12/10 21:04:13 olegk Exp $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/auth/RFC2617Scheme.java,v 1.5 2003/12/10 21:04:13 olegk Exp $ * $Revision: 1.5 $ * $Date: 2003/12/10 21:04:13 $ * @@ -144,6 +144,9 @@ if (name == null) { throw new IllegalArgumentException("Parameter name may not be null"); } + if (this.params == null) { + return null; + } return (String) this.params.get(name.toLowerCase()); } @@ -173,6 +176,8 @@ * * @return String a String identifying the authentication challenge. The * returned value may be null. + * + * @deprecated no longer used */ public String getID() { return getRealm(); Index: test/org/apache/commons/httpclient/NoncompliantPostMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/NoncompliantPostMethod.java,v retrieving revision 1.1 diff -u -r1.1 NoncompliantPostMethod.java --- test/org/apache/commons/httpclient/NoncompliantPostMethod.java 1 Feb 2003 16:10:48 -0000 1.1 +++ test/org/apache/commons/httpclient/NoncompliantPostMethod.java 15 Dec 2003 11:12:33 -0000 @@ -99,7 +99,8 @@ Header header = headers[i]; // Write all the headers but "Expect" if (!header.getName().equalsIgnoreCase("Expect") ) { - conn.print(header.toExternalForm()); + conn.print(header.toExternalForm(), + HttpConstants.HTTP_ELEMENT_CHARSET); } } } Index: test/org/apache/commons/httpclient/TestBadContentLength.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestBadContentLength.java,v retrieving revision 1.2 diff -u -r1.2 TestBadContentLength.java --- test/org/apache/commons/httpclient/TestBadContentLength.java 11 Dec 2003 22:54:19 -0000 1.2 +++ test/org/apache/commons/httpclient/TestBadContentLength.java 15 Dec 2003 11:12:33 -0000 @@ -63,7 +63,6 @@ import java.io.IOException; import java.io.InputStream; -import java.net.ProtocolException; import junit.framework.Test; import junit.framework.TestSuite;