You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2011/08/16 17:13:37 UTC

svn commit: r1158320 - in /httpcomponents/httpclient/trunk/httpclient/src: main/java/org/apache/http/impl/auth/ test/java/org/apache/http/impl/auth/

Author: olegk
Date: Tue Aug 16 15:13:36 2011
New Revision: 1158320

URL: http://svn.apache.org/viewvc?rev=1158320&view=rev
Log:
Support for auth-int qop (quality of protection) option in Digest auth scheme

Added:
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java   (with props)
Modified:
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java?rev=1158320&r1=1158319&r2=1158320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/BasicScheme.java Tue Aug 16 15:13:36 2011
@@ -38,6 +38,8 @@ import org.apache.http.auth.InvalidCrede
 import org.apache.http.auth.MalformedChallengeException;
 import org.apache.http.auth.params.AuthParams;
 import org.apache.http.message.BufferedHeader;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
 import org.apache.http.util.CharArrayBuffer;
 import org.apache.http.util.EncodingUtils;
 
@@ -109,6 +111,12 @@ public class BasicScheme extends RFC2617
         return false;
     }
 
+    @Deprecated
+    public Header authenticate(
+            final Credentials credentials, final HttpRequest request) throws AuthenticationException {
+        return authenticate(credentials, request, new BasicHttpContext());
+    }
+
     /**
      * Produces basic authorization header for the given set of {@link Credentials}.
      *
@@ -121,9 +129,11 @@ public class BasicScheme extends RFC2617
      *
      * @return a basic authorization string
      */
