Servlet 61 cookie fixes (#11936)
* Fix #11934 Servlet 6.1 Cookies * Added compliance mode MAINTAIN_QUOTES to keep the quotes as part of the cookie value. Added mode RFC6265_QUOTED that includes this violation * Never send a zero valued max-age parameter * Partitioned is set if any attribute that is not "false" is set. * Avoid equal sign for empty valued attributes * Pushed responses delete max-age==0 cookies
This commit is contained in:
parent
beff0fa990
commit
1e241d8ed5
|
@ -99,7 +99,12 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
/**
|
||||
* Allow spaces within values without quotes.
|
||||
*/
|
||||
SPACE_IN_VALUES("https://www.rfc-editor.org/rfc/rfc6265#section-5.2", "Space in value");
|
||||
SPACE_IN_VALUES("https://www.rfc-editor.org/rfc/rfc6265#section-5.2", "Space in value"),
|
||||
|
||||
/**
|
||||
* Allows quotes to be stripped from values.
|
||||
*/
|
||||
STRIPPED_QUOTES("https://www.rfc-editor.org/rfc/rfc6265#section-4.1.1", "Strip quotes from the cookie value");
|
||||
|
||||
private final String url;
|
||||
private final String description;
|
||||
|
@ -136,9 +141,23 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
* <li>{@link Violation#INVALID_COOKIES}</li>
|
||||
* <li>{@link Violation#OPTIONAL_WHITE_SPACE}</li>
|
||||
* <li>{@link Violation#SPACE_IN_VALUES}</li>
|
||||
* <li>{@link Violation#STRIPPED_QUOTES}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final CookieCompliance RFC6265 = new CookieCompliance("RFC6265", of(
|
||||
Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE, Violation.SPACE_IN_VALUES, Violation.STRIPPED_QUOTES)
|
||||
);
|
||||
|
||||
/**
|
||||
* <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#INVALID_COOKIES}</li>
|
||||
* <li>{@link Violation#OPTIONAL_WHITE_SPACE}</li>
|
||||
* <li>{@link Violation#SPACE_IN_VALUES}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public static final CookieCompliance RFC6265_QUOTED = new CookieCompliance("RFC6265_QUOTED", of(
|
||||
Violation.INVALID_COOKIES, Violation.OPTIONAL_WHITE_SPACE, Violation.SPACE_IN_VALUES)
|
||||
);
|
||||
|
||||
|
@ -182,7 +201,7 @@ public class CookieCompliance implements ComplianceViolation.Mode
|
|||
Violation.BAD_QUOTES, Violation.COMMA_NOT_VALID_OCTET, Violation.RESERVED_NAMES_NOT_DOLLAR_PREFIXED)
|
||||
));
|
||||
|
||||
private static final List<CookieCompliance> KNOWN_MODES = Arrays.asList(RFC6265, RFC6265_STRICT, RFC6265_LEGACY, RFC2965, RFC2965_LEGACY);
|
||||
private static final List<CookieCompliance> KNOWN_MODES = Arrays.asList(RFC6265, RFC6265_QUOTED, RFC6265_STRICT, RFC6265_LEGACY, RFC2965, RFC2965_LEGACY);
|
||||
private static final AtomicInteger __custom = new AtomicInteger();
|
||||
|
||||
public static CookieCompliance valueOf(String name)
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.Objects;
|
|||
import java.util.TreeMap;
|
||||
|
||||
import org.eclipse.jetty.util.Index;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* <p>Implementation of RFC6265 HTTP Cookies (with fallback support for RFC2965).</p>
|
||||
|
@ -127,7 +128,7 @@ public interface HttpCookie
|
|||
*/
|
||||
default boolean isSecure()
|
||||
{
|
||||
return Boolean.parseBoolean(getAttributes().get(SECURE_ATTRIBUTE));
|
||||
return isSetToNotFalse(SECURE_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,7 +146,7 @@ public interface HttpCookie
|
|||
*/
|
||||
default boolean isHttpOnly()
|
||||
{
|
||||
return Boolean.parseBoolean(getAttributes().get(HTTP_ONLY_ATTRIBUTE));
|
||||
return isSetToNotFalse(HTTP_ONLY_ATTRIBUTE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -154,7 +155,13 @@ public interface HttpCookie
|
|||
*/
|
||||
default boolean isPartitioned()
|
||||
{
|
||||
return Boolean.parseBoolean(getAttributes().get(PARTITIONED_ATTRIBUTE));
|
||||
return isSetToNotFalse(PARTITIONED_ATTRIBUTE);
|
||||
}
|
||||
|
||||
private boolean isSetToNotFalse(String attribute)
|
||||
{
|
||||
String value = getAttributes().get(attribute);
|
||||
return value != null && !StringUtil.asciiEqualsIgnoreCase("false", value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,6 +26,7 @@ import static org.eclipse.jetty.http.CookieCompliance.Violation.INVALID_COOKIES;
|
|||
import static org.eclipse.jetty.http.CookieCompliance.Violation.OPTIONAL_WHITE_SPACE;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.SPACE_IN_VALUES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.SPECIAL_CHARS_IN_QUOTES;
|
||||
import static org.eclipse.jetty.http.CookieCompliance.Violation.STRIPPED_QUOTES;
|
||||
|
||||
/**
|
||||
* Cookie parser
|
||||
|
@ -194,6 +195,8 @@ public class RFC6265CookieParser implements CookieParser
|
|||
string.setLength(0);
|
||||
if (c == '"')
|
||||
{
|
||||
if (!_complianceMode.allows(STRIPPED_QUOTES))
|
||||
string.append(c);
|
||||
state = State.IN_QUOTED_VALUE;
|
||||
}
|
||||
else if (c == ';')
|
||||
|
@ -276,7 +279,16 @@ public class RFC6265CookieParser implements CookieParser
|
|||
case IN_QUOTED_VALUE:
|
||||
if (c == '"')
|
||||
{
|
||||
value = string.toString();
|
||||
if (_complianceMode.allows(STRIPPED_QUOTES))
|
||||
{
|
||||
value = string.toString();
|
||||
reportComplianceViolation(STRIPPED_QUOTES, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
string.append(c);
|
||||
value = string.toString();
|
||||
}
|
||||
state = State.AFTER_QUOTED_VALUE;
|
||||
}
|
||||
else if (c == '\\' && _complianceMode.allows(ESCAPE_IN_QUOTES))
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.anyOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
public class RFC6265CookieParserTest
|
||||
|
@ -40,8 +41,8 @@ public class RFC6265CookieParserTest
|
|||
|
||||
assertThat("Cookies.length", cookies.size(), is(1));
|
||||
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
// There are 2 attributes, so 2 violations.
|
||||
assertThat(parser.violations.size(), is(2));
|
||||
// There are 2 attributes, so 2 violations, plus 3 violations for stripping quotes.
|
||||
assertThat(parser.violations.size(), is(2 + 3));
|
||||
|
||||
// Same test with RFC6265.
|
||||
parser = new TestCookieParser(CookieCompliance.RFC6265);
|
||||
|
@ -51,8 +52,8 @@ public class RFC6265CookieParserTest
|
|||
assertCookie("Cookies[1]", cookies.get(1), "Customer", "WILE_E_COYOTE", 0, null);
|
||||
assertCookie("Cookies[2]", cookies.get(2), "$Path", "/acme", 0, null);
|
||||
|
||||
// Normal cookies with attributes, so no violations
|
||||
assertThat(parser.violations.size(), is(0));
|
||||
// Normal cookies with attributes, so just 3 violations for stripping quotes
|
||||
assertThat(parser.violations.size(), is(3));
|
||||
|
||||
// Same again, but allow attributes which are ignored
|
||||
parser = new TestCookieParser(CookieCompliance.from("RFC6265,ATTRIBUTES"));
|
||||
|
@ -60,8 +61,8 @@ public class RFC6265CookieParserTest
|
|||
assertThat("Cookies.length", cookies.size(), is(1));
|
||||
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 0, null);
|
||||
|
||||
// There are 2 attributes that are seen as violations
|
||||
assertThat(parser.violations.size(), is(2));
|
||||
// There are 2 attributes, so 2 violations, plus 3 violations for stripping quotes.
|
||||
assertThat(parser.violations.size(), is(2 + 3));
|
||||
|
||||
// Same again, but allow attributes which are not ignored
|
||||
parser = new TestCookieParser(CookieCompliance.from("RFC6265,ATTRIBUTE_VALUES"));
|
||||
|
@ -69,18 +70,18 @@ public class RFC6265CookieParserTest
|
|||
assertThat("Cookies.length", cookies.size(), is(1));
|
||||
assertCookie("Cookies[0]", cookies.get(0), "Customer", "WILE_E_COYOTE", 1, "/acme");
|
||||
|
||||
// There are 2 attributes that are seen as violations
|
||||
assertThat(parser.violations.size(), is(2));
|
||||
// There are 2 attributes, so 2 violations, plus 3 violations for stripping quotes.
|
||||
assertThat(parser.violations.size(), is(2 + 3));
|
||||
|
||||
// Same test, but with RFC 6265 strict.
|
||||
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);
|
||||
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);
|
||||
|
||||
// Normal cookies with attributes, so no violations
|
||||
// No violations for the maintained quotes
|
||||
assertThat(parser.violations.size(), is(0));
|
||||
}
|
||||
|
||||
|
@ -296,6 +297,24 @@ public class RFC6265CookieParserTest
|
|||
assertCookie("Cookies[1]", cookies[1], "xyz", "pdq", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRFC6265QuotesMaintained()
|
||||
{
|
||||
String rawCookie = "A=\"quotedValue\"";
|
||||
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "A", "quotedValue", 0, null);
|
||||
|
||||
cookies = parseCookieHeaders(CookieCompliance.RFC6265_QUOTED, rawCookie);
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "A", "\"quotedValue\"", 0, null);
|
||||
|
||||
cookies = parseCookieHeaders(CookieCompliance.RFC6265_STRICT, rawCookie);
|
||||
assertThat("Cookies.length", cookies.length, is(1));
|
||||
assertCookie("Cookies[0]", cookies[0], "A", "\"quotedValue\"", 0, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRFC2965QuotedEscape()
|
||||
{
|
||||
|
@ -360,6 +379,23 @@ public class RFC6265CookieParserTest
|
|||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parameters")
|
||||
public void testRFC6265QuotedCookie(Param param)
|
||||
{
|
||||
Cookie[] cookies = parseCookieHeaders(CookieCompliance.RFC6265_QUOTED, param.input);
|
||||
|
||||
assertThat("Cookies.length", cookies.length, is(param.expected.size()));
|
||||
boolean quoted = param.input.contains("\"");
|
||||
|
||||
for (int i = 0; i < cookies.length; i++)
|
||||
{
|
||||
Cookie cookie = cookies[i];
|
||||
String value = cookie.getValue();
|
||||
assertThat(param.expected.get(i), anyOf(is(cookie.getName() + "=" + value), is(cookie.getName() + "=" + QuotedCSV.unquote(value))));
|
||||
}
|
||||
}
|
||||
|
||||
static class Cookie
|
||||
{
|
||||
String name;
|
||||
|
@ -462,7 +498,7 @@ public class RFC6265CookieParserTest
|
|||
}
|
||||
}
|
||||
|
||||
private static class Param
|
||||
public static class Param
|
||||
{
|
||||
private final String input;
|
||||
private final List<String> expected;
|
||||
|
|
|
@ -52,9 +52,7 @@ public interface ConnectionMetaData extends Attributes
|
|||
|
||||
/**
|
||||
* @return whether the functionality of pushing resources is supported
|
||||
* @deprecated in favour of 103 Early Hints
|
||||
*/
|
||||
@Deprecated(since = "12.0.1")
|
||||
default boolean isPushSupported()
|
||||
{
|
||||
return false;
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.http.Syntax;
|
|||
import org.eclipse.jetty.util.Attributes;
|
||||
import org.eclipse.jetty.util.Index;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
|
||||
/**
|
||||
* <p>Utility methods for server-side HTTP cookie handling.</p>
|
||||
|
@ -106,16 +107,16 @@ public final class HttpCookieUtils
|
|||
|
||||
public static String getSetCookie(HttpCookie httpCookie, CookieCompliance compliance)
|
||||
{
|
||||
if (compliance == null || CookieCompliance.RFC6265_LEGACY.compliesWith(compliance))
|
||||
return getRFC6265SetCookie(httpCookie);
|
||||
return getRFC2965SetCookie(httpCookie);
|
||||
if (compliance != null && compliance.getName().contains("RFC2965"))
|
||||
return getRFC2965SetCookie(httpCookie);
|
||||
return getRFC6265SetCookie(httpCookie);
|
||||
}
|
||||
|
||||
public static String getRFC2965SetCookie(HttpCookie httpCookie)
|
||||
{
|
||||
// Check arguments
|
||||
String name = httpCookie.getName();
|
||||
if (name == null || name.length() == 0)
|
||||
if (name == null || name.isEmpty())
|
||||
throw new IllegalArgumentException("Invalid cookie name");
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
@ -129,11 +130,11 @@ public final class HttpCookieUtils
|
|||
|
||||
// Look for domain and path fields and check if they need to be quoted.
|
||||
String domain = httpCookie.getDomain();
|
||||
boolean hasDomain = domain != null && domain.length() > 0;
|
||||
boolean hasDomain = domain != null && !domain.isEmpty();
|
||||
boolean quoteDomain = hasDomain && isQuoteNeeded(domain);
|
||||
|
||||
String path = httpCookie.getPath();
|
||||
boolean hasPath = path != null && path.length() > 0;
|
||||
boolean hasPath = path != null && !path.isEmpty();
|
||||
boolean quotePath = hasPath && isQuoteNeeded(path);
|
||||
|
||||
// Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted
|
||||
|
@ -209,7 +210,7 @@ public final class HttpCookieUtils
|
|||
{
|
||||
// Check arguments
|
||||
String name = httpCookie.getName();
|
||||
if (name == null || name.length() == 0)
|
||||
if (name == null || name.isEmpty())
|
||||
throw new IllegalArgumentException("Bad cookie name");
|
||||
|
||||
try
|
||||
|
@ -233,12 +234,12 @@ public final class HttpCookieUtils
|
|||
|
||||
// Append path
|
||||
String path = httpCookie.getPath();
|
||||
if (path != null && path.length() > 0)
|
||||
if (path != null && !path.isEmpty())
|
||||
builder.append("; Path=").append(path);
|
||||
|
||||
// Append domain
|
||||
String domain = httpCookie.getDomain();
|
||||
if (domain != null && domain.length() > 0)
|
||||
if (domain != null && !domain.isEmpty())
|
||||
builder.append("; Domain=").append(domain);
|
||||
|
||||
// Handle max-age and/or expires
|
||||
|
@ -253,8 +254,11 @@ public final class HttpCookieUtils
|
|||
else
|
||||
builder.append(HttpCookie.formatExpires(Instant.now().plusSeconds(maxAge)));
|
||||
|
||||
builder.append("; Max-Age=");
|
||||
builder.append(maxAge);
|
||||
if (maxAge > 0)
|
||||
{
|
||||
builder.append("; Max-Age=");
|
||||
builder.append(maxAge);
|
||||
}
|
||||
}
|
||||
|
||||
// add the other fields
|
||||
|
@ -288,8 +292,9 @@ public final class HttpCookieUtils
|
|||
{
|
||||
if (KNOWN_ATTRIBUTES.contains(e.getKey()))
|
||||
continue;
|
||||
builder.append("; ").append(e.getKey()).append("=");
|
||||
builder.append(e.getValue());
|
||||
builder.append("; ").append(e.getKey());
|
||||
if (StringUtil.isNotBlank(e.getValue()))
|
||||
builder.append("=").append(e.getValue());
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
|
@ -304,7 +309,7 @@ public final class HttpCookieUtils
|
|||
*/
|
||||
private static boolean isQuoteNeeded(String text)
|
||||
{
|
||||
if (text == null || text.length() == 0)
|
||||
if (text == null || text.isEmpty())
|
||||
return true;
|
||||
|
||||
if (QuotedStringTokenizer.isQuoted(text))
|
||||
|
|
|
@ -29,8 +29,10 @@ import static org.hamcrest.Matchers.allOf;
|
|||
import static org.hamcrest.Matchers.anEmptyMap;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.equalToIgnoringCase;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
@ -91,7 +93,7 @@ public class HttpCookieTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSetRFC2965Cookie() throws Exception
|
||||
public void testSetRFC2965Cookie()
|
||||
{
|
||||
HttpCookie httpCookie;
|
||||
|
||||
|
@ -127,7 +129,7 @@ public class HttpCookieTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testSetRFC6265Cookie() throws Exception
|
||||
public void testSetRFC6265Cookie()
|
||||
{
|
||||
HttpCookie httpCookie;
|
||||
|
||||
|
@ -139,22 +141,33 @@ public class HttpCookieTest
|
|||
|
||||
//test cookies with same name, domain and path
|
||||
httpCookie = HttpCookie.from("everything", "something", -1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
assertEquals("everything=something; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
assertEquals("everything=something; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", -1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true)));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.NONE.getAttributeValue()));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=None", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=None", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.LAX.getAttributeValue()));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=Lax", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue()));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue(), HttpCookie.PARTITIONED_ATTRIBUTE, ""));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; Partitioned; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue(), HttpCookie.PARTITIONED_ATTRIBUTE, Boolean.toString(true)));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; Partitioned; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; Partitioned; SameSite=Strict", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.SAME_SITE_ATTRIBUTE, SameSite.STRICT.getAttributeValue(), HttpCookie.PARTITIONED_ATTRIBUTE, Boolean.toString(true)));
|
||||
String rfc6265SetCookie = HttpCookieUtils.getRFC6265SetCookie(httpCookie);
|
||||
assertThat(rfc6265SetCookie, startsWith("everything=value; Path=path; Domain=domain; Expires="));
|
||||
assertThat(rfc6265SetCookie, endsWith(" GMT; Max-Age=1; Secure; HttpOnly; Partitioned; SameSite=Strict"));
|
||||
|
||||
httpCookie = HttpCookie.from("everything", "value", -1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "domain", HttpCookie.PATH_ATTRIBUTE, "path", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), "Other", "attribute", "Single", ""));
|
||||
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; HttpOnly; Other=attribute; Single", HttpCookieUtils.getRFC6265SetCookie(httpCookie));
|
||||
}
|
||||
|
||||
public static Stream<String> rfc6265BadNameSource()
|
||||
|
|
|
@ -123,7 +123,7 @@ public class ServerHttpCookieTest
|
|||
|
||||
// bad characters
|
||||
Arguments.of(RFC6265_STRICT, "Cookie: name=va\\ue", 400, null, List.of("400", "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\"", 200, "Version=", List.of("[name=\"value\"]").toArray(new String[0])),
|
||||
Arguments.of(RFC6265_STRICT, "Cookie: name=\"value;other=extra\"", 400, null, List.of("400", "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])),
|
||||
|
@ -167,13 +167,12 @@ public class ServerHttpCookieTest
|
|||
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(CookieCompliance.from("0"), "name=value;$version=1;$path=/path;$domain=domain", "name=value; Path=/path; Domain=domain"),
|
||||
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;Domain=domain;Path=/path"),
|
||||
Arguments.of(RFC2965_LEGACY, "name=value;$version=1;$path=/path;$domain=domain", "name=value;Version=1;Domain=domain;Path=/path"),
|
||||
|
||||
Arguments.of(RFC6265, "name=value", "name=value")
|
||||
);
|
||||
}
|
||||
|
|
|
@ -23,12 +23,15 @@ import java.nio.charset.IllegalCharsetNameException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -760,37 +763,54 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
referrer += "?" + query;
|
||||
pushHeaders.put(HttpHeader.REFERER, referrer);
|
||||
|
||||
StringBuilder cookieBuilder = new StringBuilder();
|
||||
Cookie[] cookies = getCookies();
|
||||
if (cookies != null)
|
||||
{
|
||||
for (Cookie cookie : cookies)
|
||||
{
|
||||
if (!cookieBuilder.isEmpty())
|
||||
cookieBuilder.append("; ");
|
||||
cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue());
|
||||
}
|
||||
}
|
||||
Cookie[] existing = getCookies();
|
||||
List<Object> cookies = new ArrayList<>();
|
||||
if (existing != null && existing.length > 0)
|
||||
cookies.addAll(Arrays.asList(existing));
|
||||
|
||||
// Any Set-Cookie in the response should be present in the push.
|
||||
for (HttpField field : _servletContextRequest.getServletContextResponse().getHeaders())
|
||||
{
|
||||
HttpHeader header = field.getHeader();
|
||||
if (header == HttpHeader.SET_COOKIE || header == HttpHeader.SET_COOKIE2)
|
||||
{
|
||||
HttpCookie httpCookie;
|
||||
if (field instanceof HttpCookieUtils.SetCookieHttpField set)
|
||||
httpCookie = set.getHttpCookie();
|
||||
else
|
||||
httpCookie = SET_COOKIE_PARSER.parse(field.getValue());
|
||||
if (httpCookie == null || httpCookie.isExpired())
|
||||
HttpCookie httpCookie = (field instanceof HttpCookieUtils.SetCookieHttpField set)
|
||||
? set.getHttpCookie()
|
||||
: SET_COOKIE_PARSER.parse(field.getValue());
|
||||
|
||||
if (httpCookie == null)
|
||||
continue;
|
||||
if (!cookieBuilder.isEmpty())
|
||||
cookieBuilder.append("; ");
|
||||
cookieBuilder.append(httpCookie.getName()).append("=").append(httpCookie.getValue());
|
||||
|
||||
if (httpCookie.isExpired())
|
||||
{
|
||||
for (Iterator<Object> i = cookies.iterator(); i.hasNext();)
|
||||
{
|
||||
Object o = i.next();
|
||||
if (o instanceof Cookie cookie && cookie.getName().equals(httpCookie.getName()))
|
||||
i.remove();
|
||||
else if (o instanceof HttpCookie cookie && cookie.getName().equals(httpCookie.getName()))
|
||||
i.remove();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cookies.add(httpCookie);
|
||||
}
|
||||
}
|
||||
if (!cookieBuilder.isEmpty())
|
||||
|
||||
if (!cookies.isEmpty())
|
||||
{
|
||||
StringBuilder cookieBuilder = new StringBuilder();
|
||||
for (Object o : cookies)
|
||||
{
|
||||
if (!cookieBuilder.isEmpty())
|
||||
cookieBuilder.append("; ");
|
||||
if (o instanceof Cookie cookie)
|
||||
cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue());
|
||||
else if (o instanceof HttpCookie httpCookie)
|
||||
cookieBuilder.append(httpCookie.getName()).append("=").append(httpCookie.getValue());
|
||||
}
|
||||
pushHeaders.put(HttpHeader.COOKIE, cookieBuilder.toString());
|
||||
}
|
||||
|
||||
String sessionId;
|
||||
HttpSession httpSession = getSession(false);
|
||||
|
|
|
@ -17,6 +17,8 @@ import java.net.URI;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
|
@ -38,6 +40,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ContextScopeListenerTest
|
||||
{
|
||||
|
@ -93,6 +96,8 @@ public class ContextScopeListenerTest
|
|||
}
|
||||
}), "/");
|
||||
|
||||
CountDownLatch complete = new CountDownLatch(3);
|
||||
|
||||
_contextHandler.addEventListener(new ContextHandler.ContextScopeListener()
|
||||
{
|
||||
// Use a lock to prevent the async thread running the listener concurrently.
|
||||
|
@ -112,12 +117,15 @@ public class ContextScopeListenerTest
|
|||
String pathInContext = (request == null) ? "null" : Request.getPathInContext(request);
|
||||
_history.add("exitScope " + pathInContext);
|
||||
_lock.unlock();
|
||||
complete.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + "/initialPath");
|
||||
ContentResponse response = _client.GET(uri);
|
||||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
|
||||
assertTrue(complete.await(5, TimeUnit.SECONDS));
|
||||
assertHistory(
|
||||
"enterScope /initialPath",
|
||||
"doGet",
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
|
@ -31,6 +32,7 @@ import jakarta.servlet.ServletResponse;
|
|||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.ee10.servlet.security.ConstraintMapping;
|
||||
|
@ -108,6 +110,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("success"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "doFilter /", "FORWARD /", "requestDestroyed /");
|
||||
}
|
||||
|
||||
|
@ -135,6 +138,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("success"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "doFilter /", "INCLUDE /", "requestDestroyed /");
|
||||
}
|
||||
|
||||
|
@ -165,6 +169,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("success"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /",
|
||||
"requestInitialized /", "doFilter /", "ASYNC /", "requestDestroyed /");
|
||||
}
|
||||
|
@ -201,6 +206,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.INTERNAL_SERVER_ERROR_500));
|
||||
assertThat(response.getContentAsString(), equalTo("error handled"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /",
|
||||
"requestInitialized /", "doFilter /error", "ERROR /error", "requestDestroyed /");
|
||||
}
|
||||
|
@ -240,6 +246,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.INTERNAL_SERVER_ERROR_500));
|
||||
assertThat(response.getContentAsString(), equalTo("error handled"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("errorHandler"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /", "errorHandler");
|
||||
}
|
||||
|
||||
|
@ -270,6 +277,7 @@ public class ServletRequestListenerTest
|
|||
ContentResponse response = _httpClient.GET("http://localhost:" + _connector.getLocalPort());
|
||||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("from servlet"));
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /");
|
||||
|
||||
response = _httpClient.GET("http://localhost:" + _connector.getLocalPort() + "/authed");
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.Random;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
@ -25,9 +26,11 @@ import org.eclipse.jetty.client.BufferingResponseListener;
|
|||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.Result;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -53,19 +56,31 @@ public class PushedResourcesTest extends AbstractTest
|
|||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
Cookie cookie0 = new Cookie("C0", "v0");
|
||||
cookie0.setMaxAge(0);
|
||||
response.addCookie(cookie0);
|
||||
Cookie cookie1 = new Cookie("C1", "v1");
|
||||
response.addCookie(cookie1);
|
||||
|
||||
String target = request.getRequestURI();
|
||||
if (target.equals(path1))
|
||||
{
|
||||
assertThat(request.getHeader("H1"), Matchers.equalTo("V1"));
|
||||
assertThat(request.getHeader("Cookie"), Matchers.equalTo("C1=v1"));
|
||||
response.getOutputStream().write(pushBytes1);
|
||||
}
|
||||
else if (target.equals(path2))
|
||||
{
|
||||
assertThat(request.getHeader("H1"), Matchers.nullValue());
|
||||
assertThat(request.getHeader("Cookie"), Matchers.equalTo("C1=v1"));
|
||||
response.getOutputStream().write(pushBytes2);
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThat(request.getHeader("Cookie"), Matchers.equalTo("C0=toBeRemoved"));
|
||||
request.newPushBuilder()
|
||||
.path(path1)
|
||||
.addHeader("H1", "V1")
|
||||
.push();
|
||||
request.newPushBuilder()
|
||||
.path(path2)
|
||||
|
@ -78,6 +93,7 @@ public class PushedResourcesTest extends AbstractTest
|
|||
CountDownLatch latch1 = new CountDownLatch(1);
|
||||
CountDownLatch latch2 = new CountDownLatch(1);
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.headers(h -> h.add("Cookie", "C0=toBeRemoved"))
|
||||
.onPush((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
|
|
|
@ -23,12 +23,15 @@ import java.nio.charset.IllegalCharsetNameException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -761,37 +764,54 @@ public class ServletApiRequest implements HttpServletRequest
|
|||
referrer += "?" + query;
|
||||
pushHeaders.put(HttpHeader.REFERER, referrer);
|
||||
|
||||
StringBuilder cookieBuilder = new StringBuilder();
|
||||
Cookie[] cookies = getCookies();
|
||||
if (cookies != null)
|
||||
{
|
||||
for (Cookie cookie : cookies)
|
||||
{
|
||||
if (!cookieBuilder.isEmpty())
|
||||
cookieBuilder.append("; ");
|
||||
cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue());
|
||||
}
|
||||
}
|
||||
Cookie[] existing = getCookies();
|
||||
List<Object> cookies = new ArrayList<>();
|
||||
if (existing != null && existing.length > 0)
|
||||
cookies.addAll(Arrays.asList(existing));
|
||||
|
||||
// Any Set-Cookie in the response should be present in the push.
|
||||
for (HttpField field : _servletContextRequest.getServletContextResponse().getHeaders())
|
||||
{
|
||||
HttpHeader header = field.getHeader();
|
||||
if (header == HttpHeader.SET_COOKIE || header == HttpHeader.SET_COOKIE2)
|
||||
{
|
||||
HttpCookie httpCookie;
|
||||
if (field instanceof HttpCookieUtils.SetCookieHttpField set)
|
||||
httpCookie = set.getHttpCookie();
|
||||
else
|
||||
httpCookie = SET_COOKIE_PARSER.parse(field.getValue());
|
||||
if (httpCookie == null || httpCookie.isExpired())
|
||||
HttpCookie httpCookie = (field instanceof HttpCookieUtils.SetCookieHttpField set)
|
||||
? set.getHttpCookie()
|
||||
: SET_COOKIE_PARSER.parse(field.getValue());
|
||||
|
||||
if (httpCookie == null)
|
||||
continue;
|
||||
if (!cookieBuilder.isEmpty())
|
||||
cookieBuilder.append("; ");
|
||||
cookieBuilder.append(httpCookie.getName()).append("=").append(httpCookie.getValue());
|
||||
|
||||
if (httpCookie.isExpired())
|
||||
{
|
||||
for (Iterator<Object> i = cookies.iterator(); i.hasNext();)
|
||||
{
|
||||
Object o = i.next();
|
||||
if (o instanceof Cookie cookie && cookie.getName().equals(httpCookie.getName()))
|
||||
i.remove();
|
||||
else if (o instanceof HttpCookie cookie && cookie.getName().equals(httpCookie.getName()))
|
||||
i.remove();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cookies.add(httpCookie);
|
||||
}
|
||||
}
|
||||
if (!cookieBuilder.isEmpty())
|
||||
|
||||
if (!cookies.isEmpty())
|
||||
{
|
||||
StringBuilder cookieBuilder = new StringBuilder();
|
||||
for (Object o : cookies)
|
||||
{
|
||||
if (!cookieBuilder.isEmpty())
|
||||
cookieBuilder.append("; ");
|
||||
if (o instanceof Cookie cookie)
|
||||
cookieBuilder.append(cookie.getName()).append("=").append(cookie.getValue());
|
||||
else if (o instanceof HttpCookie httpCookie)
|
||||
cookieBuilder.append(httpCookie.getName()).append("=").append(httpCookie.getValue());
|
||||
}
|
||||
pushHeaders.put(HttpHeader.COOKIE, cookieBuilder.toString());
|
||||
}
|
||||
|
||||
String sessionId;
|
||||
HttpSession httpSession = getSession(false);
|
||||
|
|
|
@ -17,6 +17,8 @@ import java.net.URI;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
|
@ -38,6 +40,7 @@ import org.junit.jupiter.api.Test;
|
|||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class ContextScopeListenerTest
|
||||
{
|
||||
|
@ -93,6 +96,8 @@ public class ContextScopeListenerTest
|
|||
}
|
||||
}), "/");
|
||||
|
||||
CountDownLatch complete = new CountDownLatch(3);
|
||||
|
||||
_contextHandler.addEventListener(new ContextHandler.ContextScopeListener()
|
||||
{
|
||||
// Use a lock to prevent the async thread running the listener concurrently.
|
||||
|
@ -112,12 +117,15 @@ public class ContextScopeListenerTest
|
|||
String pathInContext = (request == null) ? "null" : Request.getPathInContext(request);
|
||||
_history.add("exitScope " + pathInContext);
|
||||
_lock.unlock();
|
||||
complete.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
URI uri = URI.create("http://localhost:" + _connector.getLocalPort() + "/initialPath");
|
||||
ContentResponse response = _client.GET(uri);
|
||||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
|
||||
assertTrue(complete.await(5, TimeUnit.SECONDS));
|
||||
assertHistory(
|
||||
"enterScope /initialPath",
|
||||
"doGet",
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.servlet.AsyncContext;
|
||||
|
@ -31,6 +32,7 @@ import jakarta.servlet.ServletResponse;
|
|||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.awaitility.Awaitility;
|
||||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.ee11.servlet.security.ConstraintMapping;
|
||||
|
@ -108,6 +110,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("success"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "doFilter /", "FORWARD /", "requestDestroyed /");
|
||||
}
|
||||
|
||||
|
@ -135,6 +138,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("success"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "doFilter /", "INCLUDE /", "requestDestroyed /");
|
||||
}
|
||||
|
||||
|
@ -165,6 +169,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("success"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /",
|
||||
"requestInitialized /", "doFilter /", "ASYNC /", "requestDestroyed /");
|
||||
}
|
||||
|
@ -201,6 +206,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.INTERNAL_SERVER_ERROR_500));
|
||||
assertThat(response.getContentAsString(), equalTo("error handled"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /",
|
||||
"requestInitialized /", "doFilter /error", "ERROR /error", "requestDestroyed /");
|
||||
}
|
||||
|
@ -240,6 +246,7 @@ public class ServletRequestListenerTest
|
|||
assertThat(response.getStatus(), equalTo(HttpStatus.INTERNAL_SERVER_ERROR_500));
|
||||
assertThat(response.getContentAsString(), equalTo("error handled"));
|
||||
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("errorHandler"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /", "errorHandler");
|
||||
}
|
||||
|
||||
|
@ -270,6 +277,7 @@ public class ServletRequestListenerTest
|
|||
ContentResponse response = _httpClient.GET("http://localhost:" + _connector.getLocalPort());
|
||||
assertThat(response.getStatus(), equalTo(HttpStatus.OK_200));
|
||||
assertThat(response.getContentAsString(), equalTo("from servlet"));
|
||||
Awaitility.waitAtMost(5, TimeUnit.SECONDS).pollInterval(10, TimeUnit.MILLISECONDS).until(() -> _events.contains("requestDestroyed /"));
|
||||
assertEvents("requestInitialized /", "doFilter /", "REQUEST /", "requestDestroyed /");
|
||||
|
||||
response = _httpClient.GET("http://localhost:" + _connector.getLocalPort() + "/authed");
|
||||
|
|
|
@ -18,6 +18,7 @@ import java.util.Random;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
@ -25,9 +26,11 @@ import org.eclipse.jetty.client.BufferingResponseListener;
|
|||
import org.eclipse.jetty.client.ContentResponse;
|
||||
import org.eclipse.jetty.client.Result;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
@ -53,19 +56,31 @@ public class PushedResourcesTest extends AbstractTest
|
|||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
Cookie cookie0 = new Cookie("C0", "v0");
|
||||
cookie0.setMaxAge(0);
|
||||
response.addCookie(cookie0);
|
||||
Cookie cookie1 = new Cookie("C1", "v1");
|
||||
response.addCookie(cookie1);
|
||||
|
||||
String target = request.getRequestURI();
|
||||
if (target.equals(path1))
|
||||
{
|
||||
assertThat(request.getHeader("H1"), Matchers.equalTo("V1"));
|
||||
assertThat(request.getHeader("Cookie"), Matchers.equalTo("C1=v1"));
|
||||
response.getOutputStream().write(pushBytes1);
|
||||
}
|
||||
else if (target.equals(path2))
|
||||
{
|
||||
assertThat(request.getHeader("H1"), Matchers.nullValue());
|
||||
assertThat(request.getHeader("Cookie"), Matchers.equalTo("C1=v1"));
|
||||
response.getOutputStream().write(pushBytes2);
|
||||
}
|
||||
else
|
||||
{
|
||||
assertThat(request.getHeader("Cookie"), Matchers.equalTo("C0=toBeRemoved"));
|
||||
request.newPushBuilder()
|
||||
.path(path1)
|
||||
.addHeader("H1", "V1")
|
||||
.push();
|
||||
request.newPushBuilder()
|
||||
.path(path2)
|
||||
|
@ -78,6 +93,7 @@ public class PushedResourcesTest extends AbstractTest
|
|||
CountDownLatch latch1 = new CountDownLatch(1);
|
||||
CountDownLatch latch2 = new CountDownLatch(1);
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.headers(h -> h.add("Cookie", "C0=toBeRemoved"))
|
||||
.onPush((mainRequest, pushedRequest) -> new BufferingResponseListener()
|
||||
{
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue