Correctly handle a digest authorization header when one of the hex field values ends the header with in an invalid character.
Expand the test cases to improve coverage including this issue.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk@1832548 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/java/org/apache/tomcat/util/http/parser/HttpParser.java b/java/org/apache/tomcat/util/http/parser/HttpParser.java
index 64497a5..9618a0c 100644
--- a/java/org/apache/tomcat/util/http/parser/HttpParser.java
+++ b/java/org/apache/tomcat/util/http/parser/HttpParser.java
@@ -337,38 +337,36 @@
     }
 
 
-    // Skip any LWS and return the next char
-    static int skipLws(Reader input, boolean withReset) throws IOException {
+    // Skip any LWS and position to read the next character. The next character
+    // is returned as being able to 'peek()' it allows a small optimisation in
+    // some cases.
+    static int skipLws(Reader input) throws IOException {
 
-        if (withReset) {
-            input.mark(1);
-        }
+        input.mark(1);
         int c = input.read();
 
         while (c == 32 || c == 9 || c == 10 || c == 13) {
-            if (withReset) {
-                input.mark(1);
-            }
+            input.mark(1);
             c = input.read();
         }
 
-        if (withReset) {
-            input.reset();
-        }
+        input.reset();
         return c;
     }
 
     static SkipResult skipConstant(Reader input, String constant) throws IOException {
         int len = constant.length();
 
-        int c = skipLws(input, false);
+        skipLws(input);
+        input.mark(len);
+        int c = input.read();
 
         for (int i = 0; i < len; i++) {
             if (i == 0 && c == -1) {
                 return SkipResult.EOF;
             }
             if (c != constant.charAt(i)) {
-                input.skip(-(i + 1));
+                input.reset();
                 return SkipResult.NOT_FOUND;
             }
             if (i != (len - 1)) {
@@ -386,14 +384,18 @@
     static String readToken(Reader input) throws IOException {
         StringBuilder result = new StringBuilder();
 
-        int c = skipLws(input, false);
+        skipLws(input);
+        input.mark(1);
+        int c = input.read();
 
         while (c != -1 && isToken(c)) {
             result.append((char) c);
+            input.mark(1);
             c = input.read();
         }
-        // Skip back so non-token character is available for next read
-        input.skip(-1);
+        // Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
+        // once the end of the String has been reached.
+        input.reset();
 
         if (c != -1 && result.length() == 0) {
             return null;
@@ -409,7 +411,8 @@
      */
     static String readQuotedString(Reader input, boolean returnQuoted) throws IOException {
 
-        int c = skipLws(input, false);
+        skipLws(input);
+        int c = input.read();
 
         if (c != '"') {
             return null;
@@ -445,8 +448,8 @@
     static String readTokenOrQuotedString(Reader input, boolean returnQuoted)
             throws IOException {
 
-        // Go back so first non-LWS character is available to be read again
-        int c = skipLws(input, true);
+        // Peek at next character to enable correct method to be called
+        int c = skipLws(input);
 
         if (c == '"') {
             return readQuotedString(input, returnQuoted);
@@ -472,7 +475,9 @@
         StringBuilder result = new StringBuilder();
         boolean quoted = false;
 
-        int c = skipLws(input, false);
+        skipLws(input);
+        input.mark(1);
+        int c = input.read();
 
         if (c == '"') {
             quoted = true;
@@ -481,10 +486,12 @@
         } else {
             result.append((char) c);
         }
+        input.mark(1);
         c = input.read();
 
         while (c != -1 && isToken(c)) {
             result.append((char) c);
+            input.mark(1);
             c = input.read();
         }
 
@@ -493,8 +500,9 @@
                 return null;
             }
         } else {
-            // Skip back so non-token character is available for next read
-            input.skip(-1);
+            // Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
+            // once the end of the String has been reached.
+            input.reset();
         }
 
         if (c != -1 && result.length() == 0) {
@@ -523,7 +531,9 @@
         StringBuilder result = new StringBuilder();
         boolean quoted = false;
 
-        int c = skipLws(input, false);
+        skipLws(input);
+        input.mark(1);
+        int c = input.read();
 
         if (c == '"') {
             quoted = true;
@@ -535,6 +545,7 @@
             }
             result.append((char) c);
         }
+        input.mark(1);
         c = input.read();
 
         while (c != -1 && isHex(c)) {
@@ -542,6 +553,7 @@
                 c -= ('A' - 'a');
             }
             result.append((char) c);
+            input.mark(1);
             c = input.read();
         }
 
@@ -550,8 +562,9 @@
                 return null;
             }
         } else {
-            // Skip back so non-hex character is available for next read
-            input.skip(-1);
+            // Use mark(1)/reset() rather than skip(-1) since skip() is a NOP
+            // once the end of the String has been reached.
+            input.reset();
         }
 
         if (c != -1 && result.length() == 0) {
@@ -562,7 +575,8 @@
     }
 
     static double readWeight(Reader input, char delimiter) throws IOException {
-        int c = skipLws(input, false);
+        skipLws(input);
+        int c = input.read();
         if (c == -1 || c == delimiter) {
             // No q value just whitespace
             return 1;
@@ -572,7 +586,8 @@
             return 0;
         }
         // RFC 7231 does not allow whitespace here but be tolerant
-        c = skipLws(input, false);
+        skipLws(input);
+        c = input.read();
         if (c != '=') {
             // Malformed. Use quality of zero so it is dropped.
             skipUntil(input, c, delimiter);
@@ -580,7 +595,8 @@
         }
 
         // RFC 7231 does not allow whitespace here but be tolerant
-        c = skipLws(input, false);
+        skipLws(input);
+        c = input.read();
 
         // Should be no more than 3 decimal places
         StringBuilder value = new StringBuilder(5);
diff --git a/test/org/apache/tomcat/util/http/parser/TestAuthorizationDigest.java b/test/org/apache/tomcat/util/http/parser/TestAuthorizationDigest.java
index 0e28907..2f14841 100644
--- a/test/org/apache/tomcat/util/http/parser/TestAuthorizationDigest.java
+++ b/test/org/apache/tomcat/util/http/parser/TestAuthorizationDigest.java
@@ -126,6 +126,17 @@
     }
 
     @Test
+    public void testEndWithLhexReverse() throws Exception {
+        String header = "Digest nc=10000000";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("10000000", result.get("nc"));
+    }
+
+    @Test
     public void testQuotedLhex() throws Exception {
         String header = "Digest nc=\"09abcdef\"";
 
@@ -137,6 +148,39 @@
     }
 
     @Test
+    public void testQuotedLhexReverse() throws Exception {
+        String header = "Digest nc=\"fedcba90\"";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("fedcba90", result.get("nc"));
+    }
+
+    @Test
+    public void testLhex() throws Exception {
+        String header = "Digest nc=09abcdef";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("09abcdef", result.get("nc"));
+    }
+
+    @Test
+    public void testLhexReverse() throws Exception {
+        String header = "Digest nc=fedcba90";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("fedcba90", result.get("nc"));
+    }
+
+    @Test
     public void testQuotedLhexUppercase() throws Exception {
         String header = "Digest nc=\"00ABCDEF\"";
 
@@ -148,6 +192,39 @@
     }
 
     @Test
+    public void testQuotedLhexUppercaseReverse() throws Exception {
+        String header = "Digest nc=\"FEDCBA00\"";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("fedcba00", result.get("nc"));
+    }
+
+    @Test
+    public void testLhexUppercase() throws Exception {
+        String header = "Digest nc=00ABCDEF";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("00abcdef", result.get("nc"));
+    }
+
+    @Test
+    public void testLhexUppercaseReverse() throws Exception {
+        String header = "Digest nc=FEDCBA00";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertEquals("fedcba00", result.get("nc"));
+    }
+
+    @Test
     public void testUnclosedQuotedLhex() throws Exception {
         String header = "Digest nc=\"00000001";
 
@@ -159,6 +236,28 @@
     }
 
     @Test
+    public void testEmptyLhex() throws Exception {
+        String header = "Digest nc=";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void testQuotedEmptyLhex() throws Exception {
+        String header = "Digest nc=\"\"";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+
+        Assert.assertNull(result);
+    }
+
+    @Test
     public void testUnclosedQuotedString1() throws Exception {
         String header = "Digest username=\"test";
 
@@ -209,7 +308,17 @@
     }
 
     @Test
-    public void testNonTokenQop() throws Exception {
+    public void testEmptyQuotedTokenQop() throws Exception {
+        String header = "Digest qop=\"\"";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void testNonTokenQop01() throws Exception {
         String header = "Digest qop=au{th";
 
         StringReader input = new StringReader(header);
@@ -219,6 +328,16 @@
     }
 
     @Test
+    public void testNonTokenQop02() throws Exception {
+        String header = "Digest qop=auth{";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+        Assert.assertNull(result);
+    }
+
+    @Test
     public void testQuotedNonTokenQop() throws Exception {
         String header = "Digest qop=\"au{th\"";
 
@@ -279,7 +398,7 @@
     }
 
     @Test
-    public void testWrongCharacterInHex() throws Exception {
+    public void testWrongCharacterInHex01() throws Exception {
         String header = "Digest nc=\u044f";
 
         StringReader input = new StringReader(header);
@@ -289,6 +408,26 @@
     }
 
     @Test
+    public void testWrongCharacterInHex02() throws Exception {
+        String header = "Digest nc=aaa\u044f";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+        Assert.assertNull(result);
+    }
+
+    @Test
+    public void testWrongCharacterInHex03() throws Exception {
+        String header = "Digest nc=\u044faaa";
+
+        StringReader input = new StringReader(header);
+
+        Map<String,String> result = Authorization.parseAuthorizationDigest(input);
+        Assert.assertNull(result);
+    }
+
+    @Test
     public void testWrongCharacterInQuotedHex() throws Exception {
         String header = "Digest nc=\"\u044f\"";
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 8046dba..343e420 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -127,6 +127,10 @@
         Correctly handle a digest authorization header when the user name
         contains an escaped character. (markt)
       </fix>
+      <fix>
+        Correctly handle a digest authorization header when one of the hex
+        field values ends the header with in an invalid character. (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Jasper">