+    @Override
     public Header authenticate(
             final Credentials credentials,
-            final HttpRequest request) throws AuthenticationException {
+            final HttpRequest request,
+            final HttpContext context) throws AuthenticationException {
 
         if (credentials == null) {
             throw new IllegalArgumentException("Credentials may not be null");

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java?rev=1158320&r1=1158319&r2=1158320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/DigestScheme.java Tue Aug 16 15:13:36 2011
@@ -26,17 +26,22 @@
 
 package org.apache.http.impl.auth;
 
+import java.io.IOException;
 import java.security.MessageDigest;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Formatter;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.StringTokenizer;
 
 import org.apache.http.annotation.NotThreadSafe;
 
 import org.apache.http.Header;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpEntityEnclosingRequest;
 import org.apache.http.HttpRequest;
 import org.apache.http.auth.AuthenticationException;
 import org.apache.http.auth.Credentials;
@@ -46,6 +51,8 @@ import org.apache.http.auth.params.AuthP
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.message.BasicHeaderValueFormatter;
 import org.apache.http.message.BufferedHeader;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
 import org.apache.http.util.CharArrayBuffer;
 import org.apache.http.util.EncodingUtils;
 
@@ -122,13 +129,6 @@ public class DigestScheme extends RFC261
     public void processChallenge(
             final Header header) throws MalformedChallengeException {
         super.processChallenge(header);
-
-        if (getParameter("realm") == null) {
-            throw new MalformedChallengeException("missing realm in challenge");
-        }
-        if (getParameter("nonce") == null) {
-            throw new MalformedChallengeException("missing nonce in challenge");
-        }
         this.complete = true;
     }
 
@@ -169,6 +169,12 @@ public class DigestScheme extends RFC261
         getParameters().put(name, value);
     }
 
+    @Deprecated
+    public Header authenticate(
+            final Credentials credentials, final HttpRequest request) throws AuthenticationException {
+        return authenticate(credentials, request, new BasicHttpContext());
+    }
+
     /**
      * Produces a digest authorization string for the given set of
      * {@link Credentials}, method name and URI.
@@ -183,9 +189,11 @@ public class DigestScheme extends RFC261
      *
      * @return a digest authorization string
      */
+    @Override
     public Header authenticate(
             final Credentials credentials,
-            final HttpRequest request) throws AuthenticationException {
+            final HttpRequest request,
+            final HttpContext context) throws AuthenticationException {
 
         if (credentials == null) {
             throw new IllegalArgumentException("Credentials may not be null");
@@ -193,7 +201,12 @@ public class DigestScheme extends RFC261
         if (request == null) {
             throw new IllegalArgumentException("HTTP request may not be null");
         }
-
+        if (getParameter("realm") == null) {
+            throw new AuthenticationException("missing realm in challenge");
+        }
+        if (getParameter("nonce") == null) {
+            throw new AuthenticationException("missing nonce in challenge");
+        }
         // Add method name and request-URI to the parameter map
         getParameters().put("methodname", request.getRequestLine().getMethod());
         getParameters().put("uri", request.getRequestLine().getUri());
@@ -202,7 +215,7 @@ public class DigestScheme extends RFC261
             charset = AuthParams.getCredentialCharset(request.getParams());
             getParameters().put("charset", charset);
         }
-        return createDigestHeader(credentials);
+        return createDigestHeader(credentials, request);
     }
 
     private static MessageDigest createMessageDigest(
@@ -224,34 +237,28 @@ public class DigestScheme extends RFC261
      * @return The digest-response as String.
      */
     private Header createDigestHeader(
-            final Credentials credentials) throws AuthenticationException {
+            final Credentials credentials,
+            final HttpRequest request) throws AuthenticationException {
         String uri = getParameter("uri");
         String realm = getParameter("realm");
         String nonce = getParameter("nonce");
         String opaque = getParameter("opaque");
         String method = getParameter("methodname");
         String algorithm = getParameter("algorithm");
-        if (uri == null) {
-            throw new IllegalStateException("URI may not be null");
-        }
-        if (realm == null) {
-            throw new IllegalStateException("Realm may not be null");
-        }
-        if (nonce == null) {
-            throw new IllegalStateException("Nonce may not be null");
-        }
 
-        //TODO: add support for QOP_INT
+        Set<String> qopset = new HashSet<String>(8);
         int qop = QOP_UNKNOWN;
         String qoplist = getParameter("qop");
         if (qoplist != null) {
             StringTokenizer tok = new StringTokenizer(qoplist, ",");
             while (tok.hasMoreTokens()) {
                 String variant = tok.nextToken().trim();
-                if (variant.equals("auth")) {
-                    qop = QOP_AUTH;
-                    break;
-                }
+                qopset.add(variant.toLowerCase(Locale.US));
+            }
+            if (request instanceof HttpEntityEnclosingRequest && qopset.contains("auth-int")) {
+                qop = QOP_AUTH_INT;
+            } else if (qopset.contains("auth")) {
+                qop = QOP_AUTH;
             }
         } else {
             qop = QOP_MISSING;
@@ -265,7 +272,6 @@ public class DigestScheme extends RFC261
         if (algorithm == null) {
             algorithm = "MD5";
         }
-        // If an charset is not specified, default to ISO-8859-1.
         String charset = getParameter("charset");
         if (charset == null) {
             charset = "ISO-8859-1";
@@ -331,8 +337,31 @@ public class DigestScheme extends RFC261
             a2 = method + ':' + uri;
         } else if (qop == QOP_AUTH_INT) {
             // Method ":" digest-uri-value ":" H(entity-body)
-            //TODO: calculate entity hash if entity is repeatable
-            throw new AuthenticationException("qop-int method is not suppported");
+            HttpEntity entity = null;
+            if (request instanceof HttpEntityEnclosingRequest) {
+                entity = ((HttpEntityEnclosingRequest) request).getEntity();
+            }
+            if (entity != null && !entity.isRepeatable()) {
+                // If the entity is not repeatable, try falling back onto QOP_AUTH
+                if (qopset.contains("auth")) {
+                    qop = QOP_AUTH;
+                    a2 = method + ':' + uri;
+                } else {
+                    throw new AuthenticationException("Qop auth-int cannot be used with " +
+                            "a non-repeatable entity");
+                }
+            } else {
+                HttpEntityDigester entityDigester = new HttpEntityDigester(digester);
+                try {
+                    if (entity != null) {
+                        entity.writeTo(entityDigester);
+                    }
+                    entityDigester.close();
+                } catch (IOException ex) {
+                    throw new AuthenticationException("I/O error reading entity content", ex);
+                }
+                a2 = method + ':' + uri + ':' + encode(entityDigester.getDigest());
+            }
         } else {
             a2 = method + ':' + uri;
         }
@@ -413,7 +442,7 @@ public class DigestScheme extends RFC261
      * @param binaryData array containing the digest
      * @return encoded MD5, or <CODE>null</CODE> if encoding failed
      */
-    private static String encode(byte[] binaryData) {
+    static String encode(byte[] binaryData) {
         int n = binaryData.length;
         char[] buffer = new char[n * 2];
         for (int i = 0; i < n; i++) {

Added: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java?rev=1158320&view=auto
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java (added)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java Tue Aug 16 15:13:36 2011
@@ -0,0 +1,75 @@
+/*
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.impl.auth;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+
+class HttpEntityDigester extends OutputStream {
+
+    private final MessageDigest digester;
+    private boolean closed;
+    private byte[] digest;
+
+    HttpEntityDigester(final MessageDigest digester) {
+        super();
+        this.digester = digester;
+        this.digester.reset();
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        if (this.closed) {
+            throw new IOException("Stream has been already closed");
+        }
+        this.digester.update((byte) b);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (this.closed) {
+            throw new IOException("Stream has been already closed");
+        }
+        this.digester.update(b, off, len);
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (this.closed) {
+            return;
+        }
+        this.closed = true;
+        this.digest = this.digester.digest();
+        super.close();
+    }
+
+    public byte[] getDigest() {
+        return this.digest;
+    }
+
+}

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/HttpEntityDigester.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java?rev=1158320&r1=1158319&r2=1158320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/auth/RFC2617Scheme.java Tue Aug 16 15:13:36 2011
@@ -52,13 +52,14 @@ public abstract class RFC2617Scheme exte
     /**
      * Authentication parameter map.
      */
-    private Map<String, String> params;
+    private final Map<String, String> params;
 
     /**
      * Default constructor for RFC2617 compliant authentication schemes.
      */
     public RFC2617Scheme() {
         super();
+        this.params = new HashMap<String, String>();
     }
 
     @Override
@@ -70,8 +71,7 @@ public abstract class RFC2617Scheme exte
         if (elements.length == 0) {
             throw new MalformedChallengeException("Authentication challenge is empty");
         }
-
-        this.params = new HashMap<String, String>(elements.length);
+        this.params.clear();
         for (HeaderElement element : elements) {
             this.params.put(element.getName(), element.getValue());
         }
@@ -83,9 +83,6 @@ public abstract class RFC2617Scheme exte
      * @return the map of authentication parameters
      */
     protected Map<String, String> getParameters() {
-        if (this.params == null) {
-            this.params = new HashMap<String, String>();
-        }
         return this.params;
     }
 
@@ -98,9 +95,6 @@ public abstract class RFC2617Scheme exte
      */
     public String getParameter(final String name) {
         if (name == null) {
-            throw new IllegalArgumentException("Parameter name may not be null");
-        }
-        if (this.params == null) {
             return null;
         }
         return this.params.get(name.toLowerCase(Locale.ENGLISH));

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java?rev=1158320&r1=1158319&r2=1158320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestBasicScheme.java Tue Aug 16 15:13:36 2011
@@ -35,6 +35,8 @@ import org.apache.http.auth.MalformedCha
 import org.apache.http.auth.UsernamePasswordCredentials;
 import org.apache.http.message.BasicHeader;
 import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpContext;
 import org.apache.http.util.EncodingUtils;
 import org.junit.Assert;
 import org.junit.Test;
@@ -76,7 +78,8 @@ public class TestBasicScheme {
         authscheme.processChallenge(challenge);
 
         HttpRequest request = new BasicHttpRequest("GET", "/");
-        Header authResponse = authscheme.authenticate(creds, request);
+        HttpContext context = new BasicHttpContext();
+        Header authResponse = authscheme.authenticate(creds, request, context);
 
         String expected = "Basic " + EncodingUtils.getAsciiString(
             Base64.encodeBase64(EncodingUtils.getAsciiBytes("testuser:testpass")));
@@ -98,7 +101,8 @@ public class TestBasicScheme {
         authscheme.processChallenge(challenge);
 
         HttpRequest request = new BasicHttpRequest("GET", "/");
-        Header authResponse = authscheme.authenticate(creds, request);
+        HttpContext context = new BasicHttpContext();
+        Header authResponse = authscheme.authenticate(creds, request, context);
 
         String expected = "Basic " + EncodingUtils.getAsciiString(
             Base64.encodeBase64(EncodingUtils.getAsciiBytes("testuser:testpass")));

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java?rev=1158320&r1=1158319&r2=1158320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestDigestScheme.java Tue Aug 16 15:13:36 2011
@@ -26,11 +26,15 @@
 
 package org.apache.http.impl.auth;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.MessageDigest;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.apache.http.Header;
 import org.apache.http.HeaderElement;
+import org.apache.http.HttpEntityEnclosingRequest;
 import org.apache.http.HttpRequest;
 import org.apache.http.auth.AuthScheme;
 import org.apache.http.auth.AuthenticationException;
@@ -38,9 +42,15 @@ import org.apache.http.auth.Credentials;
 import org.apache.http.auth.AUTH;
 import org.apache.http.auth.MalformedChallengeException;
 import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
 import org.apache.http.message.BasicHeader;
 import org.apache.http.message.BasicHeaderValueParser;
+import org.apache.http.message.BasicHttpEntityEnclosingRequest;
 import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.protocol.HttpContext;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -50,14 +60,14 @@ import org.junit.Test;
 public class TestDigestScheme {
 
     @Test(expected=MalformedChallengeException.class)
-    public void testDigestAuthenticationWithNoRealm() throws Exception {
+    public void testDigestAuthenticationEmptyChallenge1() throws Exception {
         Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, "Digest");
         AuthScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
     }
 
     @Test(expected=MalformedChallengeException.class)
-    public void testDigestAuthenticationWithNoRealm2() throws Exception {
+    public void testDigestAuthenticationEmptyChallenge2() throws Exception {
         Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, "Digest ");
         AuthScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
@@ -70,8 +80,11 @@ public class TestDigestScheme {
         HttpRequest request = new BasicHttpRequest("Simple", "/");
         Credentials cred = new UsernamePasswordCredentials("username","password");
         DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
+        Assert.assertTrue(authscheme.isComplete());
+        Assert.assertFalse(authscheme.isConnectionBased());
 
         Map<String, String> table = parseAuthResponse(authResponse);
         Assert.assertEquals("username", table.get("username"));
@@ -88,8 +101,9 @@ public class TestDigestScheme {
         HttpRequest request = new BasicHttpRequest("Simple", "/");
         Credentials cred = new UsernamePasswordCredentials("username","password");
         DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
 
         Map<String, String> table = parseAuthResponse(authResponse);
         Assert.assertEquals("username", table.get("username"));
@@ -100,6 +114,47 @@ public class TestDigestScheme {
     }
 
     @Test
+    public void testDigestAuthenticationInvalidInput() throws Exception {
+        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpRequest request = new BasicHttpRequest("Simple", "/");
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
+        authscheme.processChallenge(authChallenge);
+        try {
+            authscheme.authenticate(null, request, context);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException ex) {
+        }
+        try {
+            authscheme.authenticate(cred, null, context);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException ex) {
+        }
+    }
+
+    @Test
+    public void testDigestAuthenticationOverrideParameter() throws Exception {
+        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpRequest request = new BasicHttpRequest("Simple", "/");
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
+        authscheme.processChallenge(authChallenge);
+        authscheme.overrideParamter("realm", "other realm");
+        Header authResponse = authscheme.authenticate(cred, request, context);
+
+        Map<String, String> table = parseAuthResponse(authResponse);
+        Assert.assertEquals("username", table.get("username"));
+        Assert.assertEquals("other realm", table.get("realm"));
+        Assert.assertEquals("/", table.get("uri"));
+        Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+        Assert.assertEquals("3f211de10463cbd055ab4cd9c5158eac", table.get("response"));
+    }
+
+    @Test
     public void testDigestAuthenticationWithSHA() throws Exception {
         String challenge = "Digest realm=\"realm1\", " +
                 "nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
@@ -107,9 +162,10 @@ public class TestDigestScheme {
         Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
         HttpRequest request = new BasicHttpRequest("Simple", "/");
         Credentials cred = new UsernamePasswordCredentials("username","password");
+        HttpContext context = new BasicHttpContext();
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
 
         Map<String, String> table = parseAuthResponse(authResponse);
         Assert.assertEquals("username", table.get("username"));
@@ -125,9 +181,10 @@ public class TestDigestScheme {
         Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
         HttpRequest request = new BasicHttpRequest("Simple", "/?param=value");
         Credentials cred = new UsernamePasswordCredentials("username","password");
+        HttpContext context = new BasicHttpContext();
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
 
         Map<String, String> table = parseAuthResponse(authResponse);
         Assert.assertEquals("username", table.get("username"));
@@ -146,9 +203,10 @@ public class TestDigestScheme {
 
         Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge1);
         HttpRequest request = new BasicHttpRequest("Simple", "/");
+        HttpContext context = new BasicHttpContext();
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
 
         Map<String, String> table = parseAuthResponse(authResponse);
         Assert.assertEquals("username", table.get("username"));
@@ -160,7 +218,7 @@ public class TestDigestScheme {
         authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge2);
         DigestScheme authscheme2 = new DigestScheme();
         authscheme2.processChallenge(authChallenge);
-        authResponse = authscheme2.authenticate(cred2, request);
+        authResponse = authscheme2.authenticate(cred2, request, context);
 
         table = parseAuthResponse(authResponse);
         Assert.assertEquals("uname2", table.get("username"));
@@ -170,6 +228,32 @@ public class TestDigestScheme {
         Assert.assertEquals("0283edd9ef06a38b378b3b74661391e9", table.get("response"));
     }
 
+    @Test(expected=AuthenticationException.class)
+    public void testDigestAuthenticationNoRealm() throws Exception {
+        String challenge = "Digest no-realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpContext context = new BasicHttpContext();
+        DigestScheme authscheme = new DigestScheme();
+        authscheme.processChallenge(authChallenge);
+
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        HttpRequest request = new BasicHttpRequest("Simple", "/");
+        authscheme.authenticate(cred, request, context);
+    }
+
+    @Test(expected=AuthenticationException.class)
+    public void testDigestAuthenticationNoNonce() throws Exception {
+        String challenge = "Digest realm=\"realm1\", no-nonce=\"f2a3f18799759d4f1a1c068b92b573cb\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpContext context = new BasicHttpContext();
+        DigestScheme authscheme = new DigestScheme();
+        authscheme.processChallenge(authChallenge);
+
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        HttpRequest request = new BasicHttpRequest("Simple", "/");
+        authscheme.authenticate(cred, request, context);
+    }
+
     /**
      * Test digest authentication using the MD5-sess algorithm.
      */
@@ -193,10 +277,11 @@ public class TestDigestScheme {
 
         Credentials cred = new UsernamePasswordCredentials(username, password);
         HttpRequest request = new BasicHttpRequest("Simple", "/");
+        HttpContext context = new BasicHttpContext();
 
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
         String response = authResponse.getValue();
 
         Assert.assertTrue(response.indexOf("nc=00000001") > 0); // test for quotes
@@ -239,10 +324,11 @@ public class TestDigestScheme {
         Credentials cred = new UsernamePasswordCredentials(username, password);
 
         HttpRequest request = new BasicHttpRequest("Simple", "/");
+        HttpContext context = new BasicHttpContext();
 
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge);
-        Header authResponse = authscheme.authenticate(cred, request);
+        Header authResponse = authscheme.authenticate(cred, request, context);
 
         Map<String, String> table = parseAuthResponse(authResponse);
         Assert.assertEquals(username, table.get("username"));
@@ -283,7 +369,8 @@ public class TestDigestScheme {
 
         Credentials cred = new UsernamePasswordCredentials(username, password);
         HttpRequest request = new BasicHttpRequest("Simple", "/");
-        authscheme.authenticate(cred, request);
+        HttpContext context = new BasicHttpContext();
+        authscheme.authenticate(cred, request, context);
     }
 
     /**
@@ -312,7 +399,8 @@ public class TestDigestScheme {
 
         Credentials cred = new UsernamePasswordCredentials(username, password);
         HttpRequest request = new BasicHttpRequest("Simple", "/");
-        authscheme.authenticate(cred, request);
+        HttpContext context = new BasicHttpContext();
+        authscheme.authenticate(cred, request, context);
     }
 
     @Test
@@ -346,24 +434,25 @@ public class TestDigestScheme {
         Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1);
         HttpRequest request = new BasicHttpRequest("GET", "/");
         Credentials cred = new UsernamePasswordCredentials("username","password");
+        HttpContext context = new BasicHttpContext();
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge1);
-        Header authResponse1 = authscheme.authenticate(cred, request);
+        Header authResponse1 = authscheme.authenticate(cred, request, context);
         Map<String, String> table1 = parseAuthResponse(authResponse1);
         Assert.assertEquals("00000001", table1.get("nc"));
-        Header authResponse2 = authscheme.authenticate(cred, request);
+        Header authResponse2 = authscheme.authenticate(cred, request, context);
         Map<String, String> table2 = parseAuthResponse(authResponse2);
         Assert.assertEquals("00000002", table2.get("nc"));
         String challenge2 = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", qop=auth";
         Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2);
         authscheme.processChallenge(authChallenge2);
-        Header authResponse3 = authscheme.authenticate(cred, request);
+        Header authResponse3 = authscheme.authenticate(cred, request, context);
         Map<String, String> table3 = parseAuthResponse(authResponse3);
         Assert.assertEquals("00000003", table3.get("nc"));
         String challenge3 = "Digest realm=\"realm1\", nonce=\"e273f1776275974f1a120d8b92c5b3cb\", qop=auth";
         Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3);
         authscheme.processChallenge(authChallenge3);
-        Header authResponse4 = authscheme.authenticate(cred, request);
+        Header authResponse4 = authscheme.authenticate(cred, request, context);
         Map<String, String> table4 = parseAuthResponse(authResponse4);
         Assert.assertEquals("00000001", table4.get("nc"));
     }
@@ -375,15 +464,16 @@ public class TestDigestScheme {
         Header authChallenge1 = new BasicHeader(AUTH.WWW_AUTH, challenge1);
         HttpRequest request = new BasicHttpRequest("GET", "/");
         Credentials cred = new UsernamePasswordCredentials("username","password");
+        HttpContext context = new BasicHttpContext();
         DigestScheme authscheme = new DigestScheme();
         authscheme.processChallenge(authChallenge1);
-        Header authResponse1 = authscheme.authenticate(cred, request);
+        Header authResponse1 = authscheme.authenticate(cred, request, context);
         Map<String, String> table1 = parseAuthResponse(authResponse1);
         Assert.assertEquals("00000001", table1.get("nc"));
         String cnonce1 = authscheme.getCnonce();
         String sessionKey1 = authscheme.getA1();
 
-        Header authResponse2 = authscheme.authenticate(cred, request);
+        Header authResponse2 = authscheme.authenticate(cred, request, context);
         Map<String, String> table2 = parseAuthResponse(authResponse2);
         Assert.assertEquals("00000002", table2.get("nc"));
         String cnonce2 = authscheme.getCnonce();
@@ -396,7 +486,7 @@ public class TestDigestScheme {
             "charset=utf-8, realm=\"subnet.domain.com\"";
         Header authChallenge2 = new BasicHeader(AUTH.WWW_AUTH, challenge2);
         authscheme.processChallenge(authChallenge2);
-        Header authResponse3 = authscheme.authenticate(cred, request);
+        Header authResponse3 = authscheme.authenticate(cred, request, context);
         Map<String, String> table3 = parseAuthResponse(authResponse3);
         Assert.assertEquals("00000003", table3.get("nc"));
 
@@ -410,7 +500,7 @@ public class TestDigestScheme {
             "charset=utf-8, realm=\"subnet.domain.com\"";
         Header authChallenge3 = new BasicHeader(AUTH.WWW_AUTH, challenge3);
         authscheme.processChallenge(authChallenge3);
-        Header authResponse4 = authscheme.authenticate(cred, request);
+        Header authResponse4 = authscheme.authenticate(cred, request, context);
         Map<String, String> table4 = parseAuthResponse(authResponse4);
         Assert.assertEquals("00000001", table4.get("nc"));
 
@@ -420,4 +510,113 @@ public class TestDigestScheme {
         Assert.assertFalse(cnonce1.equals(cnonce4));
         Assert.assertFalse(sessionKey1.equals(sessionKey4));
     }
+
+    @Test
+    public void testHttpEntityDigest() throws Exception {
+        HttpEntityDigester digester = new HttpEntityDigester(MessageDigest.getInstance("MD5"));
+        Assert.assertNull(digester.getDigest());
+        digester.write('a');
+        digester.write('b');
+        digester.write('c');
+        digester.write(0xe4);
+        digester.write(0xf6);
+        digester.write(0xfc);
+        digester.write(new byte[] { 'a', 'b', 'c'});
+        Assert.assertNull(digester.getDigest());
+        digester.close();
+        Assert.assertEquals("acd2b59cd01c7737d8069015584c6cac", DigestScheme.encode(digester.getDigest()));
+        try {
+            digester.write('a');
+            Assert.fail("IOException should have been thrown");
+        } catch (IOException ex) {
+        }
+        try {
+            digester.write(new byte[] { 'a', 'b', 'c'});
+            Assert.fail("IOException should have been thrown");
+        } catch (IOException ex) {
+        }
+    }
+
+    @Test
+    public void testDigestAuthenticationQopAuthInt() throws Exception {
+        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+                "qop=\"auth,auth-int\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+        request.setEntity(new StringEntity("abc\u00e4\u00f6\u00fcabc", HTTP.DEFAULT_CONTENT_CHARSET));
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
+        authscheme.processChallenge(authChallenge);
+        Header authResponse = authscheme.authenticate(cred, request, context);
+
+        Assert.assertEquals("Post:/:acd2b59cd01c7737d8069015584c6cac", authscheme.getA2());
+
+        Map<String, String> table = parseAuthResponse(authResponse);
+        Assert.assertEquals("username", table.get("username"));
+        Assert.assertEquals("realm1", table.get("realm"));
+        Assert.assertEquals("/", table.get("uri"));
+        Assert.assertEquals("auth-int", table.get("qop"));
+        Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+    }
+
+    @Test
+    public void testDigestAuthenticationQopAuthIntNullEntity() throws Exception {
+        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+                "qop=\"auth,auth-int\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
+        authscheme.processChallenge(authChallenge);
+        Header authResponse = authscheme.authenticate(cred, request, context);
+
+        Assert.assertEquals("Post:/:d41d8cd98f00b204e9800998ecf8427e", authscheme.getA2());
+
+        Map<String, String> table = parseAuthResponse(authResponse);
+        Assert.assertEquals("username", table.get("username"));
+        Assert.assertEquals("realm1", table.get("realm"));
+        Assert.assertEquals("/", table.get("uri"));
+        Assert.assertEquals("auth-int", table.get("qop"));
+        Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+    }
+
+    @Test
+    public void testDigestAuthenticationQopAuthOrAuthIntNonRepeatableEntity() throws Exception {
+        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+                "qop=\"auth,auth-int\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+        request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1));
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
+        authscheme.processChallenge(authChallenge);
+        Header authResponse = authscheme.authenticate(cred, request, context);
+
+        Assert.assertEquals("Post:/", authscheme.getA2());
+
+        Map<String, String> table = parseAuthResponse(authResponse);
+        Assert.assertEquals("username", table.get("username"));
+        Assert.assertEquals("realm1", table.get("realm"));
+        Assert.assertEquals("/", table.get("uri"));
+        Assert.assertEquals("auth", table.get("qop"));
+        Assert.assertEquals("f2a3f18799759d4f1a1c068b92b573cb", table.get("nonce"));
+    }
+
+    @Test(expected=AuthenticationException.class)
+    public void testDigestAuthenticationQopIntOnlyNonRepeatableEntity() throws Exception {
+        String challenge = "Digest realm=\"realm1\", nonce=\"f2a3f18799759d4f1a1c068b92b573cb\", " +
+                "qop=\"auth-int\"";
+        Header authChallenge = new BasicHeader(AUTH.WWW_AUTH, challenge);
+        HttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("Post", "/");
+        request.setEntity(new InputStreamEntity(new ByteArrayInputStream(new byte[] {'a'}), -1));
+        Credentials cred = new UsernamePasswordCredentials("username","password");
+        DigestScheme authscheme = new DigestScheme();
+        HttpContext context = new BasicHttpContext();
+        authscheme.processChallenge(authChallenge);
+        authscheme.authenticate(cred, request, context);
+    }
+
 }

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java?rev=1158320&r1=1158319&r2=1158320&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/auth/TestRFC2617Scheme.java Tue Aug 16 15:13:36 2011
@@ -73,11 +73,13 @@ public class TestRFC2617Scheme {
         authscheme.processChallenge(header);
 
         Assert.assertEquals("test", authscheme.getSchemeName());
+        Assert.assertEquals("test", authscheme.toString());
         Assert.assertEquals("realm1", authscheme.getParameter("realm"));
         Assert.assertEquals(null, authscheme.getParameter("test"));
         Assert.assertEquals("stuff", authscheme.getParameter("test1"));
         Assert.assertEquals("stuff, stuff", authscheme.getParameter("test2"));
         Assert.assertEquals("\"crap", authscheme.getParameter("test3"));
+        Assert.assertEquals(null, authscheme.getParameter(null));
     }
 
     @Test
@@ -87,12 +89,23 @@ public class TestRFC2617Scheme {
         buffer.append(" WWW-Authenticate:    Test       realm=\"realm1\"");
         Header header = new BufferedHeader(buffer);
 
+
         authscheme.processChallenge(header);
 
         Assert.assertEquals("test", authscheme.getSchemeName());
         Assert.assertEquals("realm1", authscheme.getParameter("realm"));
     }
 
+    @Test
+    public void testNullHeader() throws Exception {
+        TestAuthScheme authscheme = new TestAuthScheme();
+        try {
+            authscheme.processChallenge(null);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException ex) {
+        }
+    }
+
     @Test(expected=MalformedChallengeException.class)
     public void testInvalidHeader() throws Exception {
         TestAuthScheme authscheme = new TestAuthScheme();
@@ -101,6 +114,13 @@ public class TestRFC2617Scheme {
     }
 
     @Test(expected=MalformedChallengeException.class)
+    public void testInvalidSchemeName() throws Exception {
+        TestAuthScheme authscheme = new TestAuthScheme();
+        Header header = new BasicHeader(AUTH.WWW_AUTH, "Not-a-Test realm=\"realm1\"");
+        authscheme.processChallenge(header);
+    }
+
+    @Test(expected=MalformedChallengeException.class)
     public void testEmptyHeader() throws Exception {
         TestAuthScheme authscheme = new TestAuthScheme();
         Header header = new BasicHeader(AUTH.WWW_AUTH, "Test    ");