Merge remote-tracking branch 'origin/jetty-11.0.x' into jetty-12.0.x

This commit is contained in:
gregw 2023-02-21 22:01:31 +11:00
commit b720d65f4f
10 changed files with 390 additions and 141 deletions

View File

@ -128,13 +128,12 @@ public class CookieCompliance implements ComplianceViolation.Mode
* <p>A CookieCompliance mode that enforces <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> compliance,
* but allows:</p>
* <ul>
* <li>{@link Violation#ATTRIBUTES}</li>
* <li>{@link Violation#INVALID_COOKIES}</li>
* <li>{@link Violation#OPTIONAL_WHITE_SPACE}</li>
* </ul>
*/
public static final CookieCompliance RFC6265 = new CookieCompliance("RFC6265", of(
Violation.ATTRIBUTES, Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE)
Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE)
);
/**
@ -146,6 +145,7 @@ public class CookieCompliance implements ComplianceViolation.Mode
* <p>A CookieCompliance mode that enforces <a href="https://tools.ietf.org/html/rfc6265">RFC 6265</a> compliance,
* but allows:</p>
* <ul>
* <li>{@link Violation#ATTRIBUTES}</li>
* <li>{@link Violation#BAD_QUOTES}</li>
* <li>{@link Violation#ESCAPE_IN_QUOTES}</li>
* <li>{@link Violation#INVALID_COOKIES}</li>
@ -153,8 +153,8 @@ public class CookieCompliance implements ComplianceViolation.Mode
* <li>{@link Violation#SPECIAL_CHARS_IN_QUOTES}</li>
* </ul>
*/
public static final CookieCompliance RFC6265_LEGACY = new CookieCompliance("RFC6265_LEGACY", of(
Violation.BAD_QUOTES, Violation.ESCAPE_IN_QUOTES, Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE, Violation.SPECIAL_CHARS_IN_QUOTES)
public static final CookieCompliance RFC6265_LEGACY = new CookieCompliance("RFC6265_LEGACY", EnumSet.of(
Violation.ATTRIBUTES, Violation.BAD_QUOTES, Violation.ESCAPE_IN_QUOTES, Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE, Violation.SPECIAL_CHARS_IN_QUOTES)
);
/**
@ -214,40 +214,45 @@ public class CookieCompliance implements ComplianceViolation.Mode
*/
public static CookieCompliance from(String spec)
{
Set<Violation> violations;
String[] elements = spec.split("\\s*,\\s*");
switch (elements[0])
CookieCompliance compliance = valueOf(spec);
if (compliance == null)
{
case "0":
violations = noneOf(Violation.class);
break;
case "*":
violations = allOf(Violation.class);
break;
default:
String[] elements = spec.split("\\s*,\\s*");
Set<Violation> violations;
switch (elements[0])
{
CookieCompliance mode = valueOf(elements[0]);
violations = (mode == null) ? noneOf(Violation.class) : copyOf(mode.getAllowed());
break;
case "0" :
violations = noneOf(Violation.class);
break;
case "*" :
violations = allOf(Violation.class);
break;
default :
{
CookieCompliance mode = valueOf(elements[0]);
if (mode == null)
throw new IllegalArgumentException("Unknown base mode: " + elements[0]);
violations = (mode.getAllowed().isEmpty()) ? noneOf(Violation.class) : copyOf(mode.getAllowed());
}
}
}
for (int i = 1; i < elements.length; i++)
{
String element = elements[i];
boolean exclude = element.startsWith("-");
if (exclude)
element = element.substring(1);
Violation section = Violation.valueOf(element);
if (exclude)
violations.remove(section);
else
violations.add(section);
}
for (int i = 1; i < elements.length; i++)
{
String element = elements[i];
boolean exclude = element.startsWith("-");
if (exclude)
element = element.substring(1);
Violation section = Violation.valueOf(element);
if (exclude)
violations.remove(section);
else
violations.add(section);
}
CookieCompliance compliance = new CookieCompliance("CUSTOM" + __custom.getAndIncrement(), violations);
compliance = new CookieCompliance("CUSTOM" + __custom.getAndIncrement(), violations);
}
if (LOG.isDebugEnabled())
LOG.debug("CookieCompliance from {}->{}", spec, compliance);
return compliance;
@ -290,4 +295,10 @@ public class CookieCompliance implements ComplianceViolation.Mode
{
return this == mode || getAllowed().containsAll(mode.getAllowed());
}
@Override
public String toString()
{
return String.format("%s@%x%s", _name, hashCode(), _violations);
}
}

View File

@ -41,9 +41,9 @@ public interface CookieParser
return new RFC6265CookieParser(handler, compliance, complianceListener);
}
void parseField(String field);
void parseField(String field) throws InvalidCookieException;
default void parseFields(List<String> rawFields)
default void parseFields(List<String> rawFields) throws InvalidCookieException
{
// For each cookie field
for (String field : rawFields)
@ -57,4 +57,30 @@ public interface CookieParser
{
void addCookie(String name, String value, int version, String domain, String path, String comment);
}
/**
* <p>The exception thrown when a cookie cannot be parsed and {@link CookieCompliance.Violation#INVALID_COOKIES} is not allowed.</p>
*/
class InvalidCookieException extends IllegalArgumentException
{
public InvalidCookieException()
{
super();
}
public InvalidCookieException(String s)
{
super(s);
}
public InvalidCookieException(String message, Throwable cause)
{
super(message, cause);
}
public InvalidCookieException(Throwable cause)
{
super(cause);
}
}
}

View File

@ -85,7 +85,7 @@ public class RFC6265CookieParser implements CookieParser
if (token == null)
{
if (!_complianceMode.allows(INVALID_COOKIES))
throw new IllegalArgumentException("Invalid Cookie character");
throw new InvalidCookieException("Invalid Cookie character");
state = State.INVALID_COOKIE;
continue;
}
@ -100,7 +100,7 @@ public class RFC6265CookieParser implements CookieParser
if (token.isRfc2616Token())
{
if (!StringUtil.isBlank(cookieName) && c != '$')
if (!StringUtil.isBlank(cookieName) && !(c == '$' && (_complianceMode.allows(ATTRIBUTES) || _complianceMode.allows(ATTRIBUTE_VALUES))))
{
_handler.addCookie(cookieName, cookieValue, cookieVersion, cookieDomain, cookiePath, cookieComment);
cookieName = null;
@ -120,7 +120,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie name");
throw new InvalidCookieException("Bad Cookie name");
}
break;
@ -158,7 +158,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie name");
throw new InvalidCookieException("Bad Cookie name");
}
break;
@ -181,7 +181,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie");
throw new InvalidCookieException("Bad Cookie");
}
break;
@ -215,7 +215,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie value");
throw new InvalidCookieException("Bad Cookie value");
}
break;
@ -237,7 +237,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie value");
throw new InvalidCookieException("Bad Cookie value");
}
break;
@ -277,7 +277,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie quoted value");
throw new InvalidCookieException("Bad Cookie quoted value");
}
break;
@ -299,7 +299,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalArgumentException("Bad Cookie quoted value");
throw new InvalidCookieException("Bad Cookie quoted value");
}
break;
@ -323,7 +323,7 @@ public class RFC6265CookieParser implements CookieParser
}
else
{
throw new IllegalStateException("Comma cookie separator");
throw new InvalidCookieException("Comma cookie separator");
}
}
else if ((c == ' ' || c == '\t') && _complianceMode.allows(OPTIONAL_WHITE_SPACE))
@ -332,7 +332,6 @@ public class RFC6265CookieParser implements CookieParser
continue;
}
boolean knownAttribute = true;
if (StringUtil.isBlank(attributeName))
{
cookieValue = value;
@ -358,7 +357,10 @@ public class RFC6265CookieParser implements CookieParser
cookieVersion = Integer.parseInt(value);
break;
default:
knownAttribute = false;
if (!_complianceMode.allows(INVALID_COOKIES))
throw new IllegalArgumentException("Invalid Cookie attribute");
reportComplianceViolation(INVALID_COOKIES, field);
state = State.INVALID_COOKIE;
break;
}
}
@ -366,31 +368,17 @@ public class RFC6265CookieParser implements CookieParser
{
reportComplianceViolation(ATTRIBUTES, field);
}
else if (_complianceMode.allows(INVALID_COOKIES))
{
reportComplianceViolation(INVALID_COOKIES, field);
state = State.INVALID_COOKIE;
continue;
}
else
{
throw new IllegalArgumentException("Invalid Cookie with attributes");
cookieName = attributeName;
cookieValue = value;
}
attributeName = null;
}
value = null;
if (!knownAttribute)
{
if (!_complianceMode.allows(INVALID_COOKIES))
throw new IllegalArgumentException("Invalid Cookie attribute");
reportComplianceViolation(INVALID_COOKIES, field);
state = State.INVALID_COOKIE;
continue;
}
if (state == State.END)
throw new IllegalStateException("Invalid cookie");
throw new InvalidCookieException("Invalid cookie");
break;
case INVALID_COOKIE:

