Index: java/org/apache/commons/httpclient/Authenticator.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v
retrieving revision 1.23
diff -u -r1.23 Authenticator.java
--- java/org/apache/commons/httpclient/Authenticator.java 28 Jul 2002 18:08:57 -0000 1.23
+++ java/org/apache/commons/httpclient/Authenticator.java 7 Aug 2002 16:14:26 -0000
@@ -67,6 +67,7 @@
import java.security.MessageDigest;
import java.util.Hashtable;
+import java.util.StringTokenizer;
/**
* Utility methods for HTTP authorization and authentication.
@@ -200,7 +201,7 @@
* @see #digest
* @see HttpMethod#addRequestHeader
*/
- private static boolean authenticate(HttpMethod method, HttpState state, Header challengeHeader, String respHeader)
+ private static boolean authenticate(HttpMethod method, HttpState state, Header authenticateHeader, String respHeader)
throws HttpException, UnsupportedOperationException {
log.trace("enter Authenticator.authenticate(HttpMethod, HttpState, Header, String)");
@@ -219,7 +220,7 @@
boolean preemptive = ("true".equals(preemptive_str));
//if there is no challenge, attempt to use preemptive authorization
- if (challengeHeader == null){
+ if (authenticateHeader == null){
if (preemptive){
log.debug("Preemptively sending default basic credentials");
try{
@@ -234,55 +235,81 @@
return false;
}
}
- log.debug("Attempting to authenticate challenge: " + challengeHeader);
+ log.debug("Attempting to authenticate header: " + authenticateHeader);
- // Get the challenge from the header
- String challenge = challengeHeader.getValue();
+ // XXX: Get the challenge from the header
+ String authenticateValue = authenticateHeader.getValue();
- // Parse the authentication scheme from the challenge
// TODO: Use regular expression pattern matching to parse the challenge
- int space = challenge.indexOf(' ');
- if(space < 0) {
- throw new HttpException("Authentication challenge \'" + challenge + "\'does not contain an authentication scheme");
- }
- String authScheme = challenge.substring(0, space);
+ //FIXME: This fails if the contents of a challenge contains a ','
+ StringTokenizer challengeTok = new StringTokenizer(authenticateValue, ",");
+ Hashtable challengeMap = new Hashtable(7);
+ while(challengeTok.hasMoreTokens()){
+
+ // Parse the authentication scheme from the challenge
+ String chall = challengeTok.nextToken();
+ StringTokenizer authTok = new StringTokenizer(chall, " ");
+ String authScheme = authTok.nextToken();
- // Parse the realm from the authentication challenge
- // FIXME: Note that this won't work if there is more than one realm within the challenge
- if (challenge.length() < space + 1) {
- throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm");
- }
- String realmstr = challenge.substring(space+1, challenge.length());
- realmstr.trim();
- if (realmstr.length() < "realm=\"\"".length()) {
- throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm");
+ // Store the challenge keyed on the lower case authenticaion scheme
+ challengeMap.put(authScheme.toLowerCase(), chall);
}
- String realm = realmstr.substring("realm=\"".length(), realmstr.length()-1);
- log.debug("Parsed realm \"" + realm + "\" from challenge \"" + challenge + "\".");
- // Check for the authentication type, and add header if necessisary
Header requestHeader = null;
- if ("basic".equalsIgnoreCase(authScheme)) { // Basic authentication
- requestHeader = Authenticator.basic(realm, state, respHeader);
-
- } else if ("digest".equalsIgnoreCase(authScheme)) { // Digest authentication
+ if (challengeMap.containsKey("digest")) {
+ String challenge = (String)challengeMap.get("digest");
+ String realm = parseRealmFromChallenge(challenge);
requestHeader = Authenticator.digest(realm, method, state, respHeader);
-
- } else { // unrecognized authentication
- throw new UnsupportedOperationException("Authentication type \"" + authScheme + "\" is not recognized.");
+ } else if (challengeMap.containsKey("basic")) {
+ String challenge = (String)challengeMap.get("basic");
+ String realm = parseRealmFromChallenge(challenge);
+ requestHeader = Authenticator.basic(realm, state, respHeader);
+ } else if (challengeMap.size() == 0) {
+ throw new HttpException("No authentication scheme found in '" +
+ authenticateValue);
+ } else {
+ throw new UnsupportedOperationException("Requested authentication scheme " +
+ challengeMap.keySet() + " is unsupported");
}
+ //Add the header and return the result
if(requestHeader != null) { // add the header
method.addRequestHeader(requestHeader);
return true;
} else { // don't add the header
return false;
}
+
}
+ /**
+ * Parse the realm from the authentication challenge
+ */
+ private static String parseRealmFromChallenge(String challenge)
+ throws HttpException {
+ // FIXME: Note that this won't work if there is more than one realm within the challenge
+ try{
+ StringTokenizer strtok = new StringTokenizer(challenge, "=");
+ String realmName = strtok.nextToken().trim();
+ String realm = strtok.nextToken().trim();
+ int firstq = realm.indexOf('"');
+ int lastq = realm.lastIndexOf('"');
+ if (firstq+1 < lastq) {
+ realm = realm.substring(firstq+1, lastq);
+ }
+ log.debug("Parsed realm '" + realm + "' from challenge '" + challenge + "'");
+ return realm;
+ } catch (Exception ex) {
+ throw new HttpException("Failed to parse realm from challenge '" + challenge + "'");
+ }
+
+ }
+
+
+
/**
* Create a Basic Authorization header for the given
* realm and state to the given method.
@@ -311,7 +338,8 @@
}
if(null == cred) {
- throw new HttpException("No credentials available for the Basic authentication realm \'" + realm + "\'");
+ throw new HttpException("No credentials available for the Basic authentication realm \'" +
+ realm + "\'");
} else {
return new Header(respHeader, Authenticator.basic(cred));
}
Index: java/org/apache/commons/httpclient/HttpMethodBase.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
retrieving revision 1.44
diff -u -r1.44 HttpMethodBase.java
--- java/org/apache/commons/httpclient/HttpMethodBase.java 7 Aug 2002 02:13:22 -0000 1.44
+++ java/org/apache/commons/httpclient/HttpMethodBase.java 7 Aug 2002 16:14:26 -0000
@@ -641,10 +641,10 @@
break;
}
} catch (HttpException httpe) {
- log.warn("Exception thrown authenticating: " + httpe.getMessage());
+ log.warn(httpe.getMessage());
return true; // finished request
} catch (UnsupportedOperationException uoe) {
- log.warn("Exception thrown authenticating: " + uoe.getMessage());
+ log.warn(uoe.getMessage());
//FIXME: should this return true?
}
Index: test/org/apache/commons/httpclient/SimpleHttpConection.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpConection.java,v
retrieving revision 1.1
diff -u -r1.1 SimpleHttpConection.java
--- test/org/apache/commons/httpclient/SimpleHttpConection.java 2 Aug 2002 11:38:12 -0000 1.1
+++ test/org/apache/commons/httpclient/SimpleHttpConection.java 7 Aug 2002 16:14:26 -0000
@@ -63,24 +63,50 @@
package org.apache.commons.httpclient;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
+import java.util.Vector;
+
/**
- * Hack HttpConnection to test the response header reading mechanism.
+ * For test-nohost testing purposes only.
+ *
+ * @author Jeff Dever
*/
class SimpleHttpConnection extends HttpConnection {
+ static Log log = LogFactory.getLog("httpclient.test");
+
+ int hits = 0;
+
+ Vector headers = new Vector();
+ Vector bodies = new Vector();
BufferedReader headerReader = null;
ByteArrayInputStream bodyInputStream = null;
- public SimpleHttpConnection(String headers, String body) {
+ public void addResponse(String header) {
+ addResponse(header, "");
+ }
+
+ public void addResponse(String header, String body) {
+ headers.add(header);
+ bodies.add(body);
+ }
+
+ public SimpleHttpConnection(String header, String body) {
+ this();
+ headers.add(header);
+ bodies.add(body);
+ }
+
+ public SimpleHttpConnection() {
super(null, -1, "localhost", 80, false);
- this.headerReader = new BufferedReader(new StringReader(headers));
- bodyInputStream = new ByteArrayInputStream(body.getBytes());
}
public SimpleHttpConnection(String host, int port, boolean isSecure){
@@ -88,6 +114,24 @@
}
public void open() throws IOException {
+ if (headerReader != null) return;
+
+ try{
+ log.debug("hit: " + hits);
+ headerReader = new BufferedReader(
+ new StringReader((String)headers.elementAt(hits)));
+ bodyInputStream = new ByteArrayInputStream(
+ ((String)bodies.elementAt(hits)).getBytes());
+ hits++;
+ } catch (ArrayIndexOutOfBoundsException aiofbe) {
+ throw new IOException("SimpleHttpConnection has been opened more times " +
+ "than it has responses. You might need to call addResponse().");
+ }
+ }
+
+ public void close() {
+ headerReader = null;
+ bodyInputStream = null;
}
public void write(byte[] data)
@@ -100,7 +144,9 @@
public String readLine()
throws IOException, IllegalStateException {
- return headerReader.readLine();
+ String str = headerReader.readLine();
+ log.debug("read: " + str);
+ return str;
}
public InputStream getResponseInputStream(HttpMethod method) {
Index: test/org/apache/commons/httpclient/SimpleHttpMethod.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleHttpMethod.java,v
retrieving revision 1.2
diff -u -r1.2 SimpleHttpMethod.java
--- test/org/apache/commons/httpclient/SimpleHttpMethod.java 6 Aug 2002 15:15:32 -0000 1.2
+++ test/org/apache/commons/httpclient/SimpleHttpMethod.java 7 Aug 2002 16:14:26 -0000
@@ -63,12 +63,20 @@
package org.apache.commons.httpclient;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
import java.io.IOException;
-/** Simple method for testing the HttpMethodBase.
+
+/**
+ * For test-nohost testing purposes only.
+ *
+ * @author Jeff Dever
*/
class SimpleHttpMethod extends HttpMethodBase{
+ static Log log = LogFactory.getLog("httpclient.test");
Header header = null;
SimpleHttpMethod(){
Index: test/org/apache/commons/httpclient/TestAuthenticator.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v
retrieving revision 1.13
diff -u -r1.13 TestAuthenticator.java
--- test/org/apache/commons/httpclient/TestAuthenticator.java 6 Aug 2002 15:15:32 -0000 1.13
+++ test/org/apache/commons/httpclient/TestAuthenticator.java 7 Aug 2002 16:14:27 -0000
@@ -183,8 +183,8 @@
public void testBasicAuthentication() throws Exception {
HttpState state = new HttpState();
- state.setCredentials("realm1",new UsernamePasswordCredentials("username","password"));
- HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Basic realm=\"realm1\""));
+ state.setCredentials("realm",new UsernamePasswordCredentials("username","password"));
+ HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Basic realm=\"realm\""));
assertTrue(Authenticator.authenticate(method,state));
assertTrue(null != method.getRequestHeader("Authorization"));
String expected = "Basic " + new String(Base64.encode("username:password".getBytes()));
@@ -251,12 +251,53 @@
assertTrue(null == method.getRequestHeader("Authorization"));
}
- public void testMultipleChallenge() throws Exception {
+ public void testMultipleProxyChallenge() throws Exception {
HttpState state = new HttpState();
+ state.setProxyCredentials("Protected", new UsernamePasswordCredentials("name", "pass"));
HttpMethod method = new SimpleHttpMethod();
- //set both basic and digest response headers
-
- assertTrue(! Authenticator.authenticate(method,state));
+ SimpleHttpConnection conn = new SimpleHttpConnection();
+ conn.addResponse(
+ "HTTP/1.1 407 Proxy Authentication Required\r\n" +
+ "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" +
+ "Proxy-Authenticate: NTLM\r\n" +
+ "Connection: close\r\n" +
+ "Server: HttpClient Test/2.0\r\n"
+ );
+ conn.addResponse(
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Server: HttpClient Test/2.0\r\n"
+ );
+ method.execute(state, conn);
+ //assertEquals("Basic realm=\"Protected\", NTLM",
+ // method.getResponseHeader("Proxy-Authenticate").getValue().trim());
+ assertEquals("Basic " + new String(Base64.encode("name:pass".getBytes())),
+ method.getRequestHeader("Proxy-Authorization").getValue().trim());
+ }
+
+
+ public void testMultipleUnauthorizedChallenge() throws Exception {
+ HttpState state = new HttpState();
+ state.setCredentials("Protected", new UsernamePasswordCredentials("name", "pass"));
+ HttpMethod method = new SimpleHttpMethod();
+ SimpleHttpConnection conn = new SimpleHttpConnection();
+ conn.addResponse(
+ "HTTP/1.1 401 Unauthorized\r\n" +
+ "WWW-Authenticate: NTLM\r\n" +
+ "WWW-Authenticate: Basic realm=\"Protected\"\r\n" +
+ "Connection: close\r\n" +
+ "Server: HttpClient Test/2.0\r\n"
+ );
+ conn.addResponse(
+ "HTTP/1.1 200 OK\r\n" +
+ "Connection: close\r\n" +
+ "Server: HttpClient Test/2.0\r\n"
+ );
+ method.execute(state, conn);
+ //assertEquals("NTLM, Basic realm=\"Protected\"",
+ // method.getResponseHeader("WWW-Authenticate").getValue().trim());
+ assertEquals("Basic " + new String(Base64.encode("name:pass".getBytes())),
+ method.getRequestHeader("Authorization").getValue().trim());
}
@@ -336,7 +377,7 @@
checkAuthorization(cred, method.getName(), method.getRequestHeader("Authorization").getValue());
}
- public void testDigestAuthenticationWithMutlipleRealms() throws Exception {
+ public void testDigestAuthenticationWithMultipleRealms() throws Exception {
HttpState state = new HttpState();
UsernamePasswordCredentials cred = new UsernamePasswordCredentials("username","password");
state.setCredentials("realm1", cred);