diff --git a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
index 90857287c2fe..10a753b5db31 100644
--- a/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
+++ b/documentation/jetty-documentation/src/main/java/org/eclipse/jetty/docs/programming/client/http/HTTPClientDocs.java
@@ -17,8 +17,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.CookieStore;
-import java.net.HttpCookie;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.file.Path;
@@ -54,6 +52,8 @@
import org.eclipse.jetty.client.transport.HttpClientTransportDynamic;
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
import org.eclipse.jetty.fcgi.client.transport.HttpClientTransportOverFCGI;
+import org.eclipse.jetty.http.HttpCookie;
+import org.eclipse.jetty.http.HttpCookieStore;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
@@ -67,7 +67,6 @@
import org.eclipse.jetty.io.ClientConnector;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.HttpCookieStore;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -541,8 +540,8 @@ public void getCookies() throws Exception
httpClient.start();
// tag::getCookies[]
- CookieStore cookieStore = httpClient.getCookieStore();
- List
HttpClient transparently pools connections to servers, but allows direct control of connections * for cases where this is needed.
- *HttpClient also acts as a central configuration point for cookies, via {@link #getCookieStore()}.
+ *HttpClient also acts as a central configuration point for cookies, via {@link #getHttpCookieStore()}.
*Typical usage:
** HttpClient httpClient = new HttpClient(); @@ -121,8 +123,8 @@ public class HttpClient extends ContainerLifeCycle private final HttpClientTransport transport; private final ClientConnector connector; private AuthenticationStore authenticationStore = new HttpAuthenticationStore(); - private CookieManager cookieManager; - private CookieStore cookieStore; + private HttpCookieStore cookieStore; + private HttpCookieParser cookieParser; private SocketAddressResolver resolver; private HttpField agentField = new HttpField(HttpHeader.USER_AGENT, USER_AGENT); private boolean followRedirects = true; @@ -222,8 +224,9 @@ protected void doStart() throws Exception decoderFactories.put(new GZIPContentDecoder.Factory(byteBufferPool)); - cookieManager = newCookieManager(); - cookieStore = cookieManager.getCookieStore(); + if (cookieStore == null) + cookieStore = new HttpCookieStore.Default(); + cookieParser = new HttpCookieParser(); transport.setHttpClient(this); @@ -236,11 +239,6 @@ protected void doStart() throws Exception } } - private CookieManager newCookieManager() - { - return new CookieManager(getCookieStore(), CookiePolicy.ACCEPT_ALL); - } - @Override protected void doStop() throws Exception { @@ -274,7 +272,7 @@ public ListgetRequestListeners() /** * @return the cookie store associated with this instance */ - public CookieStore getCookieStore() + public HttpCookieStore getHttpCookieStore() { return cookieStore; } @@ -282,12 +280,11 @@ public CookieStore getCookieStore() /** * @param cookieStore the cookie store associated with this instance */ - public void setCookieStore(CookieStore cookieStore) + public void setHttpCookieStore(HttpCookieStore cookieStore) { if (isStarted()) throw new IllegalStateException(); this.cookieStore = Objects.requireNonNull(cookieStore); - this.cookieManager = newCookieManager(); } public void putCookie(URI uri, HttpField field) @@ -297,9 +294,8 @@ public void putCookie(URI uri, HttpField field) String value = field.getValue(); if (value != null) { - Map > header = new HashMap<>(1); - header.put(field.getHeader().asString(), List.of(value)); - cookieManager.put(uri, header); + HttpCookie cookie = cookieParser.parse(uri, field); + cookieStore.add(uri, cookie); } } catch (IOException x) @@ -1154,4 +1150,66 @@ public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.C sslContextFactory = getSslContextFactory(); return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory); } + + private static class HttpCookieParser extends CookieManager + { + public HttpCookieParser() + { + super(new Store(), CookiePolicy.ACCEPT_ALL); + } + + public HttpCookie parse(URI uri, HttpField field) throws IOException + { + // TODO: hacky implementation waiting for a real HttpCookie parser. + String value = field.getValue(); + if (value == null) + return null; + Map > header = new HashMap<>(1); + header.put(field.getHeader().asString(), List.of(value)); + put(uri, header); + Store store = (Store)getCookieStore(); + return store.cookie; + } + + private static class Store implements CookieStore + { + private HttpCookie cookie; + + @Override + public void add(URI uri, java.net.HttpCookie cookie) + { + this.cookie = HttpCookie.from(cookie); + } + + @Override + public List get(URI uri) + { + throw new UnsupportedOperationException(); + } + + @Override + public List getCookies() + { + throw new UnsupportedOperationException(); + } + + @Override + public List getURIs() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(URI uri, java.net.HttpCookie cookie) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll() + { + throw new UnsupportedOperationException(); + } + } + } } diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java index 41170ddc6feb..8e7bf1e10c16 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/Request.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.client; import java.io.IOException; -import java.net.HttpCookie; import java.net.URI; import java.net.URLEncoder; import java.nio.ByteBuffer; @@ -30,6 +29,7 @@ import java.util.function.Consumer; import java.util.function.Supplier; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpConnection.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpConnection.java index 95e4ca21b34d..e0f6696cfcfb 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpConnection.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpConnection.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.client.transport; -import java.net.CookieStore; -import java.net.HttpCookie; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; @@ -30,13 +28,14 @@ import org.eclipse.jetty.client.ProxyConfiguration; import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.http.HttpCookieStore; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.CyclicTimeouts; import org.eclipse.jetty.util.Attachable; -import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.NanoTime; import org.eclipse.jetty.util.thread.AutoLock; import org.eclipse.jetty.util.thread.Scheduler; @@ -210,12 +209,12 @@ protected void normalizeRequest(HttpRequest request) // Cookies StringBuilder cookies = convertCookies(request.getCookies(), null); - CookieStore cookieStore = getHttpClient().getCookieStore(); + HttpCookieStore cookieStore = getHttpClient().getHttpCookieStore(); if (cookieStore != null && cookieStore.getClass() != HttpCookieStore.Empty.class) { URI uri = request.getURI(); if (uri != null) - cookies = convertCookies(HttpCookieStore.matchPath(uri, cookieStore.get(uri)), cookies); + cookies = convertCookies(cookieStore.match(uri), cookies); } if (cookies != null) { diff --git a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java index c3a0dd68bd25..19276be6692e 100644 --- a/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java +++ b/jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpRequest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.client.transport; import java.io.IOException; -import java.net.HttpCookie; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; @@ -48,6 +47,7 @@ import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; import org.eclipse.jetty.client.Result; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 04ec16bb8f35..2468b376e4bd 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.HttpCookie; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; @@ -48,6 +47,7 @@ import org.eclipse.jetty.client.transport.HttpRequest; import org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP; import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; @@ -122,7 +122,7 @@ public void testStoppingClosesConnections(Scenario scenario) throws Exception Origin origin = destination.getOrigin(); String uri = origin.getScheme() + "://" + origin.getAddress(); - client.getCookieStore().add(URI.create(uri), new HttpCookie("foo", "bar")); + client.getHttpCookieStore().add(URI.create(uri), HttpCookie.from("foo", "bar")); client.stop(); diff --git a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java index ed0b0e34708c..44b7d76b76c8 100644 --- a/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java +++ b/jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpCookieTest.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.client; -import java.net.CookieStore; -import java.net.HttpCookie; import java.net.URI; import java.util.Arrays; import java.util.HashSet; @@ -23,9 +21,10 @@ import java.util.Set; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.http.HttpCookieStore; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.util.HttpCookieStore; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -60,7 +59,7 @@ protected void service(Request request, org.eclipse.jetty.server.Response respon Response response = client.GET(uri); assertEquals(200, response.getStatus()); - List cookies = client.getCookieStore().get(URI.create(uri)); + List cookies = client.getHttpCookieStore().match(URI.create(uri)); assertNotNull(cookies); assertEquals(1, cookies.size()); HttpCookie cookie = cookies.get(0); @@ -92,8 +91,8 @@ protected void service(Request request, org.eclipse.jetty.server.Response respon int port = connector.getLocalPort(); String path = "/path"; String uri = scenario.getScheme() + "://" + host + ":" + port; - HttpCookie cookie = new HttpCookie(name, value); - client.getCookieStore().add(URI.create(uri), cookie); + HttpCookie cookie = HttpCookie.from(name, value); + client.getHttpCookieStore().add(URI.create(uri), cookie); Response response = client.GET(scenario.getScheme() + "://" + host + ":" + port + path); assertEquals(200, response.getStatus()); @@ -116,7 +115,7 @@ protected void service(Request request, org.eclipse.jetty.server.Response respon .scheme(scenario.getScheme()) .send(); assertEquals(200, response.getStatus()); - assertTrue(client.getCookieStore().getCookies().isEmpty()); + assertTrue(client.getHttpCookieStore().all().isEmpty()); } @ParameterizedTest @@ -133,7 +132,7 @@ public void testPerRequestCookieIsSentWithEmptyCookieStore(Scenario scenario) th testPerRequestCookieIsSent(scenario, new HttpCookieStore.Empty()); } - private void testPerRequestCookieIsSent(Scenario scenario, CookieStore cookieStore) throws Exception + private void testPerRequestCookieIsSent(Scenario scenario, HttpCookieStore cookieStore) throws Exception { final String name = "foo"; final String value = "bar"; @@ -153,12 +152,12 @@ protected void service(Request request, org.eclipse.jetty.server.Response respon startClient(scenario, client -> { if (cookieStore != null) - client.setCookieStore(cookieStore); + client.setHttpCookieStore(cookieStore); }); ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) .scheme(scenario.getScheme()) - .cookie(new HttpCookie(name, value)) + .cookie(HttpCookie.from(name, value)) .timeout(5, TimeUnit.SECONDS) .send(); assertEquals(200, response.getStatus()); diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java index 1c6e8f310d4c..d613a5717d29 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java @@ -70,30 +70,6 @@ public static String formatDate(Instant instant) return formatDate(instant.toEpochMilli()); } - /** - * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies - * - * @param buf the buffer to put the formatted date into - * @param date the date in milliseconds - */ - public static void formatCookieDate(StringBuilder buf, long date) - { - __dateGenerator.get().doFormatCookieDate(buf, date); - } - - /** - * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies - * - * @param date the date in milliseconds - * @return the formatted date - */ - public static String formatCookieDate(long date) - { - StringBuilder buf = new StringBuilder(28); - formatCookieDate(buf, date); - return buf.toString(); - } - private final StringBuilder buf = new StringBuilder(32); private final GregorianCalendar gc = new GregorianCalendar(__GMT); diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java index 74fda8ac33f0..d5fe69705777 100644 --- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java @@ -13,51 +13,335 @@ package org.eclipse.jetty.http; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Collections; -import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.TreeMap; -import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Index; -import org.eclipse.jetty.util.QuotedStringTokenizer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Jetty Management of RFC6265 HTTP Cookies (with fallback support for RFC2965) + * Implementation of RFC6265 HTTP Cookies (with fallback support for RFC2965).
*/ public interface HttpCookie { - Logger LOG = LoggerFactory.getLogger(HttpCookie.class); - - String __COOKIE_DELIM = "\",;\\ \t"; - String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim(); - String COMMENT_ATTRIBUTE = "Comment"; String DOMAIN_ATTRIBUTE = "Domain"; + String EXPIRES_ATTRIBUTE = "Expires"; String HTTP_ONLY_ATTRIBUTE = "HttpOnly"; String MAX_AGE_ATTRIBUTE = "Max-Age"; String PATH_ATTRIBUTE = "Path"; String SAME_SITE_ATTRIBUTE = "SameSite"; String SECURE_ATTRIBUTE = "Secure"; - IndexKNOWN_ATTRIBUTES = new Index.Builder ().caseSensitive(false) - .with(COMMENT_ATTRIBUTE) - .with(DOMAIN_ATTRIBUTE) - .with(HTTP_ONLY_ATTRIBUTE) - .with(MAX_AGE_ATTRIBUTE) - .with(PATH_ATTRIBUTE) - .with(SAME_SITE_ATTRIBUTE) - .with(SECURE_ATTRIBUTE) - .build(); /** - * Name of context attribute with default SameSite cookie value + * @return the cookie name + */ + String getName(); + + /** + * @return the cookie value */ - String SAME_SITE_DEFAULT_ATTRIBUTE = "org.eclipse.jetty.cookie.sameSiteDefault"; + String getValue(); + + /** + * @return the value of the {@code Version} attribute + */ + int getVersion(); + + /** + * @return the attributes associated with this cookie + */ + Map getAttributes(); + + /** + * @return the value of the {@code Expires} attribute, or {@code null} if not present + * @see #EXPIRES_ATTRIBUTE + */ + default Instant getExpires() + { + String expires = getAttributes().get(EXPIRES_ATTRIBUTE); + return expires == null ? null : parseExpires(expires); + } + + /** + * @return the value of the {@code Max-Age} attribute, in seconds, or {@code -1} if not present + * @see #MAX_AGE_ATTRIBUTE + */ + default long getMaxAge() + { + String ma = getAttributes().get(MAX_AGE_ATTRIBUTE); + return ma == null ? -1 : Long.parseLong(ma); + } + + /** + * @return whether the cookie is expired + */ + default boolean isExpired() + { + if (getMaxAge() == 0) + return true; + Instant expires = getExpires(); + return expires != null && Instant.now().isAfter(expires); + } + + /** + * Equivalent to {@code getAttributes().get(COMMENT_ATTRIBUTE)}.
+ * + * @return the value of the {@code Comment} attribute + * @see #COMMENT_ATTRIBUTE + */ + default String getComment() + { + return getAttributes().get(COMMENT_ATTRIBUTE); + } + + /** + *Equivalent to {@code getAttributes().get(DOMAIN_ATTRIBUTE)}.
+ * + * @return the value of the {@code Domain} attribute + * @see #DOMAIN_ATTRIBUTE + */ + default String getDomain() + { + return getAttributes().get(DOMAIN_ATTRIBUTE); + } + + /** + *Equivalent to {@code getAttributes().get(PATH_ATTRIBUTE)}.
+ * + * @return the value of the {@code Path} attribute + * @see #PATH_ATTRIBUTE + */ + default String getPath() + { + return getAttributes().get(PATH_ATTRIBUTE); + } + + /** + * @return whether the {@code Secure} attribute is present + * @see #SECURE_ATTRIBUTE + */ + default boolean isSecure() + { + return Boolean.parseBoolean(getAttributes().get(SECURE_ATTRIBUTE)); + } + + /** + * @return the value of the {@code SameSite} attribute + * @see #SAME_SITE_ATTRIBUTE + */ + default SameSite getSameSite() + { + return SameSite.from(getAttributes().get(SAME_SITE_ATTRIBUTE)); + } + + /** + * @return whether the {@code HttpOnly} attribute is present + * @see #HTTP_ONLY_ATTRIBUTE + */ + default boolean isHttpOnly() + { + return Boolean.parseBoolean(getAttributes().get(HTTP_ONLY_ATTRIBUTE)); + } + + @Override + int hashCode(); + + @Override + boolean equals(Object obj); + + /** + *A wrapper for {@code HttpCookie} instances.
+ */ + class Wrapper implements HttpCookie + { + private final HttpCookie wrapped; + + public Wrapper(HttpCookie wrapped) + { + this.wrapped = wrapped; + } + + public HttpCookie getWrapped() + { + return wrapped; + } + + @Override + public String getName() + { + return getWrapped().getName(); + } + + @Override + public String getValue() + { + return getWrapped().getValue(); + } + + @Override + public int getVersion() + { + return getWrapped().getVersion(); + } + + @Override + public MapgetAttributes() + { + return getWrapped().getAttributes(); + } + + @Override + public Instant getExpires() + { + return getWrapped().getExpires(); + } + + @Override + public long getMaxAge() + { + return getWrapped().getMaxAge(); + } + + @Override + public boolean isExpired() + { + return getWrapped().isExpired(); + } + + @Override + public String getComment() + { + return getWrapped().getComment(); + } + + @Override + public String getDomain() + { + return getWrapped().getDomain(); + } + + @Override + public String getPath() + { + return getWrapped().getPath(); + } + + @Override + public boolean isSecure() + { + return getWrapped().isSecure(); + } + + @Override + public SameSite getSameSite() + { + return getWrapped().getSameSite(); + } + + @Override + public boolean isHttpOnly() + { + return getWrapped().isHttpOnly(); + } + + @Override + public int hashCode() + { + return HttpCookie.hashCode(this); + } + + @Override + public boolean equals(Object obj) + { + return HttpCookie.equals(this, obj); + } + + @Override + public String toString() + { + return HttpCookie.toString(this); + } + } + + /** + * Immutable implementation of {@link HttpCookie}.
+ */ + class Immutable implements HttpCookie + { + private final String _name; + private final String _value; + private final int _version; + private final Map_attributes; + + private Immutable(String name, String value, int version, Map attributes) + { + _name = name; + _value = value; + _version = version; + _attributes = attributes == null || attributes.isEmpty() ? Collections.emptyMap() : attributes; + } + + /** + * @return the cookie name + */ + @Override + public String getName() + { + return _name; + } + + /** + * @return the cookie value + */ + @Override + public String getValue() + { + return _value; + } + + /** + * @return the cookie version + */ + @Override + public int getVersion() + { + return _version; + } + + /** + * @return the attributes associated with this cookie + */ + @Override + public Map getAttributes() + { + return _attributes; + } + + @Override + public int hashCode() + { + return HttpCookie.hashCode(this); + } + + @Override + public boolean equals(Object obj) + { + return HttpCookie.equals(this, obj); + } + + @Override + public String toString() + { + return HttpCookie.toString(this); + } + } enum SameSite { @@ -93,7 +377,7 @@ public static SameSite from(String sameSite) } /** - * Create new HttpCookie from specific values. + * Creates new HttpCookie from specific values. * * @param name the name of the cookie * @param value the value of the cookie @@ -104,14 +388,14 @@ static HttpCookie from(String name, String value) } /** - * Create new HttpCookie from specific values and attributes. + * Creates new HttpCookie from specific values and attributes. * * @param name the name of the cookie * @param value the value of the cookie * @param attributes the map of attributes to use with this cookie (this map is used for field values * such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()}, - * {@link #isSecure()}, {@link #getComment()}. These attributes are removed from the stored - * attributes returned from {@link #getAttributes()}. + * {@link #isSecure()}, {@link #getComment()}, plus any newly defined attributes unknown to this + * code base. */ static HttpCookie from(String name, String value, Map attributes) { @@ -119,15 +403,15 @@ static HttpCookie from(String name, String value, Map attributes } /** - * Create new HttpCookie from specific values and attributes. + * Creates new HttpCookie from specific values and attributes. * * @param name the name of the cookie * @param value the value of the cookie * @param version the version of the cookie (only used in RFC2965 mode) * @param attributes the map of attributes to use with this cookie (this map is used for field values * such as {@link #getDomain()}, {@link #getPath()}, {@link #getMaxAge()}, {@link #isHttpOnly()}, - * {@link #isSecure()}, {@link #getComment()}. These attributes are removed from the stored - * attributes returned from {@link #getAttributes()}. + * {@link #isSecure()}, {@link #getComment()}, plus any newly defined attributes unknown to this + * code base. */ static HttpCookie from(String name, String value, int version, Map attributes) { @@ -156,100 +440,114 @@ static HttpCookie from(HttpCookie cookie, String... additionalAttributes) return new Immutable(cookie.getName(), cookie.getValue(), cookie.getVersion(), attributes); } - /** - * @return the cookie name - */ - String getName(); - - /** - * @return the cookie value - */ - String getValue(); - - /** - * @return the cookie version - */ - int getVersion(); - - /** - * @return the cookie comment. - * Equivalent to a `get` of {@link #COMMENT_ATTRIBUTE} on {@link #getAttributes()}. - */ - default String getComment() + static HttpCookie from(java.net.HttpCookie httpCookie) { - return getAttributes().get(COMMENT_ATTRIBUTE); + return new JavaNetHttpCookie(httpCookie); } - /** - * @return the cookie domain. - * Equivalent to a `get` of {@link #DOMAIN_ATTRIBUTE} on {@link #getAttributes()}. - */ - default String getDomain() + static Builder build(String name, String value) { - return getAttributes().get(DOMAIN_ATTRIBUTE); + return new Builder(name, value); } - /** - * @return the cookie max age in seconds - * Equivalent to a `get` of {@link #MAX_AGE_ATTRIBUTE} on {@link #getAttributes()}. - */ - default long getMaxAge() + static Builder build(String name, String value, int version) { - String ma = getAttributes().get(MAX_AGE_ATTRIBUTE); - return ma == null ? -1 : Long.parseLong(ma); + return new Builder(name, value, version); } - /** - * @return the cookie path - * Equivalent to a `get` of {@link #PATH_ATTRIBUTE} on {@link #getAttributes()}. - */ - default String getPath() + static Builder build(HttpCookie httpCookie) { - return getAttributes().get(PATH_ATTRIBUTE); + Builder builder = new Builder(httpCookie.getName(), httpCookie.getValue(), httpCookie.getVersion()); + for (Map.Entry entry : httpCookie.getAttributes().entrySet()) + builder = builder.attribute(entry.getKey(), entry.getValue()); + return builder; } - /** - * @return whether the cookie is valid for secure domains - * Equivalent to a `get` of {@link #SECURE_ATTRIBUTE} on {@link #getAttributes()}. - */ - default boolean isSecure() + static Builder build(java.net.HttpCookie httpCookie) { - return Boolean.parseBoolean(getAttributes().get(SECURE_ATTRIBUTE)); + return new Builder(httpCookie.getName(), httpCookie.getValue(), httpCookie.getVersion()) + .comment(httpCookie.getComment()) + .domain(httpCookie.getDomain()) + .httpOnly(httpCookie.isHttpOnly()) + .maxAge(httpCookie.getMaxAge()) + .path(httpCookie.getPath()) + .secure(httpCookie.getSecure()); } - /** - * @return the cookie {@code SameSite} attribute value - * Equivalent to a `get` of {@link #SAME_SITE_ATTRIBUTE} on {@link #getAttributes()}. - */ - default SameSite getSameSite() + static java.net.HttpCookie asJavaNetHttpCookie(HttpCookie httpCookie) { - return SameSite.from(getAttributes().get(SAME_SITE_ATTRIBUTE)); + if (httpCookie.getSameSite() != null) + throw new IllegalArgumentException("SameSite attribute not supported by " + java.net.HttpCookie.class.getName()); + java.net.HttpCookie cookie = new java.net.HttpCookie(httpCookie.getName(), httpCookie.getValue()); + cookie.setVersion(httpCookie.getVersion()); + cookie.setComment(httpCookie.getComment()); + cookie.setDomain(httpCookie.getDomain()); + cookie.setHttpOnly(httpCookie.isHttpOnly()); + cookie.setMaxAge(httpCookie.getMaxAge()); + cookie.setPath(httpCookie.getPath()); + cookie.setSecure(httpCookie.isSecure()); + return cookie; } /** - * @return whether the cookie is valid for the http protocol only - * Equivalent to a `get` of {@link #HTTP_ONLY_ATTRIBUTE} on {@link #getAttributes()}. + * Implementation of {@link Object#hashCode()} compatible with RFC 6265.
+ * + * @param httpCookie the cookie to be hashed + * @return the hash code of the cookie + * @see #equals(HttpCookie, Object) */ - default boolean isHttpOnly() + static int hashCode(HttpCookie httpCookie) { - return Boolean.parseBoolean(getAttributes().get(HTTP_ONLY_ATTRIBUTE)); + String domain = httpCookie.getDomain(); + if (domain != null) + domain = domain.toLowerCase(Locale.ENGLISH); + return Objects.hash(httpCookie.getName(), domain, httpCookie.getPath()); } /** - * @return the attributes associated with this cookie + *Implementation of {@link Object#equals(Object)} compatible with RFC 6265.
+ *Two cookies are equal if they have the same name (case-sensitive), the same + * domain (case-insensitive) and the same path (case-sensitive).
+ * + * @param cookie1 the first cookie to equal + * @param obj the second cookie to equal + * @return whether the cookies are equal + * @see #hashCode(HttpCookie) */ - MapgetAttributes(); + static boolean equals(HttpCookie cookie1, Object obj) + { + if (cookie1 == obj) + return true; + if (cookie1 == null || obj == null) + return false; + if (!(obj instanceof HttpCookie cookie2)) + return false; + // RFC 2965 section. 3.3.3 and RFC 6265 section 4.1.2. + // Names are case-sensitive. + if (!Objects.equals(cookie1.getName(), cookie2.getName())) + return false; + // Domains are case-insensitive. + if (!equalsIgnoreCase(cookie1.getDomain(), cookie2.getDomain())) + return false; + // Paths are case-sensitive. + return Objects.equals(cookie1.getPath(), cookie2.getPath()); + } - /** - * @return a string representation of this cookie - */ - default String asString() + private static boolean equalsIgnoreCase(String obj1, String obj2) { - return HttpCookie.asString(this); + if (obj1 == obj2) + return true; + if (obj1 == null || obj2 == null) + return false; + return obj1.equalsIgnoreCase(obj2); } /** - * @return a string representation of this cookie + * Formats this cookie into a string suitable to be used + * in {@code Cookie} or {@code Set-Cookie} headers.
+ * + * @param httpCookie the cookie to format + * @return a header string representation of the cookie */ static String asString(HttpCookie httpCookie) { @@ -264,478 +562,242 @@ static String asString(HttpCookie httpCookie) return builder.toString(); } + /** + *Formats this cookie into a string suitable to be used + * for logging.
+ * + * @param httpCookie the cookie to format + * @return a logging string representation of the cookie + */ static String toString(HttpCookie httpCookie) { - return "%x@%s".formatted(httpCookie.hashCode(), asString(httpCookie)); + return "%s@%x[%s]".formatted(httpCookie.getClass().getSimpleName(), httpCookie.hashCode(), asString(httpCookie)); } - /** - * Immutable implementation of HttpCookie. - */ - class Immutable implements HttpCookie + class JavaNetHttpCookie implements HttpCookie { - private final String _name; - private final String _value; - private final int _version; - private final Map_attributes; + private final java.net.HttpCookie _httpCookie; + private Map _attributes; - Immutable(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite, Map attributes) + private JavaNetHttpCookie(java.net.HttpCookie httpCookie) { - _name = name; - _value = value; - _version = version; - Map attrs = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - if (attributes != null) - attrs.putAll(attributes); - attrs.put(DOMAIN_ATTRIBUTE, domain); - attrs.put(PATH_ATTRIBUTE, path); - attrs.put(MAX_AGE_ATTRIBUTE, Long.toString(maxAge)); - attrs.put(HTTP_ONLY_ATTRIBUTE, Boolean.toString(httpOnly)); - attrs.put(SECURE_ATTRIBUTE, Boolean.toString(secure)); - attrs.put(COMMENT_ATTRIBUTE, comment); - attrs.put(SAME_SITE_ATTRIBUTE, sameSite == null ? null : sameSite.getAttributeValue()); - _attributes = Collections.unmodifiableMap(attrs); + _httpCookie = httpCookie; } - Immutable(String name, String value, int version, Map attributes) + @Override + public String getComment() { - _name = name; - _value = value; - _version = version; - _attributes = attributes == null ? Collections.emptyMap() : attributes; + return _httpCookie.getComment(); + } + + @Override + public String getDomain() + { + return _httpCookie.getDomain(); + } + + @Override + public long getMaxAge() + { + return _httpCookie.getMaxAge(); + } + + @Override + public String getPath() + { + return _httpCookie.getPath(); + } + + @Override + public boolean isSecure() + { + return _httpCookie.getSecure(); } - /** - * @return the cookie name - */ @Override public String getName() { - return _name; + return _httpCookie.getName(); } - /** - * @return the cookie value - */ @Override public String getValue() { - return _value; + return _httpCookie.getValue(); } - /** - * @return the cookie version - */ @Override public int getVersion() { - return _version; + return _httpCookie.getVersion(); } - /** - * @return the cookie {@code SameSite} attribute value - */ @Override - public SameSite getSameSite() + public boolean isHttpOnly() { - String val = _attributes.get(SAME_SITE_ATTRIBUTE); - if (val == null) - return null; - return SameSite.valueOf(val.toUpperCase(Locale.ENGLISH)); + return _httpCookie.isHttpOnly(); } - /** - * @return the attributes associated with this cookie - */ @Override public Map getAttributes() { + if (_attributes == null) + { + Map attributes = lazyAttributePut(null, COMMENT_ATTRIBUTE, getComment()); + attributes = lazyAttributePut(attributes, DOMAIN_ATTRIBUTE, getDomain()); + if (isHttpOnly()) + attributes = lazyAttributePut(attributes, HTTP_ONLY_ATTRIBUTE, Boolean.TRUE.toString()); + if (getMaxAge() >= 0) + attributes = lazyAttributePut(attributes, MAX_AGE_ATTRIBUTE, Long.toString(getMaxAge())); + attributes = lazyAttributePut(attributes, PATH_ATTRIBUTE, getPath()); + if (isSecure()) + attributes = lazyAttributePut(attributes, SECURE_ATTRIBUTE, Boolean.TRUE.toString()); + _attributes = HttpCookie.lazyAttributes(attributes); + } return _attributes; } @Override - public String toString() + public int hashCode() { - return "%x@%s".formatted(hashCode(), asString()); + return HttpCookie.hashCode(this); } - } - private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote) - { - if (quote) - QuotedStringTokenizer.quoteOnly(buf, s); - else - buf.append(s); + @Override + public boolean equals(Object obj) + { + return HttpCookie.equals(this, obj); + } + + @Override + public String toString() + { + return HttpCookie.toString(this); + } } /** - * Does a cookie value need to be quoted? - * - * @param s value string - * @return true if quoted; - * @throws IllegalArgumentException If there is a String contains unexpected / illegal characters + * A HttpCookie Builder + * @see HttpCookie#build(String, String) */ - private static boolean isQuoteNeededForCookie(String s) + class Builder { - if (s == null || s.length() == 0) - return true; - - if (QuotedStringTokenizer.isQuoted(s)) - return false; + private final String _name; + private final String _value; + private final int _version; + private Map _attributes; - for (int i = 0; i < s.length(); i++) + private Builder(String name, String value) { - char c = s.charAt(i); - if (__COOKIE_DELIM.indexOf(c) >= 0) - return true; - - if (c < 0x20 || c >= 0x7f) - throw new IllegalArgumentException("Illegal character in cookie value"); + this(name, value, 0); } - return false; - } - - static String getSetCookie(HttpCookie httpCookie, CookieCompliance compliance) - { - if (compliance == CookieCompliance.RFC6265) - return getRFC6265SetCookie(httpCookie); - if (compliance == CookieCompliance.RFC2965) - return getRFC2965SetCookie(httpCookie); - throw new IllegalStateException(); - } - - static String getRFC2965SetCookie(HttpCookie httpCookie) - { - // Check arguments - String name = httpCookie.getName(); - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Bad cookie name"); - - // Format value and params - StringBuilder buf = new StringBuilder(); - - // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting - boolean quoteName = isQuoteNeededForCookie(name); - quoteOnlyOrAppend(buf, name, quoteName); - - buf.append('='); - - // Append the value - String value = httpCookie.getValue(); - boolean quoteValue = isQuoteNeededForCookie(value); - quoteOnlyOrAppend(buf, value, quoteValue); - - // 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 quoteDomain = hasDomain && isQuoteNeededForCookie(domain); - - String path = httpCookie.getPath(); - boolean hasPath = path != null && path.length() > 0; - boolean quotePath = hasPath && isQuoteNeededForCookie(path); - - // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted - int version = httpCookie.getVersion(); - String comment = httpCookie.getComment(); - if (version == 0 && (comment != null || quoteName || quoteValue || quoteDomain || quotePath || - QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) || - QuotedStringTokenizer.isQuoted(path) || QuotedStringTokenizer.isQuoted(domain))) - version = 1; - - // Append version - if (version == 1) - buf.append(";Version=1"); - else if (version > 1) - buf.append(";Version=").append(version); - - // Append path - if (hasPath) + private Builder(String name, String value, int version) { - buf.append(";Path="); - quoteOnlyOrAppend(buf, path, quotePath); + _name = name; + _value = value; + _version = version; } - // Append domain - if (hasDomain) + public Builder attribute(String name, String value) { - buf.append(";Domain="); - quoteOnlyOrAppend(buf, domain, quoteDomain); + _attributes = lazyAttributePut(_attributes, name, value); + return this; } - // Handle max-age and/or expires - long maxAge = httpCookie.getMaxAge(); - if (maxAge >= 0) + public Builder comment(String comment) { - // Always use expires - // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies - buf.append(";Expires="); - if (maxAge == 0) - buf.append(__01Jan1970_COOKIE); - else - DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); - - buf.append(";Max-Age="); - buf.append(maxAge); + _attributes = lazyAttributePut(_attributes, COMMENT_ATTRIBUTE, comment); + return this; } - // add the other fields - if (httpCookie.isSecure()) - buf.append(";Secure"); - if (httpCookie.isHttpOnly()) - buf.append(";HttpOnly"); - if (comment != null) + public Builder domain(String domain) { - buf.append(";Comment="); - quoteOnlyOrAppend(buf, comment, isQuoteNeededForCookie(comment)); + _attributes = lazyAttributePut(_attributes, DOMAIN_ATTRIBUTE, domain); + return this; } - return buf.toString(); - } - - static String getRFC6265SetCookie(HttpCookie httpCookie) - { - // Check arguments - String name = httpCookie.getName(); - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Bad cookie name"); - - // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting - // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules - Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name"); - // Ensure that Per RFC6265, Cookie.value follows syntax rules - String value = httpCookie.getValue(); - Syntax.requireValidRFC6265CookieValue(value); - // Format value and params - StringBuilder buf = new StringBuilder(); - buf.append(name).append('=').append(value == null ? "" : value); - - // Append path - String path = httpCookie.getPath(); - if (path != null && path.length() > 0) - buf.append("; Path=").append(path); - - // Append domain - String domain = httpCookie.getDomain(); - if (domain != null && domain.length() > 0) - buf.append("; Domain=").append(domain); - - // Handle max-age and/or expires - long maxAge = httpCookie.getMaxAge(); - if (maxAge >= 0) - { - // Always use expires - // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies - buf.append("; Expires="); - if (maxAge == 0) - buf.append(__01Jan1970_COOKIE); + public Builder httpOnly(boolean httpOnly) + { + if (httpOnly) + _attributes = lazyAttributePut(_attributes, HTTP_ONLY_ATTRIBUTE, Boolean.TRUE.toString()); else - DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); - - buf.append("; Max-Age="); - buf.append(maxAge); + _attributes = lazyAttributeRemove(_attributes, HTTP_ONLY_ATTRIBUTE); + return this; } - // add the other fields - if (httpCookie.isSecure()) - buf.append("; Secure"); - if (httpCookie.isHttpOnly()) - buf.append("; HttpOnly"); - - Map attributes = httpCookie.getAttributes(); - - String sameSiteAttr = attributes.get(SAME_SITE_ATTRIBUTE); - if (sameSiteAttr != null) - { - buf.append("; SameSite="); - buf.append(sameSiteAttr); - } - else + public Builder maxAge(long maxAge) { - SameSite sameSite = httpCookie.getSameSite(); - if (sameSite != null) - { - buf.append("; SameSite="); - buf.append(sameSite.getAttributeValue()); - } + if (maxAge >= 0) + _attributes = lazyAttributePut(_attributes, MAX_AGE_ATTRIBUTE, Long.toString(maxAge)); + else + _attributes = lazyAttributeRemove(_attributes, MAX_AGE_ATTRIBUTE); + return this; } - //Add all other attributes - for (Map.Entry e : attributes.entrySet()) + public Builder expires(Instant expires) { - if (KNOWN_ATTRIBUTES.contains(e.getKey())) - continue; - buf.append("; ").append(e.getKey()).append("="); - buf.append(e.getValue()); + if (expires != null) + _attributes = lazyAttributePut(_attributes, EXPIRES_ATTRIBUTE, formatExpires(expires)); + else + _attributes = lazyAttributeRemove(_attributes, EXPIRES_ATTRIBUTE); + return this; } - return buf.toString(); - } - - /** - * Get the default value for SameSite cookie attribute, if one - * has been set for the given context. - * - * @param contextAttributes the context to check for default SameSite value - * @return the default SameSite value or null if one does not exist - * @throws IllegalStateException if the default value is not a permitted value - */ - static SameSite getSameSiteDefault(Attributes contextAttributes) - { - if (contextAttributes == null) - return null; - Object o = contextAttributes.getAttribute(SAME_SITE_DEFAULT_ATTRIBUTE); - if (o == null) + public Builder path(String path) { - if (LOG.isDebugEnabled()) - LOG.debug("No default value for SameSite"); - return null; + _attributes = lazyAttributePut(_attributes, PATH_ATTRIBUTE, path); + return this; } - if (o instanceof SameSite) - return (SameSite)o; - - try + public Builder secure(boolean secure) { - SameSite samesite = Enum.valueOf(SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH)); - contextAttributes.setAttribute(SAME_SITE_DEFAULT_ATTRIBUTE, samesite); - return samesite; + if (secure) + _attributes = lazyAttributePut(_attributes, SECURE_ATTRIBUTE, Boolean.TRUE.toString()); + else + _attributes = lazyAttributeRemove(_attributes, SECURE_ATTRIBUTE); + return this; } - catch (Exception e) + + public HttpCookie build() { - LOG.warn("Bad default value {} for SameSite", o); - throw new IllegalStateException(e); + return new Immutable(_name, _value, _version, lazyAttributes(_attributes)); } } - /** - * Extract the bare minimum of info from a Set-Cookie header string. - * - * - * Ideally this method should not be necessary, however as java.net.HttpCookie - * does not yet support generic attributes, we have to use it in a minimal - * fashion. When it supports attributes, we could look at reverting to a - * constructor on o.e.j.h.HttpCookie to take the set-cookie header string. - *
- * - * @param setCookieHeader the header as a string - * @return a map containing the name, value, domain, path. max-age of the set cookie header - */ - static MapextractBasics(String setCookieHeader) - { - //Parse the bare minimum - List cookies = java.net.HttpCookie.parse(setCookieHeader); - if (cookies.size() != 1) - return Collections.emptyMap(); - java.net.HttpCookie cookie = cookies.get(0); - Map fields = new HashMap<>(); - fields.put("name", cookie.getName()); - fields.put("value", cookie.getValue()); - fields.put("domain", cookie.getDomain()); - fields.put("path", cookie.getPath()); - fields.put("max-age", Long.toString(cookie.getMaxAge())); - return fields; - } - - /** - * Check if the Set-Cookie header represented as a string is for the name, domain and path given. - * - * @param setCookieHeader a Set-Cookie header - * @param name the cookie name to check - * @param domain the cookie domain to check - * @param path the cookie path to check - * @return true if all of the name, domain and path match the Set-Cookie header, false otherwise - */ - static boolean match(String setCookieHeader, String name, String domain, String path) + private static Map lazyAttributePut(Map attributes, String key, String value) { - //Parse the bare minimum - List cookies = java.net.HttpCookie.parse(setCookieHeader); - if (cookies.size() != 1) - return false; - - java.net.HttpCookie cookie = cookies.get(0); - return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path); + if (value == null) + return attributes; + if (attributes == null) + attributes = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + attributes.put(key, value); + return attributes; } - /** - * Check if the HttpCookie is for the given name, domain and path. - * - * @param cookie the jetty HttpCookie to check - * @param name the cookie name to check - * @param domain the cookie domain to check - * @param path the cookie path to check - * @return true if name, domain, and path, match all match the HttpCookie, false otherwise - */ - static boolean match(HttpCookie cookie, String name, String domain, String path) + private static Map lazyAttributeRemove(Map attributes, String key) { - if (cookie == null) - return false; - return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path); + if (attributes == null) + return null; + attributes.remove(key); + return attributes; } - /** - * Check if all old parameters match the new parameters. - * - * @return true if old and new names match exactly and the old and new domains match case-insensitively and the paths match exactly - */ - private static boolean match(String oldName, String oldDomain, String oldPath, String newName, String newDomain, String newPath) + private static Map lazyAttributes(Map attributes) { - if (oldName == null) - { - if (newName != null) - return false; - } - else if (!oldName.equals(newName)) - return false; - - if (oldDomain == null) - { - if (newDomain != null) - return false; - } - else if (!oldDomain.equalsIgnoreCase(newDomain)) - return false; - - if (oldPath == null) - return newPath == null; - - return oldPath.equals(newPath); + return attributes == null || attributes.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(attributes); } - class SetCookieHttpField extends HttpField + static String formatExpires(Instant expires) { - final HttpCookie _cookie; - - public SetCookieHttpField(HttpCookie cookie, CookieCompliance compliance) - { - super(HttpHeader.SET_COOKIE, getSetCookie(cookie, compliance)); - this._cookie = cookie; - } - - public HttpCookie getHttpCookie() - { - return _cookie; - } + return DateTimeFormatter.RFC_1123_DATE_TIME + .withZone(ZoneOffset.UTC) + .format(expires); } - /** - * Check that samesite is set on the cookie. If not, use a - * context default value, if one has been set. - * - * @param cookie the cookie to check - * @param attributes the context to check settings - * @return either the original cookie, or a new one that has the samesit default set - */ - static HttpCookie checkSameSite(HttpCookie cookie, Attributes attributes) + private static Instant parseExpires(String expires) { - if (cookie == null || cookie.getSameSite() != null) - return cookie; - - //sameSite is not set, use the default configured for the context, if one exists - SameSite contextDefault = HttpCookie.getSameSiteDefault(attributes); - if (contextDefault == null) - return cookie; //no default set - - return HttpCookie.from(cookie, HttpCookie.SAME_SITE_ATTRIBUTE, contextDefault.getAttributeValue()); + // TODO: RFC 1123 format only for now, see https://www.rfc-editor.org/rfc/rfc2616#section-3.3.1. + return ZonedDateTime.parse(expires, DateTimeFormatter.RFC_1123_DATE_TIME).toInstant(); } } diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookieStore.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookieStore.java new file mode 100644 index 000000000000..1eb74b8f0cda --- /dev/null +++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookieStore.java @@ -0,0 +1,383 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 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.http; + +import java.net.URI; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jetty.util.NanoTime; +import org.eclipse.jetty.util.thread.AutoLock; + +/** + * A container for {@link HttpCookie}s.
+ *HTTP cookies are stored along with a {@link URI} via {@link #add(URI, HttpCookie)} + * and retrieved via {@link #match(URI)}, which implements the path matching algorithm + * defined by RFC 6265.
+ */ +public interface HttpCookieStore +{ + /** + *Adds a cookie to this store, if possible.
+ *The cookie may not be added for various reasons; for example, + * it may be already expired, or its domain attribute does not + * match that of the URI, etc.
+ *The cookie is associated with the given {@code URI}, so that + * a call to {@link #match(URI)} returns the cookie only if the + * URIs match.
+ * + * @param uri the {@code URI} associated with the cookie + * @param cookie the cookie to add + * @return whether the cookie has been added + */ + public boolean add(URI uri, HttpCookie cookie); + + /** + * @return all the cookies + */ + public Listall(); + + /** + * Returns the cookies that match the given {@code URI}.
+ * + * @param uri the {@code URI} to match against + * @return a list of cookies that match the given {@code URI} + */ + public Listmatch(URI uri); + + /** + * Removes the cookie associated with the given {@code URI}.
+ * + * @param uri the {@code URI} associated with the cookie to remove + * @param cookie the cookie to remove + * @return whether the cookie has been removed + */ + public boolean remove(URI uri, HttpCookie cookie); + + /** + *Removes all the cookies from this store.
+ * + * @return whether the store modified by this call + */ + public boolean clear(); + + /** + *An implementation of {@link HttpCookieStore} that does not store any cookie.
+ */ + public static class Empty implements HttpCookieStore + { + @Override + public boolean add(URI uri, HttpCookie cookie) + { + return false; + } + + @Override + public Listall() + { + return List.of(); + } + + @Override + public List match(URI uri) + { + return List.of(); + } + + @Override + public boolean remove(URI uri, HttpCookie cookie) + { + return false; + } + + @Override + public boolean clear() + { + return false; + } + } + + /** + * A default implementation of {@link HttpCookieStore}.
+ */ + public static class Default implements HttpCookieStore + { + private final AutoLock lock = new AutoLock(); + private final Map> cookies = new HashMap<>(); + + @Override + public boolean add(URI uri, HttpCookie cookie) + { + // TODO: reject if cookie size is too big? + + String cookieDomain = cookie.getDomain(); + if (cookieDomain != null) + { + cookieDomain = cookieDomain.toLowerCase(Locale.ENGLISH); + if (cookieDomain.startsWith(".")) + cookieDomain = cookieDomain.substring(1); + // RFC 6265 section 4.1.2.3, ignore Domain if ends with ".". + if (cookieDomain.endsWith(".")) + cookieDomain = uri.getHost(); + // Reject top-level domains. + // TODO: should also reject "top" domain such as co.uk, gov.au, etc. + if (!cookieDomain.contains(".")) + { + if (!cookieDomain.equals("localhost")) + return false; + } + + String domain = uri.getHost(); + if (domain != null) + { + domain = domain.toLowerCase(Locale.ENGLISH); + // If uri.host==foo.example.com, only accept + // cookie.domain==(foo.example.com|example.com). + if (!domain.endsWith(cookieDomain)) + return false; + int beforeMatch = domain.length() - cookieDomain.length() - 1; + if (beforeMatch >= 0 && domain.charAt(beforeMatch) != '.') + return false; + } + } + else + { + // No explicit cookie domain, use the origin domain. + cookieDomain = uri.getHost(); + } + + // Cookies are stored under their domain, so that: + // - add(sub.example.com, cookie[Domain]=null) => Key[domain=sub.example.com] + // - add(sub.example.com, cookie[Domain]=example.com) => Key[domain=example.com] + // This facilitates the matching algorithm. + Key key = new Key(uri.getScheme(), cookieDomain); + boolean[] result = new boolean[1]; + try (AutoLock ignored = lock.lock()) + { + cookies.compute(key, (k, v) -> + { + // RFC 6265, section 4.1.2. + // Evict an existing cookie with + // same name, domain and path. + if (v != null) + v.remove(cookie); + + // Add only non-expired cookies. + if (cookie.isExpired()) + { + result[0] = true; + return v == null || v.isEmpty() ? null : v; + } + + if (v == null) + v = new ArrayList<>(); + v.add(new Cookie(cookie)); + return v; + }); + } + + return !result[0]; + } + + @Override + public List all() + { + try (AutoLock ignored = lock.lock()) + { + return cookies.values().stream() + .flatMap(Collection::stream) + .toList(); + } + } + + @Override + public List match(URI uri) + { + List result = new ArrayList<>(); + String scheme = uri.getScheme(); + boolean secure = HttpScheme.HTTPS.is(scheme); + String uriDomain = uri.getHost(); + String path = uri.getPath(); + if (path == null || path.trim().isEmpty()) + path = "/"; + + try (AutoLock ignored = lock.lock()) + { + // Given the way cookies are stored in the Map, the matching + // algorithm starts with the URI domain and iterates chopping + // its subdomains, accumulating the results. + // For example, for uriDomain = sub.example.com, the cookies + // Map is accessed with the following Keys: + // - Key[domain=sub.example.com] + // - chop domain to example.com + // - Key[domain=example.com] + // - chop domain to com + // invalid domain, exit iteration. + String domain = uriDomain; + while (true) + { + Key key = new Key(scheme, domain); + List stored = cookies.get(key); + Iterator iterator = stored == null ? Collections.emptyIterator() : stored.iterator(); + while (iterator.hasNext()) + { + HttpCookie cookie = iterator.next(); + + // Check and remove expired cookies. + if (cookie.isExpired()) + { + iterator.remove(); + continue; + } + + // Check whether the cookie is secure. + if (cookie.isSecure() && !secure) + continue; + + // Match the domain. + if (!domainMatches(uriDomain, key.domain, cookie.getDomain())) + continue; + + // Match the path. + if (!pathMatches(path, cookie.getPath())) + continue; + + result.add(cookie); + } + + int dot = domain.indexOf('.'); + if (dot < 0) + break; + // Remove one subdomain. + domain = domain.substring(dot + 1); + // Exit if the top-level domain was reached. + if (domain.indexOf('.') < 0) + break; + } + } + + return result; + } + + private static boolean domainMatches(String uriDomain, String domain, String cookieDomain) + { + if (uriDomain == null) + return true; + if (domain != null) + domain = domain.toLowerCase(Locale.ENGLISH); + uriDomain = uriDomain.toLowerCase(Locale.ENGLISH); + if (cookieDomain != null) + cookieDomain = cookieDomain.toLowerCase(Locale.ENGLISH); + if (cookieDomain == null || cookieDomain.endsWith(".")) + { + // RFC 6265, section 4.1.2.3. + // No cookie domain -> cookie sent only to origin server. + return uriDomain.equals(domain); + } + if (cookieDomain.startsWith(".")) + cookieDomain = cookieDomain.substring(1); + if (uriDomain.endsWith(cookieDomain)) + { + // The domain is the same as, or a subdomain of, the cookie domain. + int beforeMatch = uriDomain.length() - cookieDomain.length() - 1; + // Domains are the same. + if (beforeMatch == -1) + return true; + // Verify it is a proper subdomain such as bar.foo.com, + // not just a suffix of a domain such as bazfoo.com. + return uriDomain.charAt(beforeMatch) == '.'; + } + return false; + } + + private static boolean pathMatches(String path, String cookiePath) + { + if (cookiePath == null) + return true; + // RFC 6265, section 5.1.4, path matching algorithm. + if (path.equals(cookiePath)) + return true; + if (path.startsWith(cookiePath)) + return cookiePath.endsWith("/") || path.charAt(cookiePath.length()) == '/'; + return false; + } + + @Override + public boolean remove(URI uri, HttpCookie cookie) + { + Key key = new Key(uri.getScheme(), uri.getHost()); + try (AutoLock ignored = lock.lock()) + { + boolean[] result = new boolean[1]; + cookies.compute(key, (k, v) -> + { + if (v == null) + return null; + boolean removed = v.remove(cookie); + result[0] = removed; + return v.isEmpty() ? null : v; + }); + return result[0]; + } + } + + @Override + public boolean clear() + { + try (AutoLock ignored = lock.lock()) + { + if (cookies.isEmpty()) + return false; + cookies.clear(); + return true; + } + } + + private record Key(String scheme, String domain) + { + private Key(String scheme, String domain) + { + this.scheme = scheme; + this.domain = domain.toLowerCase(Locale.ENGLISH); + } + } + + private static class Cookie extends HttpCookie.Wrapper + { + private final long creationNanoTime = NanoTime.now(); + + public Cookie(HttpCookie wrapped) + { + super(wrapped); + } + + @Override + public boolean isExpired() + { + long maxAge = getMaxAge(); + if (maxAge >= 0 && NanoTime.secondsSince(creationNanoTime) > maxAge) + return true; + Instant expires = getExpires(); + return expires != null && Instant.now().isAfter(expires); + } + } + } +} diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieStoreTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieStoreTest.java new file mode 100644 index 000000000000..ac827cf99b61 --- /dev/null +++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieStoreTest.java @@ -0,0 +1,276 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 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.http; + +import java.net.URI; +import java.time.Instant; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class HttpCookieStoreTest +{ + @Test + public void testRejectCookieForTopDomain() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com"); + assertFalse(store.add(uri, HttpCookie.build("n", "v").domain("com").build())); + assertFalse(store.add(uri, HttpCookie.build("n", "v").domain(".com").build())); + } + + @Test + public void testRejectExpiredCookie() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com"); + assertFalse(store.add(uri, HttpCookie.build("n", "v").maxAge(0).build())); + assertFalse(store.add(uri, HttpCookie.build("n", "v").expires(Instant.now().minusSeconds(1)).build())); + } + + @Test + public void testRejectCookieForNonMatchingDomain() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com"); + assertFalse(store.add(uri, HttpCookie.build("n", "v").domain("sub.example.com").build())); + assertFalse(store.add(uri, HttpCookie.build("n", "v").domain("foo.com").build())); + } + + @Test + public void testAcceptCookieForMatchingDomain() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://sub.example.com"); + assertTrue(store.add(uri, HttpCookie.build("n", "v").domain("sub.example.com").build())); + assertTrue(store.add(uri, HttpCookie.build("n", "v").domain("example.com").build())); + } + + @Test + public void testAcceptCookieForLocalhost() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://localhost"); + assertTrue(store.add(uri, HttpCookie.build("n", "v").domain("localhost").build())); + } + + @Test + public void testReplaceCookie() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com"); + assertTrue(store.add(uri, HttpCookie.from("n", "v1"))); + // Replace the cookie with another that has a different value. + assertTrue(store.add(uri, HttpCookie.from("n", "v2"))); + List matches = store.match(uri); + assertEquals(1, matches.size()); + assertEquals("v2", matches.get(0).getValue()); + } + + @Test + public void testReplaceCookieWithDomain() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com"); + assertTrue(store.add(uri, HttpCookie.build("n", "v1").domain("example.com").build())); + // Replace the cookie with another that has a different value. + // Domain comparison must be case-insensitive. + assertTrue(store.add(uri, HttpCookie.build("n", "v2").domain("EXAMPLE.COM").build())); + List matches = store.match(uri); + assertEquals(1, matches.size()); + assertEquals("v2", matches.get(0).getValue()); + } + + @Test + public void testReplaceCookieWithPath() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com/path"); + assertTrue(store.add(uri, HttpCookie.build("n", "v1").path("/path").build())); + // Replace the cookie with another that has a different value. + // Path comparison must be case-sensitive. + assertTrue(store.add(uri, HttpCookie.build("n", "v2").path("/path").build())); + List matches = store.match(uri); + assertEquals(1, matches.size()); + assertEquals("v2", matches.get(0).getValue()); + // Same path but different case should generate another cookie. + assertTrue(store.add(uri, HttpCookie.build("n", "v3").path("/PATH").build())); + matches = store.all(); + assertEquals(2, matches.size()); + } + + @Test + public void testMatch() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://example.com"); + assertTrue(store.add(cookieURI, HttpCookie.from("n", "v1"))); + + // Same domain with a path must match. + URI uri = URI.create("http://example.com/path"); + List matches = store.match(uri); + assertEquals(1, matches.size()); + + // Subdomain must not match because the cookie was added without + // Domain attribute, so must be sent only to the origin domain. + uri = URI.create("http://sub.example.com"); + matches = store.match(uri); + assertEquals(0, matches.size()); + + // Different domain must not match. + uri = URI.create("http://foo.com"); + matches = store.match(uri); + assertEquals(0, matches.size()); + } + + @Test + public void testMatchWithDomain() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://sub.example.com"); + assertTrue(store.add(cookieURI, HttpCookie.build("n", "v1").domain("example.com").build())); + + // Same domain with a path must match. + URI uri = URI.create("http://sub.example.com/path"); + List matches = store.match(uri); + assertEquals(1, matches.size()); + + // Parent domain must match. + uri = URI.create("http://example.com"); + matches = store.match(uri); + assertEquals(1, matches.size()); + + // Different subdomain must match due to the Domain attribute. + uri = URI.create("http://bar.example.com"); + matches = store.match(uri); + assertEquals(1, matches.size()); + } + + @Test + public void testMatchManyWithDomain() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://sub.example.com"); + assertTrue(store.add(cookieURI, HttpCookie.build("n1", "v1").domain("example.com").build())); + cookieURI = URI.create("http://example.com"); + assertTrue(store.add(cookieURI, HttpCookie.from("n2", "v2"))); + + URI uri = URI.create("http://sub.example.com/path"); + List matches = store.match(uri); + assertEquals(1, matches.size()); + + // Parent domain must match. + uri = URI.create("http://example.com"); + matches = store.match(uri); + assertEquals(2, matches.size()); + + // Different subdomain must match due to the Domain attribute. + uri = URI.create("http://bar.example.com"); + matches = store.match(uri); + assertEquals(1, matches.size()); + } + + @Test + public void testMatchManyWithPath() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://example.com"); + assertTrue(store.add(cookieURI, HttpCookie.build("n1", "v1").path("/path").build())); + cookieURI = URI.create("http://example.com"); + assertTrue(store.add(cookieURI, HttpCookie.from("n2", "v2"))); + + URI uri = URI.create("http://example.com"); + List matches = store.match(uri); + assertEquals(1, matches.size()); + + uri = URI.create("http://example.com/"); + matches = store.match(uri); + assertEquals(1, matches.size()); + + uri = URI.create("http://example.com/other"); + matches = store.match(uri); + assertEquals(1, matches.size()); + + uri = URI.create("http://example.com/path"); + matches = store.match(uri); + assertEquals(2, matches.size()); + + uri = URI.create("http://example.com/path/"); + matches = store.match(uri); + assertEquals(2, matches.size()); + + uri = URI.create("http://example.com/path/more"); + matches = store.match(uri); + assertEquals(2, matches.size()); + } + + @Test + public void testExpiredCookieDoesNotMatch() throws Exception + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://example.com"); + long expireSeconds = 1; + assertTrue(store.add(cookieURI, HttpCookie.build("n1", "v1").maxAge(expireSeconds).build())); + + TimeUnit.SECONDS.sleep(2 * expireSeconds); + + List matches = store.match(cookieURI); + assertEquals(0, matches.size()); + } + + @Test + public void testRemove() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://example.com/path"); + assertTrue(store.add(cookieURI, HttpCookie.from("n1", "v1"))); + + URI removeURI = URI.create("http://example.com"); + // Cookie value should not matter. + assertTrue(store.remove(removeURI, HttpCookie.from("n1", "n2"))); + assertFalse(store.remove(removeURI, HttpCookie.from("n1", "n2"))); + } + + @Test + public void testSecureCookie() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI uri = URI.create("http://example.com"); + assertTrue(store.add(uri, HttpCookie.build("n1", "v1").secure(true).build())); + URI secureURI = URI.create("https://example.com"); + assertTrue(store.add(secureURI, HttpCookie.build("n2", "v2").secure(true).build())); + + List matches = store.match(uri); + assertEquals(0, matches.size()); + + matches = store.match(secureURI); + assertEquals(2, matches.size()); + } + + @Test + public void testClear() + { + HttpCookieStore store = new HttpCookieStore.Default(); + URI cookieURI = URI.create("http://example.com"); + assertTrue(store.add(cookieURI, HttpCookie.from("n1", "v1"))); + + assertTrue(store.clear()); + assertFalse(store.clear()); + } +} diff --git a/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java b/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java index cfd72e8b243e..32ad05d818e9 100644 --- a/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java +++ b/jetty-core/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyHandler.java @@ -35,6 +35,7 @@ import org.eclipse.jetty.client.ProtocolHandlers; import org.eclipse.jetty.client.Result; import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; +import org.eclipse.jetty.http.HttpCookieStore; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; @@ -48,7 +49,6 @@ import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; -import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.LifeCycle; @@ -203,7 +203,7 @@ protected HttpClient newHttpClient() protected void configureHttpClient(HttpClient httpClient) { httpClient.setFollowRedirects(false); - httpClient.setCookieStore(new HttpCookieStore.Empty()); + httpClient.setHttpCookieStore(new HttpCookieStore.Empty()); } protected static String requestId(Request clientToProxyRequest) diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpCookieUtils.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpCookieUtils.java new file mode 100644 index 000000000000..3b3eb0124c69 --- /dev/null +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/HttpCookieUtils.java @@ -0,0 +1,436 @@ +// +// ======================================================================== +// Copyright (c) 1995-2022 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.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jetty.http.CookieCompliance; +import org.eclipse.jetty.http.HttpCookie; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.Syntax; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.Index; +import org.eclipse.jetty.util.QuotedStringTokenizer; + +/** + * Utility methods for server-side HTTP cookie handling.
+ */ +public final class HttpCookieUtils +{ + /** + * Name of context attribute with default SameSite cookie value + */ + public static final String SAME_SITE_DEFAULT_ATTRIBUTE = "org.eclipse.jetty.cookie.sameSiteDefault"; + + private static final IndexKNOWN_ATTRIBUTES = new Index.Builder ().caseSensitive(false) + .with(HttpCookie.COMMENT_ATTRIBUTE) + .with(HttpCookie.DOMAIN_ATTRIBUTE) + .with(HttpCookie.EXPIRES_ATTRIBUTE) + .with(HttpCookie.HTTP_ONLY_ATTRIBUTE) + .with(HttpCookie.MAX_AGE_ATTRIBUTE) + .with(HttpCookie.PATH_ATTRIBUTE) + .with(HttpCookie.SAME_SITE_ATTRIBUTE) + .with(HttpCookie.SECURE_ATTRIBUTE) + .build(); + // RFC 1123 format of epoch for the Expires attribute. + private static final String EPOCH_EXPIRES = "Thu, 01 Jan 1970 00:00:00 GMT"; + + /** + * Check that samesite is set on the cookie. If not, use a + * context default value, if one has been set. + * + * @param cookie the cookie to check + * @param attributes the context to check settings + * @return either the original cookie, or a new one that has the samesit default set + */ + public static HttpCookie checkSameSite(HttpCookie cookie, Attributes attributes) + { + if (cookie == null || cookie.getSameSite() != null) + return cookie; + + //sameSite is not set, use the default configured for the context, if one exists + HttpCookie.SameSite contextDefault = getSameSiteDefault(attributes); + if (contextDefault == null) + return cookie; //no default set + + return HttpCookie.from(cookie, HttpCookie.SAME_SITE_ATTRIBUTE, contextDefault.getAttributeValue()); + } + + /** + * Extract the bare minimum of info from a Set-Cookie header string. + * + * + * Ideally this method should not be necessary, however as java.net.HttpCookie + * does not yet support generic attributes, we have to use it in a minimal + * fashion. When it supports attributes, we could look at reverting to a + * constructor on o.e.j.h.HttpCookie to take the set-cookie header string. + *
+ * + * @param setCookieHeader the header as a string + * @return a map containing the name, value, domain, path. max-age of the set cookie header + */ + public static MapextractBasics(String setCookieHeader) + { + //Parse the bare minimum + List cookies = java.net.HttpCookie.parse(setCookieHeader); + if (cookies.size() != 1) + return Collections.emptyMap(); + java.net.HttpCookie cookie = cookies.get(0); + Map fields = new HashMap<>(); + fields.put("name", cookie.getName()); + fields.put("value", cookie.getValue()); + fields.put("domain", cookie.getDomain()); + fields.put("path", cookie.getPath()); + fields.put("max-age", Long.toString(cookie.getMaxAge())); + return fields; + } + + /** + * Get the default value for SameSite cookie attribute, if one + * has been set for the given context. + * + * @param contextAttributes the context to check for default SameSite value + * @return the default SameSite value or null if one does not exist + * @throws IllegalStateException if the default value is not a permitted value + */ + public static HttpCookie.SameSite getSameSiteDefault(Attributes contextAttributes) + { + if (contextAttributes == null) + return null; + Object o = contextAttributes.getAttribute(SAME_SITE_DEFAULT_ATTRIBUTE); + if (o == null) + return null; + + if (o instanceof HttpCookie.SameSite) + return (HttpCookie.SameSite)o; + + try + { + HttpCookie.SameSite samesite = Enum.valueOf(HttpCookie.SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH)); + contextAttributes.setAttribute(SAME_SITE_DEFAULT_ATTRIBUTE, samesite); + return samesite; + } + catch (Exception e) + { + throw new IllegalStateException(e); + } + } + + public static String getSetCookie(HttpCookie httpCookie, CookieCompliance compliance) + { + if (compliance == CookieCompliance.RFC6265) + return getRFC6265SetCookie(httpCookie); + if (compliance == CookieCompliance.RFC2965) + return getRFC2965SetCookie(httpCookie); + throw new IllegalStateException(); + } + + public static String getRFC2965SetCookie(HttpCookie httpCookie) + { + // Check arguments + String name = httpCookie.getName(); + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Invalid cookie name"); + + StringBuilder builder = new StringBuilder(); + + quoteIfNeededAndAppend(name, builder); + + builder.append('='); + + String value = httpCookie.getValue(); + quoteIfNeededAndAppend(value, builder); + + // 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 quoteDomain = hasDomain && isQuoteNeeded(domain); + + String path = httpCookie.getPath(); + boolean hasPath = path != null && path.length() > 0; + 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 + int version = httpCookie.getVersion(); + String comment = httpCookie.getComment(); + if (version == 0 && (comment != null || isQuoteNeeded(name) || isQuoteNeeded(value) || quoteDomain || quotePath || + QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) || + QuotedStringTokenizer.isQuoted(path) || QuotedStringTokenizer.isQuoted(domain))) + version = 1; + + if (version == 1) + builder.append(";Version=1"); + else if (version > 1) + builder.append(";Version=").append(version); + + if (hasDomain) + { + builder.append(";Domain="); + if (quoteDomain) + QuotedStringTokenizer.quoteOnly(builder, domain); + else + builder.append(domain); + } + + if (hasPath) + { + builder.append(";Path="); + if (quotePath) + QuotedStringTokenizer.quoteOnly(builder, path); + else + builder.append(path); + } + + // Handle max-age and/or expires + long maxAge = httpCookie.getMaxAge(); + if (maxAge >= 0) + { + // Always add the Expires attribute too, as some + // browsers do not handle max-age even with v1 cookies. + builder.append(";Expires="); + if (maxAge == 0) + builder.append(EPOCH_EXPIRES); + else + builder.append(HttpCookie.formatExpires(Instant.now().plusSeconds(maxAge))); + + builder.append(";Max-Age="); + builder.append(maxAge); + } + + if (httpCookie.isSecure()) + builder.append(";Secure"); + + if (httpCookie.isHttpOnly()) + builder.append(";HttpOnly"); + + HttpCookie.SameSite sameSite = httpCookie.getSameSite(); + if (sameSite != null) + builder.append(";SameSite=").append(sameSite.getAttributeValue()); + + if (comment != null) + { + builder.append(";Comment="); + quoteIfNeededAndAppend(comment, builder); + } + + return builder.toString(); + } + + public static String getRFC6265SetCookie(HttpCookie httpCookie) + { + // Check arguments + String name = httpCookie.getName(); + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Bad cookie name"); + + // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting + // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules + Syntax.requireValidRFC2616Token(name, "RFC6265 Cookie name"); + // Ensure that Per RFC6265, Cookie.value follows syntax rules + String value = httpCookie.getValue(); + Syntax.requireValidRFC6265CookieValue(value); + + // Format value and params + StringBuilder builder = new StringBuilder(); + builder.append(name).append('=').append(value == null ? "" : value); + + // Append path + String path = httpCookie.getPath(); + if (path != null && path.length() > 0) + builder.append("; Path=").append(path); + + // Append domain + String domain = httpCookie.getDomain(); + if (domain != null && domain.length() > 0) + builder.append("; Domain=").append(domain); + + // Handle max-age and/or expires + long maxAge = httpCookie.getMaxAge(); + if (maxAge >= 0) + { + // Always use expires + // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies + builder.append("; Expires="); + if (maxAge == 0) + builder.append(EPOCH_EXPIRES); + else + builder.append(HttpCookie.formatExpires(Instant.now().plusSeconds(maxAge))); + + builder.append("; Max-Age="); + builder.append(maxAge); + } + + // add the other fields + if (httpCookie.isSecure()) + builder.append("; Secure"); + if (httpCookie.isHttpOnly()) + builder.append("; HttpOnly"); + + Map attributes = httpCookie.getAttributes(); + + String sameSiteAttr = attributes.get(HttpCookie.SAME_SITE_ATTRIBUTE); + if (sameSiteAttr != null) + { + builder.append("; SameSite="); + builder.append(sameSiteAttr); + } + else + { + HttpCookie.SameSite sameSite = httpCookie.getSameSite(); + if (sameSite != null) + { + builder.append("; SameSite="); + builder.append(sameSite.getAttributeValue()); + } + } + + //Add all other attributes + for (Map.Entry e : attributes.entrySet()) + { + if (KNOWN_ATTRIBUTES.contains(e.getKey())) + continue; + builder.append("; ").append(e.getKey()).append("="); + builder.append(e.getValue()); + } + + return builder.toString(); + } + + /** + * Whether a cookie name/value/attribute needs to be quoted.
+ * + * @param text the text to check + * @return whether the text needs to be quoted + * @throws IllegalArgumentException if the text contains illegal characters + */ + private static boolean isQuoteNeeded(String text) + { + if (text == null || text.length() == 0) + return true; + + if (QuotedStringTokenizer.isQuoted(text)) + return false; + + for (int i = 0; i < text.length(); i++) + { + char c = text.charAt(i); + if ("\",;\\ \t".indexOf(c) >= 0) + return true; + + if (c < 0x20 || c >= 0x7F) + throw new IllegalArgumentException("Illegal character in cookie value"); + } + + return false; + } + + /** + * Check if the Set-Cookie header represented as a string is for the name, domain and path given. + * + * @param setCookieHeader a Set-Cookie header + * @param name the cookie name to check + * @param domain the cookie domain to check + * @param path the cookie path to check + * @return true if all of the name, domain and path match the Set-Cookie header, false otherwise + */ + public static boolean match(String setCookieHeader, String name, String domain, String path) + { + //Parse the bare minimum + Listcookies = java.net.HttpCookie.parse(setCookieHeader); + if (cookies.size() != 1) + return false; + + java.net.HttpCookie cookie = cookies.get(0); + return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path); + } + + /** + * Check if the HttpCookie is for the given name, domain and path. + * + * @param cookie the jetty HttpCookie to check + * @param name the cookie name to check + * @param domain the cookie domain to check + * @param path the cookie path to check + * @return true if name, domain, and path, match all match the HttpCookie, false otherwise + */ + public static boolean match(HttpCookie cookie, String name, String domain, String path) + { + if (cookie == null) + return false; + return match(cookie.getName(), cookie.getDomain(), cookie.getPath(), name, domain, path); + } + + /** + * Check if all old parameters match the new parameters. + * + * @return true if old and new names match exactly and the old and new domains match case-insensitively and the paths match exactly + */ + private static boolean match(String oldName, String oldDomain, String oldPath, String newName, String newDomain, String newPath) + { + if (oldName == null) + { + if (newName != null) + return false; + } + else if (!oldName.equals(newName)) + return false; + + if (oldDomain == null) + { + if (newDomain != null) + return false; + } + else if (!oldDomain.equalsIgnoreCase(newDomain)) + return false; + + if (oldPath == null) + return newPath == null; + + return oldPath.equals(newPath); + } + + private static void quoteIfNeededAndAppend(String text, StringBuilder builder) + { + if (isQuoteNeeded(text)) + QuotedStringTokenizer.quoteOnly(builder, text); + else + builder.append(text); + } + + private HttpCookieUtils() + { + } + + public static class SetCookieHttpField extends HttpField + { + private final HttpCookie _cookie; + + public SetCookieHttpField(HttpCookie cookie, CookieCompliance compliance) + { + super(HttpHeader.SET_COOKIE, getSetCookie(cookie, compliance)); + this._cookie = cookie; + } + + public HttpCookie getHttpCookie() + { + return _cookie; + } + } +} diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 53f3f19e588e..3df2552217e7 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -180,7 +180,7 @@ static void addCookie(Response response, HttpCookie cookie) throw new IllegalArgumentException("Cookie.name cannot be blank/null"); Request request = response.getRequest(); - response.getHeaders().add(new HttpCookie.SetCookieHttpField(HttpCookie.checkSameSite(cookie, request.getContext()), + response.getHeaders().add(new HttpCookieUtils.SetCookieHttpField(HttpCookieUtils.checkSameSite(cookie, request.getContext()), request.getConnectionMetaData().getHttpConfiguration().getResponseCookieCompliance())); // Expire responses with set-cookie headers so they do not get cached. @@ -202,18 +202,18 @@ static void replaceCookie(Response response, HttpCookie cookie) if (field.getHeader() == HttpHeader.SET_COOKIE) { CookieCompliance compliance = httpConfiguration.getResponseCookieCompliance(); - if (field instanceof HttpCookie.SetCookieHttpField) + if (field instanceof HttpCookieUtils.SetCookieHttpField) { - if (!HttpCookie.match(((HttpCookie.SetCookieHttpField)field).getHttpCookie(), cookie.getName(), cookie.getDomain(), cookie.getPath())) + if (!HttpCookieUtils.match(((HttpCookieUtils.SetCookieHttpField)field).getHttpCookie(), cookie.getName(), cookie.getDomain(), cookie.getPath())) continue; } else { - if (!HttpCookie.match(field.getValue(), cookie.getName(), cookie.getDomain(), cookie.getPath())) + if (!HttpCookieUtils.match(field.getValue(), cookie.getName(), cookie.getDomain(), cookie.getPath())) continue; } - i.set(new HttpCookie.SetCookieHttpField(HttpCookie.checkSameSite(cookie, request.getContext()), compliance)); + i.set(new HttpCookieUtils.SetCookieHttpField(HttpCookieUtils.checkSameSite(cookie, request.getContext()), compliance)); return; } } diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpCookieTest.java similarity index 63% rename from jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieTest.java rename to jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpCookieTest.java index 33a8418e9b3b..739c1d4880a5 100644 --- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieTest.java +++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/HttpCookieTest.java @@ -11,11 +11,12 @@ // ======================================================================== // -package org.eclipse.jetty.http; +package org.eclipse.jetty.server; import java.util.Map; import java.util.stream.Stream; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpCookie.SameSite; import org.eclipse.jetty.util.AttributesMap; import org.hamcrest.Matchers; @@ -25,7 +26,11 @@ import static org.hamcrest.MatcherAssert.assertThat; 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.equalTo; +import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -40,49 +45,49 @@ public void testDefaultSameSite() AttributesMap context = new AttributesMap(); //test null value for default - assertNull(HttpCookie.getSameSiteDefault(context)); + assertNull(HttpCookieUtils.getSameSiteDefault(context)); //test good value for default as SameSite enum - context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, SameSite.LAX); - assertEquals(SameSite.LAX, HttpCookie.getSameSiteDefault(context)); + context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, SameSite.LAX); + assertEquals(SameSite.LAX, HttpCookieUtils.getSameSiteDefault(context)); //test good value for default as String - context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "NONE"); - assertEquals(SameSite.NONE, HttpCookie.getSameSiteDefault(context)); + context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, "NONE"); + assertEquals(SameSite.NONE, HttpCookieUtils.getSameSiteDefault(context)); //test case for default as String - context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "sTrIcT"); - assertEquals(SameSite.STRICT, HttpCookie.getSameSiteDefault(context)); + context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, "sTrIcT"); + assertEquals(SameSite.STRICT, HttpCookieUtils.getSameSiteDefault(context)); //test bad value for default as String - context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "fooBAR"); + context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, "fooBAR"); assertThrows(IllegalStateException.class, - () -> HttpCookie.getSameSiteDefault(context)); + () -> HttpCookieUtils.getSameSiteDefault(context)); } @Test public void testMatchCookie() { //match with header string - assertTrue(HttpCookie.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", + assertTrue(HttpCookieUtils.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", "everything", "domain", "path")); - assertFalse(HttpCookie.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", + assertFalse(HttpCookieUtils.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", "something", "domain", "path")); - assertFalse(HttpCookie.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", + assertFalse(HttpCookieUtils.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", "everything", "realm", "path")); - assertFalse(HttpCookie.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", + assertFalse(HttpCookieUtils.match("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", "everything", "domain", "street")); //match including set-cookie:, this is really testing the java.net.HttpCookie parser, but worth throwing in there - assertTrue(HttpCookie.match("Set-Cookie: everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", + assertTrue(HttpCookieUtils.match("Set-Cookie: everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax; Foo=Bar", "everything", "domain", "path")); //match via cookie HttpCookie httpCookie = HttpCookie.from("everything", "value", 0, 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.COMMENT_ATTRIBUTE, "comment")); - assertTrue(HttpCookie.match(httpCookie, "everything", "domain", "path")); - assertFalse(HttpCookie.match(httpCookie, "something", "domain", "path")); - assertFalse(HttpCookie.match(httpCookie, "everything", "realm", "path")); - assertFalse(HttpCookie.match(httpCookie, "everything", "domain", "street")); + assertTrue(HttpCookieUtils.match(httpCookie, "everything", "domain", "path")); + assertFalse(HttpCookieUtils.match(httpCookie, "something", "domain", "path")); + assertFalse(HttpCookieUtils.match(httpCookie, "everything", "realm", "path")); + assertFalse(HttpCookieUtils.match(httpCookie, "everything", "domain", "street")); } @Test @@ -91,33 +96,33 @@ public void testSetRFC2965Cookie() throws Exception HttpCookie httpCookie; httpCookie = HttpCookie.from("null", null, -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - assertEquals("null=", HttpCookie.getRFC2965SetCookie(httpCookie)); + assertEquals("null=", HttpCookieUtils.getRFC2965SetCookie(httpCookie)); httpCookie = HttpCookie.from("minimal", "value", -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - assertEquals("minimal=value", HttpCookie.getRFC2965SetCookie(httpCookie)); + assertEquals("minimal=value", HttpCookieUtils.getRFC2965SetCookie(httpCookie)); httpCookie = HttpCookie.from("everything", "something", 0, 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.COMMENT_ATTRIBUTE, "noncomment")); - assertEquals("everything=something;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=noncomment", HttpCookie.getRFC2965SetCookie(httpCookie)); + assertEquals("everything=something;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=noncomment", HttpCookieUtils.getRFC2965SetCookie(httpCookie)); httpCookie = HttpCookie.from("everything", "value", 0, 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.COMMENT_ATTRIBUTE, "comment")); - assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment", HttpCookie.getRFC2965SetCookie(httpCookie)); + assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment", HttpCookieUtils.getRFC2965SetCookie(httpCookie)); httpCookie = HttpCookie.from("ev erything", "va lue", 1, Map.of(HttpCookie.DOMAIN_ATTRIBUTE, "do main", HttpCookie.PATH_ATTRIBUTE, "pa th", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true), HttpCookie.COMMENT_ATTRIBUTE, "co mment")); - String setCookie = HttpCookie.getRFC2965SetCookie(httpCookie); + String setCookie = HttpCookieUtils.getRFC2965SetCookie(httpCookie); assertThat(setCookie, Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires=")); assertThat(setCookie, Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\"")); httpCookie = HttpCookie.from("name", "value", 0, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - setCookie = HttpCookie.getRFC2965SetCookie(httpCookie); + setCookie = HttpCookieUtils.getRFC2965SetCookie(httpCookie); assertEquals(-1, setCookie.indexOf("Version=")); httpCookie = HttpCookie.from("name", "v a l u e", 0, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - setCookie = HttpCookie.getRFC2965SetCookie(httpCookie); + setCookie = HttpCookieUtils.getRFC2965SetCookie(httpCookie); httpCookie = HttpCookie.from("json", "{\"services\":[\"cwa\", \"aa\"]}", -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"", HttpCookie.getRFC2965SetCookie(httpCookie)); + assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"", HttpCookieUtils.getRFC2965SetCookie(httpCookie)); httpCookie = HttpCookie.from("name", "value%=", 0, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - setCookie = HttpCookie.getRFC2965SetCookie(httpCookie); + setCookie = HttpCookieUtils.getRFC2965SetCookie(httpCookie); assertEquals("name=value%=", setCookie); } @@ -127,26 +132,26 @@ public void testSetRFC6265Cookie() throws Exception HttpCookie httpCookie; httpCookie = HttpCookie.from("null", null, -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - assertEquals("null=", HttpCookie.getRFC6265SetCookie(httpCookie)); + assertEquals("null=", HttpCookieUtils.getRFC6265SetCookie(httpCookie)); httpCookie = HttpCookie.from("minimal", "value", -1, Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(-1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(false), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(false))); - assertEquals("minimal=value", HttpCookie.getRFC6265SetCookie(httpCookie)); + assertEquals("minimal=value", HttpCookieUtils.getRFC6265SetCookie(httpCookie)); //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", HttpCookie.getRFC6265SetCookie(httpCookie)); + assertEquals("everything=something; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; 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", HttpCookie.getRFC6265SetCookie(httpCookie)); + assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; 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", HttpCookie.getRFC6265SetCookie(httpCookie)); + 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)); 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", HttpCookie.getRFC6265SetCookie(httpCookie)); + 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)); 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", HttpCookie.getRFC6265SetCookie(httpCookie)); + 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)); } public static Stream rfc6265BadNameSource() @@ -172,7 +177,7 @@ public void testSetRFC6265CookieBadName(String badNameExample) () -> { HttpCookie httpCookie = HttpCookie.from(badNameExample, "value", -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true))); - HttpCookie.getRFC6265SetCookie(httpCookie); + HttpCookieUtils.getRFC6265SetCookie(httpCookie); }); // make sure that exception mentions just how mad of a name it truly is assertThat("message", ex.getMessage(), @@ -209,7 +214,7 @@ public void testSetRFC6265CookieBadValue(String badValueExample) () -> { HttpCookie httpCookie = HttpCookie.from("name", badValueExample, -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true))); - HttpCookie.getRFC6265SetCookie(httpCookie); + HttpCookieUtils.getRFC6265SetCookie(httpCookie); }); assertThat("message", ex.getMessage(), containsString("RFC6265")); } @@ -255,4 +260,106 @@ public void testSetRFC6265CookieGoodValue(String goodValueExample) HttpCookie.from("name", goodValueExample, -1, Map.of(HttpCookie.PATH_ATTRIBUTE, "/", HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1), HttpCookie.HTTP_ONLY_ATTRIBUTE, Boolean.toString(true), HttpCookie.SECURE_ATTRIBUTE, Boolean.toString(true))); // should not throw an exception } + + @Test + public void testBuilderSimple() + { + HttpCookie httpCookie = HttpCookie.build("name", "value").build(); + assertThat(httpCookie.getName(), equalToIgnoringCase("name")); + assertThat(httpCookie.getValue(), equalTo("value")); + assertThat(httpCookie.getVersion(), equalTo(0)); + assertThat(httpCookie.getAttributes(), anEmptyMap()); + } + + @Test + public void testBuilderNull() + { + HttpCookie httpCookie = HttpCookie.build("name", "value") + .attribute(null, null) + .comment(null) + .domain(null) + .httpOnly(false) + .maxAge(-1) + .secure(false) + .path(null) + .build(); + assertThat(httpCookie.getName(), equalToIgnoringCase("name")); + assertThat(httpCookie.getValue(), equalTo("value")); + assertThat(httpCookie.getVersion(), equalTo(0)); + assertThat(httpCookie.getAttributes(), anEmptyMap()); + } + + @Test + public void testBuilderFull() + { + HttpCookie httpCookie = HttpCookie.build("name", "value") + .attribute("some", "value") + .comment("comment") + .domain("domain") + .httpOnly(true) + .maxAge(42) + .secure(true) + .path("/path") + .build(); + assertThat(httpCookie.getName(), equalToIgnoringCase("name")); + assertThat(httpCookie.getValue(), equalTo("value")); + assertThat(httpCookie.getVersion(), equalTo(0)); + assertThat(httpCookie.getAttributes().keySet(), containsInAnyOrder( + "some", + HttpCookie.COMMENT_ATTRIBUTE, + HttpCookie.DOMAIN_ATTRIBUTE, + HttpCookie.HTTP_ONLY_ATTRIBUTE, + HttpCookie.MAX_AGE_ATTRIBUTE, + HttpCookie.SECURE_ATTRIBUTE, + HttpCookie.PATH_ATTRIBUTE)); + assertThat(httpCookie.getAttributes().values(), containsInAnyOrder( + "value", + Boolean.TRUE.toString(), + Boolean.TRUE.toString(), + "comment", + "domain", + "42", + "/path")); + } + + @Test + public void testJavaNetHttpCookie() + { + java.net.HttpCookie cookie = new java.net.HttpCookie("name", "value"); + cookie.setVersion(1); + cookie.setComment("comment"); + cookie.setDomain("domain"); + cookie.setHttpOnly(true); + cookie.setMaxAge(42); + cookie.setPath("/path"); + cookie.setSecure(true); + + HttpCookie httpCookie = HttpCookie.from(cookie); + + assertThat(httpCookie.getName(), equalTo("name")); + assertThat(httpCookie.getValue(), equalTo("value")); + assertThat(httpCookie.getVersion(), equalTo(1)); + assertThat(httpCookie.getDomain(), equalTo("domain")); + assertThat(httpCookie.getMaxAge(), equalTo(42L)); + assertThat(httpCookie.isSecure(), equalTo(true)); + + assertThat(httpCookie.getAttributes().keySet(), containsInAnyOrder( + HttpCookie.COMMENT_ATTRIBUTE, + HttpCookie.DOMAIN_ATTRIBUTE, + HttpCookie.HTTP_ONLY_ATTRIBUTE, + HttpCookie.MAX_AGE_ATTRIBUTE, + HttpCookie.SECURE_ATTRIBUTE, + HttpCookie.PATH_ATTRIBUTE)); + assertThat(httpCookie.getAttributes().values(), containsInAnyOrder( + Boolean.TRUE.toString(), + Boolean.TRUE.toString(), + "comment", + "domain", + "42", + "/path")); + + java.net.HttpCookie cookie2 = HttpCookie.asJavaNetHttpCookie(httpCookie); + assertEquals(cookie, cookie2); + } + } diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/HttpCookieStore.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/HttpCookieStore.java deleted file mode 100644 index d2066cc70c93..000000000000 --- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/HttpCookieStore.java +++ /dev/null @@ -1,142 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2022 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.util; - -import java.net.CookieManager; -import java.net.CookieStore; -import java.net.HttpCookie; -import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Implementation of {@link CookieStore} that delegates to an instance created by {@link CookieManager} - * via {@link CookieManager#getCookieStore()}. - */ -public class HttpCookieStore implements CookieStore -{ - private final CookieStore delegate; - - public HttpCookieStore() - { - delegate = new CookieManager().getCookieStore(); - } - - @Override - public void add(URI uri, HttpCookie cookie) - { - delegate.add(uri, cookie); - } - - @Override - public List get(URI uri) - { - return delegate.get(uri); - } - - @Override - public List getCookies() - { - return delegate.getCookies(); - } - - @Override - public List getURIs() - { - return delegate.getURIs(); - } - - @Override - public boolean remove(URI uri, HttpCookie cookie) - { - return delegate.remove(uri, cookie); - } - - @Override - public boolean removeAll() - { - return delegate.removeAll(); - } - - public static List matchPath(URI uri, List cookies) - { - if (cookies == null || cookies.isEmpty()) - return Collections.emptyList(); - List result = new ArrayList<>(4); - String path = uri.getPath(); - if (path == null || path.trim().isEmpty()) - path = "/"; - for (HttpCookie cookie : cookies) - { - String cookiePath = cookie.getPath(); - if (cookiePath == null) - { - result.add(cookie); - } - else - { - // RFC 6265, section 5.1.4, path matching algorithm. - if (path.equals(cookiePath)) - { - result.add(cookie); - } - else if (path.startsWith(cookiePath)) - { - if (cookiePath.endsWith("/") || path.charAt(cookiePath.length()) == '/') - result.add(cookie); - } - } - } - return result; - } - - public static class Empty implements CookieStore - { - @Override - public void add(URI uri, HttpCookie cookie) - { - } - - @Override - public List get(URI uri) - { - return Collections.emptyList(); - } - - @Override - public List getCookies() - { - return Collections.emptyList(); - } - - @Override - public List getURIs() - { - return Collections.emptyList(); - } - - @Override - public boolean remove(URI uri, HttpCookie cookie) - { - return false; - } - - @Override - public boolean removeAll() - { - return false; - } - } -} diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java b/jetty-core/jetty-websocket/jetty-websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java index e53481e36e7c..bbcca37a8d6f 100644 --- a/jetty-core/jetty-websocket/jetty-websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java +++ b/jetty-core/jetty-websocket/jetty-websocket-core-client/src/main/java/org/eclipse/jetty/websocket/core/client/CoreClientUpgradeRequest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.websocket.core.client; import java.io.IOException; -import java.net.HttpCookie; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -31,6 +30,7 @@ import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; import org.eclipse.jetty.client.Result; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; diff --git a/jetty-ee10/jetty-ee10-proxy/src/main/java/org/eclipse/jetty/ee10/proxy/AbstractProxyServlet.java b/jetty-ee10/jetty-ee10-proxy/src/main/java/org/eclipse/jetty/ee10/proxy/AbstractProxyServlet.java index 17f67e6f49bd..b94661fdf1d1 100644 --- a/jetty-ee10/jetty-ee10-proxy/src/main/java/org/eclipse/jetty/ee10/proxy/AbstractProxyServlet.java +++ b/jetty-ee10/jetty-ee10-proxy/src/main/java/org/eclipse/jetty/ee10/proxy/AbstractProxyServlet.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; +import org.eclipse.jetty.http.HttpCookieStore; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; @@ -47,7 +48,6 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ClientConnector; -import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -276,7 +276,7 @@ protected HttpClient createHttpClient() throws ServletException client.setFollowRedirects(false); // Must not store cookies, otherwise cookies of different clients will mix. - client.setCookieStore(new HttpCookieStore.Empty()); + client.setHttpCookieStore(new HttpCookieStore.Empty()); Executor executor; String value = config.getInitParameter("maxThreads"); diff --git a/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/ProxyServletTest.java b/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/ProxyServletTest.java index 43489b03f831..255b09601b74 100644 --- a/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/ProxyServletTest.java +++ b/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/ProxyServletTest.java @@ -21,7 +21,6 @@ import java.io.PrintWriter; import java.io.Writer; import java.net.ConnectException; -import java.net.HttpCookie; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -76,6 +75,7 @@ import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextRequest; import org.eclipse.jetty.ee10.servlet.ServletHolder; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; @@ -1194,7 +1194,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) .send(); assertEquals(200, response1.getStatus()); assertTrue(response1.getHeaders().contains(PROXIED_HEADER)); - List cookies = client.getCookieStore().getCookies(); + List cookies = client.getHttpCookieStore().all(); assertEquals(1, cookies.size()); assertEquals(name, cookies.get(0).getName()); assertEquals(value1, cookies.get(0).getValue()); @@ -1209,7 +1209,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) .send(); assertEquals(200, response2.getStatus()); assertTrue(response2.getHeaders().contains(PROXIED_HEADER)); - cookies = client2.getCookieStore().getCookies(); + cookies = client2.getHttpCookieStore().all(); assertEquals(1, cookies.size()); assertEquals(name, cookies.get(0).getName()); assertEquals(value2, cookies.get(0).getValue()); diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java index 45f0652af0b8..967b4201bd12 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java @@ -67,6 +67,7 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.server.ConnectionMetaData; import org.eclipse.jetty.server.FormFields; +import org.eclipse.jetty.server.HttpCookieUtils; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Session; import org.eclipse.jetty.session.AbstractSessionManager; @@ -528,7 +529,7 @@ public PushBuilder newPushBuilder() pushCookies.append(cookies); for (String setCookie : setCookies) { - Map cookieFields = HttpCookie.extractBasics(setCookie); + Map cookieFields = HttpCookieUtils.extractBasics(setCookie); String cookieName = cookieFields.get("name"); String cookieValue = cookieFields.get("value"); String cookieMaxAge = cookieFields.get("max-age"); diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiResponse.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiResponse.java index 222a126ce884..ba3f2d5aafd2 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiResponse.java +++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiResponse.java @@ -721,9 +721,15 @@ public Map getAttributes() } @Override - public String asString() + public int hashCode() { - return HttpCookie.asString(this); + return HttpCookie.hashCode(this); + } + + @Override + public boolean equals(Object obj) + { + return HttpCookie.equals(this, obj); } @Override diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/SessionHandlerTest.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/SessionHandlerTest.java index 80a64ec4677a..782fa255ca31 100644 --- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/SessionHandlerTest.java +++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/SessionHandlerTest.java @@ -39,6 +39,7 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.logging.StacklessLogging; +import org.eclipse.jetty.server.HttpCookieUtils; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -494,7 +495,7 @@ public void testSessionCookie() throws Exception assertEquals(99, cookie.getMaxAge()); assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite()); - String cookieStr = HttpCookie.getRFC6265SetCookie(cookie); + String cookieStr = HttpCookieUtils.getRFC6265SetCookie(cookie); assertThat(cookieStr, containsString("; SameSite=Strict; ham=cheese")); } diff --git a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/SessionRenewTest.java b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/SessionRenewTest.java index 442c2e35e3e1..c0c8156fb5c6 100644 --- a/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/SessionRenewTest.java +++ b/jetty-ee10/jetty-ee10-tests/jetty-ee10-test-sessions/jetty-ee10-test-sessions-common/src/test/java/org/eclipse/jetty/ee10/session/SessionRenewTest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee10.session; import java.io.IOException; -import java.net.HttpCookie; import java.util.concurrent.TimeUnit; import jakarta.servlet.ServletException; @@ -29,6 +28,7 @@ import org.eclipse.jetty.client.Request; import org.eclipse.jetty.ee10.servlet.ServletContextHandler; import org.eclipse.jetty.ee10.servlet.ServletContextRequest; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.session.DefaultSessionCache; import org.eclipse.jetty.session.DefaultSessionCacheFactory; import org.eclipse.jetty.session.DefaultSessionIdManager; @@ -172,7 +172,7 @@ public void testSessionRenewalMultiContext() throws Exception //make a request to change the sessionid Request request = client.newRequest("http://localhost:" + port + contextPathA + servletMapping + "?action=renew"); - request.cookie(new HttpCookie(SessionManager.__DefaultSessionCookie, "1234")); + request.cookie(HttpCookie.from(SessionManager.__DefaultSessionCookie, "1234")); ContentResponse renewResponse = request.send(); assertEquals(HttpServletResponse.SC_OK, renewResponse.getStatus()); String newSessionCookie = renewResponse.getHeaders().get("Set-Cookie"); diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/WebSocketClient.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/WebSocketClient.java index 7a6f15133435..17c9411fe636 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/WebSocketClient.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/WebSocketClient.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee10.websocket.client; import java.io.IOException; -import java.net.CookieStore; import java.net.SocketAddress; import java.net.URI; import java.time.Duration; @@ -323,21 +322,6 @@ public void setConnectTimeout(long ms) getHttpClient().setConnectTimeout(ms); } - public CookieStore getCookieStore() - { - return getHttpClient().getCookieStore(); - } - - public void setCookieStore(CookieStore cookieStore) - { - getHttpClient().setCookieStore(cookieStore); - } - - public ByteBufferPool getByteBufferPool() - { - return getHttpClient().getByteBufferPool(); - } - @Override public Executor getExecutor() { diff --git a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java index 6a2e72ed0988..281e765b75fa 100644 --- a/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java +++ b/jetty-ee10/jetty-ee10-websocket/jetty-ee10-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee10/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java @@ -49,7 +49,9 @@ public DelegatedJettyClientUpgradeRequest(CoreClientUpgradeRequest delegate) @Override public List getCookies() { - return delegate.getCookies(); + return delegate.getCookies().stream() + .map(org.eclipse.jetty.http.HttpCookie::asJavaNetHttpCookie) + .toList(); } @Override diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Request.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Request.java index 7b495c740cdd..d0156c77e366 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Request.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Request.java @@ -66,7 +66,6 @@ import org.eclipse.jetty.http.ComplianceViolation; import org.eclipse.jetty.http.HttpCompliance; import org.eclipse.jetty.http.HttpCookie; -import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; @@ -79,6 +78,8 @@ import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.server.HttpCookieUtils; +import org.eclipse.jetty.server.HttpCookieUtils.SetCookieHttpField; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Session; import org.eclipse.jetty.session.AbstractSessionManager; @@ -316,10 +317,10 @@ public PushBuilder newPushBuilder() } else { - Map cookieFields = HttpCookie.extractBasics(field.getValue()); + Map cookieFields = HttpCookieUtils.extractBasics(field.getValue()); cookieName = cookieFields.get("name"); cookieValue = cookieFields.get("value"); - cookieMaxAge = cookieFields.get("max-age") != null ? Long.valueOf(cookieFields.get("max-age")) : -1; + cookieMaxAge = cookieFields.get("max-age") != null ? Long.parseLong(cookieFields.get("max-age")) : -1; } if (cookieMaxAge > 0) diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java index 91f0468d0815..ab4698ca6373 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java @@ -37,7 +37,6 @@ import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpCookie.SameSite; -import org.eclipse.jetty.http.HttpCookie.SetCookieHttpField; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; @@ -52,6 +51,8 @@ import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.http.content.HttpContent; import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.server.HttpCookieUtils; +import org.eclipse.jetty.server.HttpCookieUtils.SetCookieHttpField; import org.eclipse.jetty.server.Session; import org.eclipse.jetty.session.SessionManager; import org.eclipse.jetty.util.AtomicBiInteger; @@ -252,7 +253,7 @@ private HttpCookie checkSameSite(HttpCookie cookie) return cookie; //sameSite is not set, use the default configured for the context, if one exists - SameSite contextDefault = HttpCookie.getSameSiteDefault(_channel.getRequest().getContext().getCoreContext()); + SameSite contextDefault = HttpCookieUtils.getSameSiteDefault(_channel.getRequest().getContext().getCoreContext()); if (contextDefault == null) return cookie; //no default set @@ -288,14 +289,14 @@ public void replaceCookie(HttpCookie cookie) { CookieCompliance compliance = getHttpChannel().getHttpConfiguration().getResponseCookieCompliance(); - if (field instanceof HttpCookie.SetCookieHttpField) + if (field instanceof HttpCookieUtils.SetCookieHttpField) { - if (!HttpCookie.match(((HttpCookie.SetCookieHttpField)field).getHttpCookie(), cookie.getName(), cookie.getDomain(), cookie.getPath())) + if (!HttpCookieUtils.match(((HttpCookieUtils.SetCookieHttpField)field).getHttpCookie(), cookie.getName(), cookie.getDomain(), cookie.getPath())) continue; } else { - if (!HttpCookie.match(field.getValue(), cookie.getName(), cookie.getDomain(), cookie.getPath())) + if (!HttpCookieUtils.match(field.getValue(), cookie.getName(), cookie.getDomain(), cookie.getPath())) continue; } @@ -1582,9 +1583,15 @@ public Map getAttributes() } @Override - public String asString() + public int hashCode() { - return HttpCookie.asString(this); + return HttpCookie.hashCode(this); + } + + @Override + public boolean equals(Object obj) + { + return HttpCookie.equals(this, obj); } @Override diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java index 07da0e897247..b8f3bdd4bd24 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/RequestTest.java @@ -73,6 +73,7 @@ import org.eclipse.jetty.server.Context; import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.HttpCookieUtils; import org.eclipse.jetty.server.HttpStream; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.LocalConnector.LocalEndPoint; @@ -1870,10 +1871,10 @@ public void testPushBuilder() String uri = "http://host/foo/something"; HttpChannel httpChannel = new HttpChannel(_context, new MockConnectionMetaData(_connector)); Request request = new MockRequest(httpChannel, new HttpInput(httpChannel)); - request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(HttpCookie.from("good", "thumbsup", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(100))), CookieCompliance.RFC6265)); - request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(HttpCookie.from("bonza", "bewdy", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1))), CookieCompliance.RFC6265)); - request.getResponse().getHttpFields().add(new HttpCookie.SetCookieHttpField(HttpCookie.from("bad", "thumbsdown", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0))), CookieCompliance.RFC6265)); - request.getResponse().getHttpFields().add(new HttpField(HttpHeader.SET_COOKIE, HttpCookie.getSetCookie(HttpCookie.from("ugly", "duckling", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(100))), CookieCompliance.RFC6265))); + request.getResponse().getHttpFields().add(new HttpCookieUtils.SetCookieHttpField(HttpCookie.from("good", "thumbsup", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(100))), CookieCompliance.RFC6265)); + request.getResponse().getHttpFields().add(new HttpCookieUtils.SetCookieHttpField(HttpCookie.from("bonza", "bewdy", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(1))), CookieCompliance.RFC6265)); + request.getResponse().getHttpFields().add(new HttpCookieUtils.SetCookieHttpField(HttpCookie.from("bad", "thumbsdown", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(0))), CookieCompliance.RFC6265)); + request.getResponse().getHttpFields().add(new HttpField(HttpHeader.SET_COOKIE, HttpCookieUtils.getSetCookie(HttpCookie.from("ugly", "duckling", Map.of(HttpCookie.MAX_AGE_ATTRIBUTE, Long.toString(100))), CookieCompliance.RFC6265))); request.getResponse().getHttpFields().add(new HttpField(HttpHeader.SET_COOKIE, "flow=away; Max-Age=0; Secure; HttpOnly; SameSite=None")); HttpFields.Mutable fields = HttpFields.build(); fields.add(HttpHeader.AUTHORIZATION, "Basic foo"); diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java index 8bf2e051e4f3..1c0bdbbdee83 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/ResponseTest.java @@ -58,6 +58,7 @@ import org.eclipse.jetty.server.Context; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.HttpCookieUtils; import org.eclipse.jetty.server.HttpStream; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.NetworkConnector; @@ -1942,7 +1943,7 @@ public void testAddCookieInInclude() throws Exception @Test public void testAddCookieSameSiteByComment() throws Exception { - _context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, HttpCookie.SameSite.STRICT); + _context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, HttpCookie.SameSite.STRICT); Response response = getResponse(); Cookie cookie = new Cookie("name", "value"); @@ -1960,7 +1961,7 @@ public void testAddCookieSameSiteByComment() throws Exception public void testAddCookieSameSiteDefault() throws Exception { Response response = getResponse(); - _context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, HttpCookie.SameSite.STRICT); + _context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, HttpCookie.SameSite.STRICT); Cookie cookie = new Cookie("name", "value"); cookie.setDomain("domain"); cookie.setPath("/path"); @@ -1974,7 +1975,7 @@ public void testAddCookieSameSiteDefault() throws Exception response.getHttpFields().remove("Set-Cookie"); //test bad default samesite value - _context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "FooBar"); + _context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, "FooBar"); assertThrows(IllegalStateException.class, () -> response.addCookie(cookie)); @@ -2123,7 +2124,7 @@ public void testReplaceHttpCookie() public void testReplaceHttpCookieSameSite() { Response response = getResponse(); - _context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "LAX"); + _context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, "LAX"); //replace with no prior does an add response.replaceCookie(HttpCookie.from("Foo", "123456")); String set = response.getHttpFields().get("Set-Cookie"); @@ -2163,7 +2164,7 @@ public void testReplaceParsedHttpCookie() public void testReplaceParsedHttpCookieSiteDefault() { Response response = getResponse(); - _context.setAttribute(HttpCookie.SAME_SITE_DEFAULT_ATTRIBUTE, "LAX"); + _context.setAttribute(HttpCookieUtils.SAME_SITE_DEFAULT_ATTRIBUTE, "LAX"); response.addHeader(HttpHeader.SET_COOKIE.asString(), "Foo=123456"); response.replaceCookie(HttpCookie.from("Foo", "value")); diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java index 61c07fe471d1..84fd88876279 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java @@ -31,6 +31,7 @@ import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpTester; +import org.eclipse.jetty.server.HttpCookieUtils; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Session; @@ -195,7 +196,7 @@ public void testSessionCookie() throws Exception assertEquals(99, cookie.getMaxAge()); assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite()); - String cookieStr = HttpCookie.getRFC6265SetCookie(cookie); + String cookieStr = HttpCookieUtils.getRFC6265SetCookie(cookie); assertThat(cookieStr, containsString("; SameSite=Strict")); } diff --git a/jetty-ee9/jetty-ee9-proxy/src/main/java/org/eclipse/jetty/ee9/proxy/AbstractProxyServlet.java b/jetty-ee9/jetty-ee9-proxy/src/main/java/org/eclipse/jetty/ee9/proxy/AbstractProxyServlet.java index 5d2975ff649e..0f67b893ac1b 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/main/java/org/eclipse/jetty/ee9/proxy/AbstractProxyServlet.java +++ b/jetty-ee9/jetty-ee9-proxy/src/main/java/org/eclipse/jetty/ee9/proxy/AbstractProxyServlet.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.client.Request; import org.eclipse.jetty.client.Response; import org.eclipse.jetty.client.transport.HttpClientTransportDynamic; +import org.eclipse.jetty.http.HttpCookieStore; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; @@ -47,7 +48,6 @@ import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.io.ClientConnector; -import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -276,7 +276,7 @@ protected HttpClient createHttpClient() throws ServletException client.setFollowRedirects(false); // Must not store cookies, otherwise cookies of different clients will mix. - client.setCookieStore(new HttpCookieStore.Empty()); + client.setHttpCookieStore(new HttpCookieStore.Empty()); Executor executor; String value = config.getInitParameter("maxThreads"); diff --git a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java index 2d4af79a1fa7..c6d41f538c7b 100644 --- a/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java +++ b/jetty-ee9/jetty-ee9-proxy/src/test/java/org/eclipse/jetty/ee9/proxy/ProxyServletTest.java @@ -21,7 +21,6 @@ import java.io.PrintWriter; import java.io.Writer; import java.net.ConnectException; -import java.net.HttpCookie; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -75,6 +74,7 @@ import org.eclipse.jetty.ee9.servlet.FilterHolder; import org.eclipse.jetty.ee9.servlet.ServletContextHandler; import org.eclipse.jetty.ee9.servlet.ServletHolder; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; @@ -1193,7 +1193,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) .send(); assertEquals(200, response1.getStatus()); assertTrue(response1.getHeaders().contains(PROXIED_HEADER)); - List cookies = client.getCookieStore().getCookies(); + List cookies = client.getHttpCookieStore().all(); assertEquals(1, cookies.size()); assertEquals(name, cookies.get(0).getName()); assertEquals(value1, cookies.get(0).getValue()); @@ -1208,7 +1208,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) .send(); assertEquals(200, response2.getStatus()); assertTrue(response2.getHeaders().contains(PROXIED_HEADER)); - cookies = client2.getCookieStore().getCookies(); + cookies = client2.getHttpCookieStore().all(); assertEquals(1, cookies.size()); assertEquals(name, cookies.get(0).getName()); assertEquals(value2, cookies.get(0).getValue()); diff --git a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/SessionRenewTest.java b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/SessionRenewTest.java index 745a94a69255..752fdc9053c4 100644 --- a/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/SessionRenewTest.java +++ b/jetty-ee9/jetty-ee9-tests/jetty-ee9-test-sessions/jetty-ee9-test-sessions-common/src/test/java/org/eclipse/jetty/ee9/session/SessionRenewTest.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee9.session; import java.io.IOException; -import java.net.HttpCookie; import java.util.concurrent.TimeUnit; import jakarta.servlet.ServletException; @@ -29,6 +28,7 @@ import org.eclipse.jetty.client.Request; import org.eclipse.jetty.ee9.nested.SessionHandler.ServletSessionApi; import org.eclipse.jetty.ee9.servlet.ServletContextHandler; +import org.eclipse.jetty.http.HttpCookie; import org.eclipse.jetty.session.DefaultSessionCache; import org.eclipse.jetty.session.DefaultSessionCacheFactory; import org.eclipse.jetty.session.DefaultSessionIdManager; @@ -172,7 +172,7 @@ public void testSessionRenewalMultiContext() throws Exception //make a request to change the sessionid Request request = client.newRequest("http://localhost:" + port + contextPathA + servletMapping + "?action=renew"); - request.cookie(new HttpCookie(SessionManager.__DefaultSessionCookie, "1234")); + request.cookie(HttpCookie.from(SessionManager.__DefaultSessionCookie, "1234")); ContentResponse renewResponse = request.send(); assertEquals(HttpServletResponse.SC_OK, renewResponse.getStatus()); String newSessionCookie = renewResponse.getHeaders().get("Set-Cookie"); diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java index 87387e557e5e..6049d87f50cf 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/WebSocketClient.java @@ -14,7 +14,6 @@ package org.eclipse.jetty.ee9.websocket.client; import java.io.IOException; -import java.net.CookieStore; import java.net.SocketAddress; import java.net.URI; import java.time.Duration; @@ -324,21 +323,6 @@ public void setConnectTimeout(long ms) getHttpClient().setConnectTimeout(ms); } - public CookieStore getCookieStore() - { - return getHttpClient().getCookieStore(); - } - - public void setCookieStore(CookieStore cookieStore) - { - getHttpClient().setCookieStore(cookieStore); - } - - public ByteBufferPool getByteBufferPool() - { - return getHttpClient().getByteBufferPool(); - } - @Override public Executor getExecutor() { diff --git a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java index 2e4f8ff12433..57da73870322 100644 --- a/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java +++ b/jetty-ee9/jetty-ee9-websocket/jetty-ee9-websocket-jetty-client/src/main/java/org/eclipse/jetty/ee9/websocket/client/impl/DelegatedJettyClientUpgradeRequest.java @@ -49,7 +49,9 @@ public DelegatedJettyClientUpgradeRequest(CoreClientUpgradeRequest delegate) @Override public List getCookies() { - return delegate.getCookies(); + return delegate.getCookies().stream() + .map(org.eclipse.jetty.http.HttpCookie::asJavaNetHttpCookie) + .toList(); } @Override