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 2020/07/01 10:28:12 UTC

[httpcomponents-client] branch master updated: RFC 7235 compliance, HTTPCLIENT-2086: fixed parsing of token68 based (base64-encoded) auth schemes.

This is an automated email from the ASF dual-hosted git repository.

olegk pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/httpcomponents-client.git


The following commit(s) were added to refs/heads/master by this push:
     new da28440  RFC 7235 compliance, HTTPCLIENT-2086: fixed parsing of token68 based (base64-encoded) auth schemes.
da28440 is described below

commit da28440a58c381abcbd34d648372a819902fabfd
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Wed Jun 24 20:52:50 2020 +0200

    RFC 7235 compliance, HTTPCLIENT-2086: fixed parsing of token68 based (base64-encoded) auth schemes.
---
 .../http/impl/auth/AuthChallengeParser.java        | 163 +++++++++++++------
 .../hc/client5/http/NameValuePairMatcher.java      |  65 ++++++++
 .../http/impl/auth/TestAuthChallengeParser.java    | 180 ++++++++++++---------
 3 files changed, 281 insertions(+), 127 deletions(-)

diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthChallengeParser.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthChallengeParser.java
index 6ad6c39..8fd57fe 100644
--- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthChallengeParser.java
+++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/AuthChallengeParser.java
@@ -38,6 +38,7 @@ import org.apache.hc.core5.http.ParseException;
 import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.apache.hc.core5.http.message.ParserCursor;
 import org.apache.hc.core5.http.message.TokenParser;
