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:
Greg Wilkins 2024-06-23 13:13:18 +10:00 committed by GitHub
parent beff0fa990
commit 1e241d8ed5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 281 additions and 88 deletions

View File

@ -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)

View File

@ -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);
}
/**

View File

@ -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))

View File

@ -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;

View File

@ -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;

View File

@ -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))

View File

@ -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()

View File

@ -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")
);
}

View File

@ -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);

View File

@ -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",

View File

@ -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");

View File

@ -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

View File

@ -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);

View File

@ -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",

View File

@ -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");

View File

@ -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