View File

@ -85,58 +85,12 @@ public class RFC6265CookieParserLenientTest
Arguments.of("!f!o!o!=wat", "!f!o!o!", "wat"),
Arguments.of("__MyHost=Foo", "__MyHost", "Foo"),
Arguments.of("some-thing-else=to-parse", "some-thing-else", "to-parse"),
// RFC2109 - names with attr/token syntax starting with '$' (and not a cookie reserved word)
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00#section-5.2
// Cannot pass names through as jakarta.servlet.http.Cookie class does not allow them
Arguments.of("$foo=bar", null, null),
Arguments.of("$foo=bar", "$foo", "bar"),
// Tests that conform to RFC6265
Arguments.of("abc=foobar!", "abc", "foobar!"),
Arguments.of("abc=\"foobar!\"", "abc", "foobar!")
/* TODO need to discuss if we should support these cases
,
// UTF-8 raw values (not encoded) - VIOLATION of RFC6265
Arguments.of("2sides=\u262F", null, null), // 2 byte (YIN YANG) - rejected due to not being DQUOTED
Arguments.of("currency=\"\u20AC\"", "currency", "\u20AC"), // 3 byte (EURO SIGN)
Arguments.of("gothic=\"\uD800\uDF48\"", "gothic", "\uD800\uDF48"), // 4 byte (GOTHIC LETTER HWAIR)
// Spaces
Arguments.of("foo=bar baz", "foo", "bar baz"),
Arguments.of("foo=\"bar baz\"", "foo", "bar baz"),
Arguments.of("z=a b c d e f g", "z", "a b c d e f g"),
// Bad tspecials usage - VIOLATION of RFC6265
Arguments.of("foo=bar;baz", "foo", "bar"),
Arguments.of("foo=\"bar;baz\"", "foo", "bar;baz"),
Arguments.of("z=a;b,c:d;e/f[g]", "z", "a"),
Arguments.of("z=\"a;b,c:d;e/f[g]\"", "z", "a;b,c:d;e/f[g]"),
Arguments.of("name=quoted=\"\\\"badly\\\"\"", "name", "quoted=\"\\\"badly\\\"\""), // someone attempting to escape a DQUOTE from within a DQUOTED pair)
// Quoted with other Cookie keywords
Arguments.of("x=\"$Version=0\"", "x", "$Version=0"),
Arguments.of("x=\"$Path=/\"", "x", "$Path=/"),
Arguments.of("x=\"$Path=/ $Domain=.foo.com\"", "x", "$Path=/ $Domain=.foo.com"),
Arguments.of("x=\" $Path=/ $Domain=.foo.com \"", "x", " $Path=/ $Domain=.foo.com "),
Arguments.of("a=\"b; $Path=/a; c=d; $PATH=/c; e=f\"; $Path=/e/", "a", "b; $Path=/a; c=d; $PATH=/c; e=f"), // VIOLATES RFC6265
// Lots of equals signs
Arguments.of("query=b=c&d=e", "query", "b=c&d=e"),
// Escaping
Arguments.of("query=%7B%22sessionCount%22%3A5%2C%22sessionTime%22%3A14151%7D", "query", "%7B%22sessionCount%22%3A5%2C%22sessionTime%22%3A14151%7D"),
// Google cookies (seen in wild, has `tspecials` of ':' in value)
Arguments.of("GAPS=1:A1aaaAaAA1aaAAAaa1a11a:aAaaAa-aaA1-", "GAPS", "1:A1aaaAaAA1aaAAAaa1a11a:aAaaAa-aaA1-"),
// Strong abuse of cookie spec (lots of tspecials) - VIOLATION of RFC6265
Arguments.of("$Version=0; rToken=F_TOKEN''!--\"</a>=&{()}", "rToken", "F_TOKEN''!--\"</a>=&{()}"),
// Commas that were not commas
Arguments.of("name=foo,bar", "name", "foo,bar"),
Arguments.of("name=foo , bar", "name", "foo , bar"),
Arguments.of("name=foo , bar, bob", "name", "foo , bar, bob")
*/
);
}