+import org.apache.hc.core5.util.TextUtils;
 
 /**
  * Authentication challenge parser.
@@ -58,22 +59,26 @@ public class AuthChallengeParser {
     // These private static variables must be treated as immutable and never exposed outside this class
     private static final BitSet TERMINATORS = TokenParser.INIT_BITSET(BLANK, EQUAL_CHAR, COMMA_CHAR);
     private static final BitSet DELIMITER = TokenParser.INIT_BITSET(COMMA_CHAR);
+    private static final BitSet SPACE = TokenParser.INIT_BITSET(BLANK);
 
-    NameValuePair parseTokenOrParameter(final CharSequence buffer, final ParserCursor cursor) {
+    static class ChallengeInt {
 
-        tokenParser.skipWhiteSpace(buffer, cursor);
-        final String token = tokenParser.parseToken(buffer, cursor, TERMINATORS);
-        if (!cursor.atEnd()) {
-            if (buffer.charAt(cursor.getPos()) == BLANK) {
-                tokenParser.skipWhiteSpace(buffer, cursor);
-            }
-            if (!cursor.atEnd() && buffer.charAt(cursor.getPos()) == EQUAL_CHAR) {
-                cursor.updatePos(cursor.getPos() + 1);
-                final String value = tokenParser.parseValue(buffer, cursor, DELIMITER);
-                return new BasicNameValuePair(token, value);
-            }
+        final String schemeName;
+        final List<NameValuePair> params;
+
+        ChallengeInt(final String schemeName) {
+            this.schemeName = schemeName;
+            this.params = new ArrayList<>();
         }
-        return new BasicNameValuePair(token, null);
+
+        @Override
+        public String toString() {
+            return "ChallengeInternal{" +
+                    "schemeName='" + schemeName + '\'' +
+                    ", params=" + params +
+                    '}';
+        }
+
     }
 
     /**
@@ -86,52 +91,114 @@ public class AuthChallengeParser {
      */
     public List<AuthChallenge> parse(
             final ChallengeType challengeType, final CharSequence buffer, final ParserCursor cursor) throws ParseException {
-
-        final List<AuthChallenge> list = new ArrayList<>();
-        String schemeName = null;
-        final List<NameValuePair> params = new ArrayList<>();
-        while (!cursor.atEnd()) {
-            final NameValuePair tokenOrParameter = parseTokenOrParameter(buffer, cursor);
-            if (tokenOrParameter.getValue() == null && !cursor.atEnd() && buffer.charAt(cursor.getPos()) != COMMA_CHAR) {
-                if (schemeName != null) {
-                    if (params.isEmpty()) {
-                        throw new ParseException("Malformed auth challenge");
-                    }
-                    list.add(createAuthChallenge(challengeType, schemeName, params));
+        tokenParser.skipWhiteSpace(buffer, cursor);
+        if (cursor.atEnd()) {
+            throw new ParseException("Malformed auth challenge");
+        }
+        final List<ChallengeInt> internalChallenges = new ArrayList<>();
+        final String schemeName = tokenParser.parseToken(buffer, cursor, SPACE);
+        if (TextUtils.isBlank(schemeName)) {
+            throw new ParseException("Malformed auth challenge");
+        }
+        ChallengeInt current = new ChallengeInt(schemeName);
+        while (current != null) {
+            internalChallenges.add(current);
+            current = parseChallenge(buffer, cursor, current);
+        }
+        final List<AuthChallenge> challenges = new ArrayList<>(internalChallenges.size());
+        for (final ChallengeInt internal : internalChallenges) {
+            final List<NameValuePair> params = internal.params;
+            String token68 = null;
+            if (params.size() == 1) {
+                final NameValuePair param = params.get(0);
+                if (param.getValue() == null) {
+                    token68 = param.getName();
                     params.clear();
                 }
-                schemeName = tokenOrParameter.getName();
-            } else {
-                params.add(tokenOrParameter);
-                if (!cursor.atEnd() && buffer.charAt(cursor.getPos()) != COMMA_CHAR) {
-                    schemeName = null;
-                }
-            }
-            if (!cursor.atEnd() && buffer.charAt(cursor.getPos()) == COMMA_CHAR) {
-                cursor.updatePos(cursor.getPos() + 1);
             }
+            challenges.add(
+                    new AuthChallenge(challengeType, internal.schemeName, token68, !params.isEmpty() ? params : null));
         }
-        list.add(createAuthChallenge(challengeType, schemeName, params));
-        return list;
+        return challenges;
     }
 
-    private static AuthChallenge createAuthChallenge(final ChallengeType challengeType, final String schemeName, final List<NameValuePair> params) throws ParseException {
-        if (schemeName != null) {
-            if (params.size() == 1) {
-                final NameValuePair nvp = params.get(0);
-                if (nvp.getValue() == null) {
-                    return new AuthChallenge(challengeType, schemeName, nvp.getName(), null);
+    ChallengeInt parseChallenge(
+            final CharSequence buffer,
+            final ParserCursor cursor,
+            final ChallengeInt currentChallenge) throws ParseException {
+        for (;;) {
+            tokenParser.skipWhiteSpace(buffer, cursor);
+            if (cursor.atEnd()) {
+                return null;
+            }
+            final String token = parseToken(buffer, cursor);
+            if (TextUtils.isBlank(token)) {
+                throw new ParseException("Malformed auth challenge");
+            }
+            tokenParser.skipWhiteSpace(buffer, cursor);
+
+            // it gets really messy here
+            if (cursor.atEnd()) {
+                // at the end of the header
+                currentChallenge.params.add(new BasicNameValuePair(token, null));
+            } else {
+                char ch = buffer.charAt(cursor.getPos());
+                if (ch == EQUAL_CHAR) {
+                    cursor.updatePos(cursor.getPos() + 1);
+                    final String value = tokenParser.parseValue(buffer, cursor, DELIMITER);
+                    tokenParser.skipWhiteSpace(buffer, cursor);
+                    if (!cursor.atEnd()) {
+                        ch = buffer.charAt(cursor.getPos());
+                        if (ch == COMMA_CHAR) {
+                            cursor.updatePos(cursor.getPos() + 1);
+                        }
+                    }
+                    currentChallenge.params.add(new BasicNameValuePair(token, value));
+                } else if (ch == COMMA_CHAR) {
+                    cursor.updatePos(cursor.getPos() + 1);
+                    currentChallenge.params.add(new BasicNameValuePair(token, null));
+                } else {
+                    // the token represents new challenge
+                    if (currentChallenge.params.isEmpty()) {
+                        throw new ParseException("Malformed auth challenge");
+                    }
+                    return new ChallengeInt(token);
                 }
             }
-            return new AuthChallenge(challengeType, schemeName, null, params.size() > 0 ? params : null);
         }
-        if (params.size() == 1) {
-            final NameValuePair nvp = params.get(0);
-            if (nvp.getValue() == null) {
-                return new AuthChallenge(challengeType, nvp.getName(), null, null);
+    }
+
+    String parseToken(final CharSequence buf, final ParserCursor cursor) {
+        final StringBuilder dst = new StringBuilder();
+        while (!cursor.atEnd()) {
+            int pos = cursor.getPos();
+            char current = buf.charAt(pos);
+            if (TERMINATORS.get(current)) {
+                // Here it gets really ugly
+                if (current == EQUAL_CHAR) {
+                    // it can be a start of a parameter value or token68 padding
+                    // Look ahead and see if there are more '=' or at end of buffer
+                    if (pos + 1 < cursor.getUpperBound() && buf.charAt(pos + 1) != EQUAL_CHAR) {
+                        break;
+                    }
+                    do {
+                        dst.append(current);
+                        pos++;
+                        cursor.updatePos(pos);
+                        if (cursor.atEnd()) {
+                            break;
+                        }
+                        current = buf.charAt(pos);
+                    } while (current == EQUAL_CHAR);
+                } else {
+                    break;
+                }
+            } else {
+                dst.append(current);
+                cursor.updatePos(pos + 1);
             }
         }
-        throw new ParseException("Malformed auth challenge");
+        return dst.toString();
     }
 
 }
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/NameValuePairMatcher.java b/httpclient5/src/test/java/org/apache/hc/client5/http/NameValuePairMatcher.java
new file mode 100644
index 0000000..f5d7e57
--- /dev/null
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/NameValuePairMatcher.java
@@ -0,0 +1,65 @@
+/*
+ * ====================================================================
+ * 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.hc.client5.http;
+
+import org.apache.hc.core5.http.NameValuePair;
+import org.apache.hc.core5.util.LangUtils;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+
+public class NameValuePairMatcher extends BaseMatcher<NameValuePair> {
+
+    private final String name;
+    private final String value;
+
+    public NameValuePairMatcher(final String name, final String value) {
+        this.name = name;
+        this.value = value;
+    }
+
+    @Override
+    public boolean matches(final Object item) {
+        if (item instanceof NameValuePair) {
+            final NameValuePair nvp = (NameValuePair) item;
+            return LangUtils.equals(nvp.getName(), name) && LangUtils.equals(nvp.getValue(), value);
+        }
+        return false;
+    }
+
+    @Override
+    public void describeTo(final Description description) {
+        description.appendText("equals ").appendValue(name).appendText("=").appendValue(value);
+    }
+
+    @Factory
+    public static Matcher<NameValuePair> equals(final String name, final String value) {
+        return new NameValuePairMatcher(name, value);
+    }
+
+}
diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestAuthChallengeParser.java b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestAuthChallengeParser.java
index 95fbe1a..f9613de 100644
--- a/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestAuthChallengeParser.java
+++ b/httpclient5/src/test/java/org/apache/hc/client5/http/impl/auth/TestAuthChallengeParser.java
@@ -28,14 +28,15 @@ package org.apache.hc.client5.http.impl.auth;
 
 import java.util.List;
 
+import org.apache.hc.client5.http.NameValuePairMatcher;
 import org.apache.hc.client5.http.auth.AuthChallenge;
 import org.apache.hc.client5.http.auth.ChallengeType;
 import org.apache.hc.client5.http.auth.StandardAuthScheme;
 import org.apache.hc.core5.http.NameValuePair;
 import org.apache.hc.core5.http.ParseException;
-import org.apache.hc.core5.http.message.BasicNameValuePair;
 import org.apache.hc.core5.http.message.ParserCursor;
 import org.apache.hc.core5.util.CharArrayBuffer;
+import org.hamcrest.CoreMatchers;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -50,102 +51,75 @@ public class TestAuthChallengeParser {
     }
 
     @Test
-    public void testParseBasicToken() throws Exception {
+    public void testParseTokenTerminatedByBlank() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("blah");
+        buffer.append("aaabbbbccc ");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("blah", nvp.getName());
-        Assert.assertEquals(null, nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc"));
     }
 
     @Test
-    public void testParseTokenWithBlank() throws Exception {
+    public void testParseTokenTerminatedByComma() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("blah ");
+        buffer.append("aaabbbbccc, ");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("blah", nvp.getName());
-        Assert.assertEquals(null, nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc"));
     }
 
     @Test
-    public void testParseTokenWithBlanks() throws Exception {
+    public void testParseTokenTerminatedByEndOfStream() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("  blah  blah ");
+        buffer.append("aaabbbbccc");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("blah", nvp.getName());
-        Assert.assertEquals(null, nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc"));
     }
 
     @Test
-    public void testParseTokenDelimited() throws Exception {
+    public void testParsePaddedToken68() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("blah,blah");
+        buffer.append("aaabbbbccc==== ");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("blah", nvp.getName());
-        Assert.assertEquals(null, nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc===="));
+        Assert.assertThat(cursor.atEnd(), CoreMatchers.equalTo(false));
+        Assert.assertThat(buffer.charAt(cursor.getPos()), CoreMatchers.equalTo(' '));
     }
 
     @Test
-    public void testParseParameterSimple() throws Exception {
+    public void testParsePaddedToken68SingleEqual() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("param=blah");
+        buffer.append("aaabbbbccc=");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("param", nvp.getName());
-        Assert.assertEquals("blah", nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc="));
+        Assert.assertThat(cursor.atEnd(), CoreMatchers.equalTo(true));
     }
 
     @Test
-    public void testParseParameterDelimited() throws Exception {
-        final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("param   =  blah  ,  ");
+    public void testParsePaddedToken68MultipleEquals() throws Exception {
+        final CharArrayBuffer buffer = new CharArrayBuffer(16);
+        buffer.append("aaabbbbccc======");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("param", nvp.getName());
-        Assert.assertEquals("blah", nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc======"));
+        Assert.assertThat(cursor.atEnd(), CoreMatchers.equalTo(true));
     }
 
     @Test
-    public void testParseParameterQuoted() throws Exception {
+    public void testParsePaddedToken68TerminatedByComma() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append(" param   =  \" blah  blah \"");
+        buffer.append("aaabbbbccc====,");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("param", nvp.getName());
-        Assert.assertEquals(" blah  blah ", nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc===="));
+        Assert.assertThat(cursor.atEnd(), CoreMatchers.equalTo(false));
+        Assert.assertThat(buffer.charAt(cursor.getPos()), CoreMatchers.equalTo(','));
     }
 
     @Test
-    public void testParseParameterEscaped() throws Exception {
+    public void testParseTokenTerminatedByParameter() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append(" param   =  \" blah  \\\"blah\\\" \"");
+        buffer.append("aaabbbbccc=blah");
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("param", nvp.getName());
-        Assert.assertEquals(" blah  \"blah\" ", nvp.getValue());
-    }
-
-    @Test
-    public void testParseParameterNoValue() throws Exception {
-        final CharArrayBuffer buffer = new CharArrayBuffer(64);
-        buffer.append("param   =  ,  ");
-        final ParserCursor cursor = new ParserCursor(0, buffer.length());
-        final NameValuePair nvp = parser.parseTokenOrParameter(buffer, cursor);
-        Assert.assertNotNull(nvp);
-        Assert.assertEquals("param", nvp.getName());
-        Assert.assertEquals("", nvp.getValue());
+        Assert.assertThat(parser.parseToken(buffer, cursor), CoreMatchers.equalTo("aaabbbbccc"));
+        Assert.assertThat(cursor.atEnd(), CoreMatchers.equalTo(false));
+        Assert.assertThat(buffer.charAt(cursor.getPos()), CoreMatchers.equalTo('='));
     }
 
     @Test
@@ -162,7 +136,7 @@ public class TestAuthChallengeParser {
         final List<NameValuePair> params = challenge1.getParams();
         Assert.assertNotNull(params);
         Assert.assertEquals(1, params.size());
-        assertNameValuePair(new BasicNameValuePair("realm", "blah"), params.get(0));
+        Assert.assertThat(params.get(0), NameValuePairMatcher.equals("realm", "blah"));
     }
 
     @Test
@@ -179,7 +153,7 @@ public class TestAuthChallengeParser {
         final List<NameValuePair> params = challenge1.getParams();
         Assert.assertNotNull(params);
         Assert.assertEquals(1, params.size());
-        assertNameValuePair(new BasicNameValuePair("realm", "blah"), params.get(0));
+        Assert.assertThat(params.get(0), NameValuePairMatcher.equals("realm", "blah"));
     }
 
     @Test
@@ -219,9 +193,9 @@ public class TestAuthChallengeParser {
         final List<NameValuePair> params1 = challenge1.getParams();
         Assert.assertNotNull(params1);
         Assert.assertEquals(3, params1.size());
-        assertNameValuePair(new BasicNameValuePair("realm", "blah"), params1.get(0));
-        assertNameValuePair(new BasicNameValuePair("param1", "this"), params1.get(1));
-        assertNameValuePair(new BasicNameValuePair("param2", "that"), params1.get(2));
+        Assert.assertThat(params1.get(0), NameValuePairMatcher.equals("realm", "blah"));
+        Assert.assertThat(params1.get(1), NameValuePairMatcher.equals("param1", "this"));
+        Assert.assertThat(params1.get(2), NameValuePairMatcher.equals("param2", "that"));
 
         final AuthChallenge challenge2 = challenges.get(1);
         Assert.assertEquals(StandardAuthScheme.BASIC, challenge2.getSchemeName());
@@ -229,10 +203,38 @@ public class TestAuthChallengeParser {
         final List<NameValuePair> params2 = challenge2.getParams();
         Assert.assertNotNull(params2);
         Assert.assertEquals(4, params2.size());
-        assertNameValuePair(new BasicNameValuePair("realm", "\"yada\""), params2.get(0));
-        assertNameValuePair(new BasicNameValuePair("this", null), params2.get(1));
-        assertNameValuePair(new BasicNameValuePair("that", ""), params2.get(2));
-        assertNameValuePair(new BasicNameValuePair("this-and-that", null), params2.get(3));
+        Assert.assertThat(params2.get(0), NameValuePairMatcher.equals("realm", "\"yada\""));
+        Assert.assertThat(params2.get(1), NameValuePairMatcher.equals("this", null));
+        Assert.assertThat(params2.get(2), NameValuePairMatcher.equals("that", ""));
+        Assert.assertThat(params2.get(3), NameValuePairMatcher.equals("this-and-that", null));
+    }
+
+    @Test
+    public void testParseMultipleAuthChallengeWithParamsContainingComma() throws Exception {
+        final CharArrayBuffer buffer = new CharArrayBuffer(64);
+        buffer.append(StandardAuthScheme.BASIC + " realm=blah, param1 = \"this, param2=that\", " +
+                StandardAuthScheme.BASIC + " realm=\"\\\"yada,,,,\\\"\"");
+        final ParserCursor cursor = new ParserCursor(0, buffer.length());
+        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
+        Assert.assertNotNull(challenges);
+        Assert.assertEquals(2, challenges.size());
+
+        final AuthChallenge challenge1 = challenges.get(0);
+        Assert.assertEquals(StandardAuthScheme.BASIC, challenge1.getSchemeName());
+        Assert.assertEquals(null, challenge1.getValue());
+        final List<NameValuePair> params1 = challenge1.getParams();
+        Assert.assertNotNull(params1);
+        Assert.assertEquals(2, params1.size());
+        Assert.assertThat(params1.get(0), NameValuePairMatcher.equals("realm", "blah"));
+        Assert.assertThat(params1.get(1), NameValuePairMatcher.equals("param1", "this, param2=that"));
+
+        final AuthChallenge challenge2 = challenges.get(1);
+        Assert.assertEquals(StandardAuthScheme.BASIC, challenge2.getSchemeName());
+        Assert.assertEquals(null, challenge2.getValue());
+        final List<NameValuePair> params2 = challenge2.getParams();
+        Assert.assertNotNull(params2);
+        Assert.assertEquals(1, params2.size());
+        Assert.assertThat(params2.get(0), NameValuePairMatcher.equals("realm", "\"yada,,,,\""));
     }
 
     @Test
@@ -304,12 +306,12 @@ public class TestAuthChallengeParser {
         final List<NameValuePair> params1 = challenge1.getParams();
         Assert.assertNotNull(params1);
         Assert.assertEquals(2, params1.size());
-        assertNameValuePair(new BasicNameValuePair("blah", null), params1.get(0));
-        assertNameValuePair(new BasicNameValuePair("blah", null), params1.get(1));
+        Assert.assertThat(params1.get(0), NameValuePairMatcher.equals("blah", null));
+        Assert.assertThat(params1.get(1), NameValuePairMatcher.equals("blah", null));
     }
 
     @Test
-    public void testParseNTLMAuthChallenge() throws Exception {
+    public void testParseEmptyNTLMAuthChallenge() throws Exception {
         final CharArrayBuffer buffer = new CharArrayBuffer(64);
         buffer.append(StandardAuthScheme.NTLM);
         final ParserCursor cursor = new ParserCursor(0, buffer.length());
@@ -321,12 +323,32 @@ public class TestAuthChallengeParser {
         Assert.assertEquals(null, challenge1.getValue());
     }
 
-    private static void assertNameValuePair (
-            final NameValuePair expected,
-            final NameValuePair result) {
-        Assert.assertNotNull(result);
-        Assert.assertEquals(expected.getName(), result.getName());
-        Assert.assertEquals(expected.getValue(), result.getValue());
+    @Test
+    public void testParseParameterAndToken68AuthChallengeMix() throws Exception {
+        final CharArrayBuffer buffer = new CharArrayBuffer(64);
+        buffer.append("scheme1 aaaa  , scheme2 aaaa==,  scheme3 aaaa=aaaa, scheme4 aaaa=");
+        final ParserCursor cursor = new ParserCursor(0, buffer.length());
+        final List<AuthChallenge> challenges = parser.parse(ChallengeType.TARGET, buffer, cursor);
+        Assert.assertNotNull(challenges);
+        Assert.assertEquals(4, challenges.size());
+        final AuthChallenge challenge1 = challenges.get(0);
+        Assert.assertThat(challenge1.getSchemeName(), CoreMatchers.equalTo("scheme1"));
+        Assert.assertThat(challenge1.getValue(), CoreMatchers.equalTo("aaaa"));
+        Assert.assertThat(challenge1.getParams(), CoreMatchers.nullValue());
+        final AuthChallenge challenge2 = challenges.get(1);
+        Assert.assertThat(challenge2.getSchemeName(), CoreMatchers.equalTo("scheme2"));
+        Assert.assertThat(challenge2.getValue(), CoreMatchers.equalTo("aaaa=="));
+        Assert.assertThat(challenge2.getParams(), CoreMatchers.nullValue());
+        final AuthChallenge challenge3 = challenges.get(2);
+        Assert.assertThat(challenge3.getSchemeName(), CoreMatchers.equalTo("scheme3"));
+        Assert.assertThat(challenge3.getValue(), CoreMatchers.nullValue());
+        Assert.assertThat(challenge3.getParams(), CoreMatchers.notNullValue());
+        Assert.assertThat(challenge3.getParams().size(), CoreMatchers.equalTo(1));
+        Assert.assertThat(challenge3.getParams().get(0), NameValuePairMatcher.equals("aaaa", "aaaa"));
+        final AuthChallenge challenge4 = challenges.get(3);
+        Assert.assertThat(challenge4.getSchemeName(), CoreMatchers.equalTo("scheme4"));
+        Assert.assertThat(challenge4.getValue(), CoreMatchers.equalTo("aaaa="));
+        Assert.assertThat(challenge4.getParams(), CoreMatchers.nullValue());
     }
 
 }