Index: java/org/apache/tomcat/util/http/Cookies.java =================================================================== --- java/org/apache/tomcat/util/http/Cookies.java (revision 1552814) +++ java/org/apache/tomcat/util/http/Cookies.java (working copy) @@ -508,14 +508,7 @@ private static final int getTokenEndPosition(byte bytes[], int off, int end, int version, boolean isName){ int pos = off; - while (pos < end && - (!CookieSupport.isHttpSeparator((char)bytes[pos]) || - version == 0 && - CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 && - bytes[pos] != '=' && - !CookieSupport.isV0Separator((char)bytes[pos]) || - !isName && bytes[pos] == '=' && - CookieSupport.ALLOW_EQUALS_IN_VALUE)) { + while (pos < end && allowInToken(bytes[pos], version, isName)) { pos++; } @@ -525,6 +518,34 @@ return pos; } + private static boolean allowInToken(byte b, int version, boolean isName) { + // byte is signed so cast into a positive int for comparisons + int octet = ((int)b) & 0xff; + + // disallow all controls + if (octet < 0x20 && octet != 0x09 || octet >= 0x7f && octet < 0xa0) { + throw new IllegalArgumentException( + "Control character in cookie value or attribute."); + } + + // values 0xa0-0xff are allowed in V0 values, otherwise disallow + if (octet >= 0x80) { + if (isName || version != 0) { + throw new IllegalArgumentException( + "Control character in cookie value or attribute."); + } + return true; + } + + return !CookieSupport.isHttpSeparator((char) b) || + version == 0 && + CookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0 && + b != '=' && + !CookieSupport.isV0Separator((char) b) || + !isName && b == '=' && + CookieSupport.ALLOW_EQUALS_IN_VALUE; + } + /** * Given a starting position after an initial quote character, this gets * the position of the end quote. This escapes anything after a '\' char Index: test/org/apache/tomcat/util/http/TestCookies.java =================================================================== --- test/org/apache/tomcat/util/http/TestCookies.java (revision 1552814) +++ test/org/apache/tomcat/util/http/TestCookies.java (working copy) @@ -17,11 +17,108 @@ package org.apache.tomcat.util.http; +import java.nio.charset.StandardCharsets; + +import javax.servlet.http.Cookie; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; public class TestCookies { + private Cookies cookies; + @Before + public void init() { + this.cookies = new Cookies(null); + } + + @Test(expected = IllegalArgumentException.class) + public void disallow8bitInName() { + process("f\u00f6o=bar"); + } + + @Test(expected = IllegalArgumentException.class) + public void disallowControlInName() { + process("f\010o=bar"); + } + + @Test(expected = IllegalArgumentException.class) + public void disallow8BitControlInName() { + process("f\210o=bar"); + } + @Test + public void allow8BitInV0Value() { + process("foo=b\u00e1r"); + expect(makeCookie("foo", "b\u00e1r", 0)); + } + + @Test(expected = IllegalArgumentException.class) + public void disallow8bitInV1UnquotedValue() { + process("$Version=1; foo=b\u00e1r"); + } + + @Test + public void allow8bitInV1QuotedValue() { + process("$Version=1; foo=\"b\u00e1r\""); + expect(makeCookie("foo", "b\u00e1r", 1)); + } + + @Test(expected = IllegalArgumentException.class) + public void disallowControlInV0Value() { + process("foo=b\010r"); + } + + @Test(expected = IllegalArgumentException.class) + public void disallow8BitControlInV0Value() { + process("foo=b\210r"); + } + + @Test(expected = IllegalArgumentException.class) + public void disallowControlInV1UnquotedValue() { + process("$Version=1; foo=b\010r"); + } + + @Ignore + @Test(expected = IllegalArgumentException.class) + public void disallowControlInV1QuotedValue() { + process("$Version=1; foo=\"b\010r\""); + } + + @Test(expected = IllegalArgumentException.class) + public void disallow8BitControlInV1UnquotedValue() { + process("$Version=1; foo=b\210r"); + } + + @Ignore + @Test(expected = IllegalArgumentException.class) + public void disallow8BitControlInV1QuotedValue() { + process("$Version=1; foo=\"b\210r\""); + } + + private void process(String header) { + byte[] bytes = header.getBytes(StandardCharsets.ISO_8859_1); + cookies.processCookieHeader(bytes, 0, bytes.length); + } + + private void expect(Cookie... expected) { + Assert.assertEquals(expected.length, cookies.getCookieCount()); + for (int i = 0; i < expected.length; i++) { + ServerCookie actual = cookies.getCookie(i); + Assert.assertEquals(expected[i].getName(), actual.getName().toString()); + Assert.assertEquals(expected[i].getValue(), actual.getValue().toString()); + } + } + + private static Cookie makeCookie(String name, String value, int version) { + Cookie cookie = new Cookie(name, value); + cookie.setVersion(version); + return cookie; + } + + @Test public void testCookies() throws Exception { test("foo=bar; a=b", "foo", "bar", "a", "b"); test("foo=bar;a=b", "foo", "bar", "a", "b");