View File

@ -23,7 +23,6 @@ import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class RFC6265CookieParserTest
{
@ -36,7 +35,7 @@ public class RFC6265CookieParserTest
String rawCookie = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"";
// Test with RFC 2965.
TestCookieParser parser = new TestCookieParser(CookieCompliance.RFC2965_LEGACY);
TestCookieParser parser = new TestCookieParser(CookieCompliance.RFC2965);
List<Cookie> cookies = parser.parseFields(rawCookie);
assertThat("Cookies.length", cookies.size(), is(1));
@ -44,18 +43,45 @@ public class RFC6265CookieParserTest
// There are 2 attributes, so 2 violations.
assertThat(parser.violations.size(), is(2));
// Same test with RFC 6265.
// Same test with RFC6265.
parser = new TestCookieParser(CookieCompliance.RFC6265);
cookies = parser.parseFields(rawCookie);
assertThat("Cookies.length", cookies.size(), is(3));
assertCookie("Cookies[0]", cookies.get(0), "$Version", "1", 0, null);
assertCookie("Cookies[1]", cookies.get(1), "Customer", "WILE_E_COYOTE", 0, null);
assertCookie("Cookies[2]", cookies.get(2), "$Path", "/acme", 0, null);
// There attributes are seen as just normal cookies, so no violations
assertThat(parser.violations.size(), is(0));
// Same again, but allow attributes which are ignored
parser = new TestCookieParser(CookieCompliance.from("RFC6265,ATTRIBUTES"));
cookies = parser.parseFields(rawCookie);
assertThat("Cookies.length", cookies.size(), is(1));
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 0, null);
// There are 2 attributes, so 2 violations.
// There attributes are seen as just normal cookies, so no violations
assertThat(parser.violations.size(), is(2));
// Same again, but allow attributes which are not ignored
parser = new TestCookieParser(CookieCompliance.from("RFC6265,ATTRIBUTE_VALUES"));
cookies = parser.parseFields(rawCookie);
assertThat("Cookies.length", cookies.size(), is(1));
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 1, "/acme");
// There attributes are seen as just normal cookies, so no violations
assertThat(parser.violations.size(), is(2));
// Same test with RFC 6265 strict should throw.
TestCookieParser strictParser = new TestCookieParser(CookieCompliance.RFC6265_STRICT);
assertThrows(IllegalArgumentException.class, () -> strictParser.parseFields(rawCookie));
parser = new TestCookieParser(CookieCompliance.RFC6265_STRICT);
cookies = parser.parseFields(rawCookie);
assertThat("Cookies.length", cookies.size(), is(3));
assertCookie("Cookies[0]", cookies.get(0), "$Version", "1", 0, null);
assertCookie("Cookies[1]", cookies.get(1), "Customer", "WILE_E_COYOTE", 0, null);
assertCookie("Cookies[2]", cookies.get(2), "$Path", "/acme", 0, null);
// There attributes are seen as just normal cookies, so no violations
assertThat(parser.violations.size(), is(0));
}
/**
@ -68,12 +94,30 @@ public class RFC6265CookieParserTest
"Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " +
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 1, "/acme");
cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
assertThat("Cookies.length", cookies.length, is(5));
assertCookie("Cookies[0]", cookies[0], "$Version", "1", 0, null);
assertCookie("Cookies[1]", cookies[1], "Customer", "WILE_E_COYOTE", 0, null);
assertCookie("Cookies[2]", cookies[2], "$Path", "/acme", 0, null);
assertCookie("Cookies[3]", cookies[3], "Part_Number", "Rocket_Launcher_0001", 0, null);
assertCookie("Cookies[4]", cookies[4], "$Path", "/acme", 0, null);
cookies = parseCookieHeaders(CookieCompliance.from("RFC6265,ATTRIBUTES"), rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 0, null);
assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 0, null);
cookies = parseCookieHeaders(CookieCompliance.from("RFC6265,ATTRIBUTE_VALUES"), rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 1, "/acme");
/* TODO Is this a better interpretation of dollar attributes
cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
assertThat("Cookies.length", cookies.length, is(4));
@ -95,7 +139,7 @@ public class RFC6265CookieParserTest
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; " +
"Shipping=\"FedEx\"; $Path=\"/acme\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(3));
assertCookie("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
@ -113,7 +157,7 @@ public class RFC6265CookieParserTest
"Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " +
"Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "Part_Number", "Riding_Rocket_0023", 1, "/acme/ammo");
@ -130,7 +174,7 @@ public class RFC6265CookieParserTest
"session_id=\"1234\"; " +
"session_id=\"1111\"; $Domain=\".cracker.edu\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
@ -146,15 +190,25 @@ public class RFC6265CookieParserTest
String rawCookie = "$Version=\"1\"; session_id=\"1234\", " +
"$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(2));
assertCookie("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 1, null);
cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
assertThat("Cookies.length", cookies.length, is(1));
assertCookie("Cookies[1]", cookies[0], "session_id", "1111", 0, null);
assertThat("Cookies.length", cookies.length, is(3));
assertCookie("Cookies[0]", cookies[0], "$Version", "1", 0, null);
assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 0, null);
assertCookie("Cookies[2]", cookies[2], "$Domain", ".cracker.edu", 0, null);
cookies = parseCookieHeaders(CookieCompliance.from("RFC6265,COMMA_SEPARATOR"), rawCookie);
assertThat("Cookies.length", cookies.length, is(5));
assertCookie("Cookies[0]", cookies[0], "$Version", "1", 0, null);
assertCookie("Cookies[1]", cookies[1], "session_id", "1234", 0, null);
assertCookie("Cookies[3]", cookies[2], "$Version", "1", 0, null);
assertCookie("Cookies[3]", cookies[3], "session_id", "1111", 0, null);
assertCookie("Cookies[4]", cookies[4], "$Domain", ".cracker.edu", 0, null);
}
/**
@ -210,7 +264,8 @@ public class RFC6265CookieParserTest
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
assertThat("Cookies.length", cookies.length, is(0));
assertThat("Cookies.length", cookies.length, is(1));
assertCookie("Cookies[0]", cookies[0], "$key", "value", 0, null);
}
@Test
@ -245,7 +300,7 @@ public class RFC6265CookieParserTest
public void testRFC2965QuotedEscape()
{
String rawCookie = "A=\"double\\\"quote\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(1));
assertCookie("Cookies[0]", cookies[0], "A", "double\"quote", 0, null);
@ -255,16 +310,12 @@ public class RFC6265CookieParserTest
public void testRFC2965QuotedSpecial()
{
String rawCookie = "A=\", ;\"";
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965_LEGACY, rawCookie);
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie);
assertThat("Cookies.length", cookies.length, is(1));
assertCookie("Cookies[0]", cookies[0], "A", ", ;", 0, null);
}
// TODO:
// $X; N=V
// $X=Y; N=V
public static List<Param> parameters()
{
return Arrays.asList(
@ -290,8 +341,8 @@ public class RFC6265CookieParserTest
new Param("A=\"1\u0007\"; B=2; C=3", "B=2", "C=3"),
new Param(""),
new Param("@={}"),
new Param("$X=Y; N=V", "N=V"),
new Param("N=V; $X=Y", "N=V")
new Param("$X=Y; N=V", "$X=Y", "N=V"),
new Param("N=V; $X=Y", "N=V", "$X=Y")
);
}

View File

@ -73,8 +73,9 @@
<Set name="persistentConnectionsEnabled" property="jetty.httpConfig.persistentConnectionsEnabled"/>
<Set name="httpCompliance"><Call class="org.eclipse.jetty.http.HttpCompliance" name="from"><Arg><Property name="jetty.httpConfig.compliance" deprecated="jetty.http.compliance" default="RFC7230"/></Arg></Call></Set>
<Set name="uriCompliance"><Call class="org.eclipse.jetty.http.UriCompliance" name="from"><Arg><Property name="jetty.httpConfig.uriCompliance" default="SAFE"/></Arg></Call></Set>
<Set name="requestCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.requestCookieCompliance" default="RFC6265"/></Arg></Call></Set>
<Set name="responseCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.responseCookieCompliance" default="RFC6265"/></Arg></Call></Set>
<Set name="requestCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="from"><Arg><Property name="jetty.httpConfig.requestCookieCompliance" default="RFC6265"/></Arg></Call></Set>
<Set name="responseCookieCompliance"><Call class="org.eclipse.jetty.http.CookieCompliance" name="from"><Arg><Property name="jetty.httpConfig.responseCookieCompliance" default="RFC6265"/></Arg></Call></Set>
<Set name="multiPartFormDataCompliance"><Call class="org.eclipse.jetty.server.MultiPartFormDataCompliance" name="valueOf"><Arg><Property name="jetty.httpConfig.multiPartFormDataCompliance" default="RFC7578"/></Arg></Call></Set>
<Set name="relativeRedirectAllowed"><Property name="jetty.httpConfig.relativeRedirectAllowed" default="false"/></Set>
<Set name="useInputDirectByteBuffers" property="jetty.httpConfig.useInputDirectByteBuffers"/>
<Set name="useOutputDirectByteBuffers" property="jetty.httpConfig.useOutputDirectByteBuffers"/>

View File

@ -75,7 +75,7 @@ etc/jetty.xml
## URI Compliance: DEFAULT, LEGACY, RFC3986, RFC3986_UNAMBIGUOUS, UNSAFE
# jetty.httpConfig.uriCompliance=DEFAULT
## Cookie compliance mode for parsing request Cookie headers: RFC2965, RFC6265
## Cookie compliance mode for parsing request Cookie headers: RFC6265_STRICT, RFC6265, RFC6265_LEGACY, RFC2965, RFC2965_LEGACY
# jetty.httpConfig.requestCookieCompliance=RFC6265
## Cookie compliance mode for generating response Set-Cookie: RFC2965, RFC6265

View File

@ -135,11 +135,9 @@ public final class HttpCookieUtils
public static String getSetCookie(HttpCookie httpCookie, CookieCompliance compliance)
{
if (compliance == CookieCompliance.RFC6265)
if (compliance == null || CookieCompliance.RFC6265_LEGACY.compliesWith(compliance))
return getRFC6265SetCookie(httpCookie);
if (compliance == CookieCompliance.RFC2965)
return getRFC2965SetCookie(httpCookie);
throw new IllegalStateException();
return getRFC2965SetCookie(httpCookie);
}
public static String getRFC2965SetCookie(HttpCookie httpCookie)

View File

@ -0,0 +1,211 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.IOException;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.CookieParser;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.UrlEncoded;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.eclipse.jetty.http.CookieCompliance.RFC2965;
import static org.eclipse.jetty.http.CookieCompliance.RFC2965_LEGACY;
import static org.eclipse.jetty.http.CookieCompliance.RFC6265;
import static org.eclipse.jetty.http.CookieCompliance.RFC6265_LEGACY;
import static org.eclipse.jetty.http.CookieCompliance.RFC6265_STRICT;
import static org.eclipse.jetty.http.CookieCompliance.from;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
public class ServerHttpCookieTest
{
private Server _server;
private LocalConnector _connector;
private HttpConfiguration _httpConfiguration;
@BeforeEach
public void beforeEach() throws Exception
{
_server = new Server();
_connector = new LocalConnector(_server);
_server.addConnector(_connector);
_server.setHandler(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
HttpConfiguration config = baseRequest.getHttpChannel().getHttpConfiguration();
baseRequest.setHandled(true);
String setCookie = baseRequest.getParameter("SetCookie");
if (setCookie != null)
{
CookieParser parser = CookieParser.newParser((name, value, version, domain, path, comment) ->
{
Cookie cookie = new Cookie(name, value);
if (version > 0)
cookie.setVersion(version);
if (domain != null)
cookie.setDomain(domain);
if (path != null)
cookie.setPath(path);
if (comment != null)
cookie.setComment(comment);
response.addCookie(cookie);
}, RFC2965, null);
parser.parseField(setCookie);
}
Cookie[] cookies = request.getCookies();
StringBuilder out = new StringBuilder();
if (cookies != null)
{
for (Cookie cookie : cookies)
{
out
.append("[")
.append(cookie.getName())
.append('=')
.append(cookie.getValue());
if (cookie.getVersion() > 0)
out.append(";Version=").append(cookie.getVersion());
if (cookie.getPath() != null)
out.append(";Path=").append(cookie.getPath());
if (cookie.getDomain() != null)
out.append(";Domain=").append(cookie.getDomain());
out.append("]\n");
}
}
response.getWriter().println(out);
}
});
_httpConfiguration = _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration();
_server.start();
}
public static Stream<Arguments> requestCases()
{
return Stream.of(
Arguments.of(RFC6265_STRICT, "Cookie: name=value", 200, "Version=", List.of("[name=value]").toArray(new String[0])),
// Attribute tests
// TODO $name attributes are ignored because servlet 5.0 Cookie class rejects them. They are not ignored in servlet 6.0
Arguments.of(RFC6265_STRICT, "Cookie: $version=1; name=value", 200, "Version=", List.of("[name=value]").toArray(new String[0])),
Arguments.of(RFC6265, "Cookie: $version=1; name=value", 200, "Version=", List.of("[name=value]").toArray(new String[0])),
Arguments.of(RFC6265, "Cookie: name=value;$path=/path", 200, "Path=", List.of("[name=value]").toArray(new String[0])),
Arguments.of(from("RFC6265,ATTRIBUTES"), "Cookie: name=value;$path=/path", 200, "/path", List.of("name=value").toArray(new String[0])),
Arguments.of(from("RFC6265_STRICT,ATTRIBUTE_VALUES"), "Cookie: name=value;$path=/path", 200, null, List.of("name=value;Path=/path").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: name=value;$path=/path", 200, null, List.of("name=value;Path=/path").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: $Version=1;name=value;$path=/path", 200, null, List.of("name=value;Version=1;Path=/path").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: $Version=1;name=value;$path=/path;$Domain=host", 200, null, List.of("name=value;Version=1;Path=/path;Domain=host").toArray(new String[0])),
// multiple cookie tests
Arguments.of(RFC6265_STRICT, "Cookie: name=value; other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=value, other=extra", 400, null, List.of("BadMessageException", "Comma cookie separator").toArray(new String[0])),
Arguments.of(from("RFC6265_STRICT,COMMA_SEPARATOR,"), "Cookie: name=value, other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
Arguments.of(RFC6265, "Cookie: name=value, other=extra", 200, "name=value", null),
Arguments.of(RFC6265_LEGACY, "Cookie: name=value, other=extra", 200, null, List.of("[name=value, other=extra]").toArray(new String[0])),
Arguments.of(RFC6265_LEGACY, "Cookie: name=value; other=extra", 200, null, List.of("[name=value]", "other=extra]").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: name=value, other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
Arguments.of(RFC2965_LEGACY, "Cookie: name=value, other=extra", 200, "Version=", List.of("[name=value]", "[other=extra]").toArray(new String[0])),
// white space
Arguments.of(RFC6265_STRICT, "Cookie: name =value", 400, null, List.of("BadMessageException", "Bad Cookie name").toArray(new String[0])),
Arguments.of(from("RFC6265,OPTIONAL_WHITE_SPACE"), "Cookie: name =value", 200, null, List.of("name=value").toArray(new String[0])),
// bad characters
Arguments.of(RFC6265_STRICT, "Cookie: name=va\\ue", 400, null, List.of("BadMessageException", "Bad Cookie value").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=\"value\"", 200, "Version=", List.of("[name=value]").toArray(new String[0])),
Arguments.of(RFC6265_STRICT, "Cookie: name=\"value;other=extra\"", 400, null, List.of("BadMessageException", "Bad Cookie quoted value").toArray(new String[0])),
Arguments.of(RFC6265, "Cookie: name=\"value;other=extra\"", 200, "name=value", null),
Arguments.of(RFC6265_LEGACY, "Cookie: name=\"value;other=extra\"", 200, null, List.of("[name=value;other=extra]").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: name=\"value;other=extra\"", 200, null, List.of("[name=value;other=extra]").toArray(new String[0])),
Arguments.of(RFC2965, "Cookie: name=\"value;other=extra", 200, "name=value", null),
Arguments.of(RFC2965_LEGACY, "Cookie: name=\"value;other=extra\"", 200, null, List.of("[name=value;other=extra]").toArray(new String[0])),
Arguments.of(RFC2965_LEGACY, "Cookie: name=\"value;other=extra", 200, null, List.of("[name=\"value;other=extra]").toArray(new String[0])),
// TCK check
Arguments.of(RFC6265, "Cookie: $Version=1; name1=value1; $Domain=hostname; $Path=/servlet_jsh_cookie_web", 200, null, List.of("name1=value1").toArray(new String[0]))
);
}
@ParameterizedTest
@MethodSource("requestCases")
public void testRequestCookies(CookieCompliance compliance, String cookie, int status, String unexpected, String... expectations) throws Exception
{
_httpConfiguration.setRequestCookieCompliance(compliance);
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse("GET / HTTP/1.0\r\n" + cookie + "\r\n\r\n"));
assertThat(response.getStatus(), equalTo(status));
if (unexpected != null)
assertThat(response.getContent(), not(containsString(unexpected)));
if (expectations != null)
for (String expected : expectations)
assertThat(response.getContent(), containsString(expected));
}
public static Stream<Arguments> responseCases()
{
return Stream.of(
Arguments.of(RFC6265_STRICT, "name=value", "name=value"),
Arguments.of(RFC6265, "name=value", "name=value"),
Arguments.of(RFC6265_LEGACY, "name=value", "name=value"),
Arguments.of(RFC2965, "name=value", "name=value"),
Arguments.of(RFC2965_LEGACY, "name=value", "name=value"),
Arguments.of(RFC6265_STRICT, "name=value;$version=1;$path=/path;$domain=domain", "name=value; Path=/path; Domain=domain"),
Arguments.of(RFC6265, "name=value;$version=1;$path=/path;$domain=domain", "name=value; Path=/path; Domain=domain"),
Arguments.of(RFC6265_LEGACY, "name=value;$version=1;$path=/path;$domain=domain", "name=value; Path=/path; Domain=domain"),
Arguments.of(RFC2965, "name=value;$version=1;$path=/path;$domain=domain", "name=value;Version=1;Path=/path;Domain=domain"),
Arguments.of(RFC2965_LEGACY, "name=value;$version=1;$path=/path;$domain=domain", "name=value;Version=1;Path=/path;Domain=domain"),
Arguments.of(RFC6265, "name=value", "name=value")
);
}
@ParameterizedTest
@MethodSource("responseCases")
public void testResponseCookies(CookieCompliance compliance, String cookie, String expected) throws Exception
{
_httpConfiguration.setResponseCookieCompliance(compliance);
HttpTester.Response response = HttpTester.parseResponse(_connector.getResponse("GET /?SetCookie=" + UrlEncoded.encodeString(cookie) + " HTTP/1.0\r\n\r\n"));
assertThat(response.getStatus(), equalTo(200));
String setCookie = response.get(HttpHeader.SET_COOKIE);
if (expected == null)
assertThat(setCookie, nullValue());
else
assertThat(setCookie, equalTo(expected));
}
}

View File

@ -17,9 +17,11 @@ import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.http.Cookie;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.CookieCompliance;
import org.eclipse.jetty.http.CookieParser;
import org.eclipse.jetty.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -95,7 +97,14 @@ public class Cookies implements CookieParser.Handler
if (_parsed)
return _cookies;
_parser.parseFields(_rawFields);
try
{
_parser.parseFields(_rawFields);
}
catch (CookieParser.InvalidCookieException invalidCookieException)
{
throw new BadMessageException(HttpStatus.BAD_REQUEST_400, invalidCookieException.getMessage(), invalidCookieException);
}
_cookies = _cookieList.toArray(new Cookie[0]);
_cookieList.clear();
_parsed = true;