diff --git a/CHANGES.md b/CHANGES.md
index 4f4fa17..7a156ff 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,10 @@
# Version 2.1 (unreleased)
+Servlet API 6.0.0 is now the minimum servlet API officially supported.
+Version 5 should on a "best effort" basis.
+
+\#259: The SameSite attribute in cookies is now preserved.
+
# Version 2.0 released on 2023-06-28
\#231: Added support of preserveCookiePath configuration parameter. It allows to keep cookie path unchanged in Set-Cookie server response header.
diff --git a/README.md b/README.md
index 51fa79e..a816d61 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,8 @@ See [CHANGES.md](CHANGES.md) for a history of changes.
Build & Installation
------------
+**Java version:** The minimum Java version is 11.
+
Simply build the jar using "mvn package" at the command line.
The jar is built to "target/smiley-http-proxy-servlet-VERSION.jar".
You don't have to build the jar if you aren't modifying the code, since released
diff --git a/pom.xml b/pom.xml
index 957fbe6..4037552 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,7 +57,7 @@
jakarta.servlet
jakarta.servlet-api
- 5.0.0
+ 6.0.0
provided
@@ -103,9 +103,9 @@
- org.eclipse.jetty
- jetty-servlet
- 11.0.15
+ org.apache.tomcat.embed
+ tomcat-embed-core
+ 10.1.54
test
diff --git a/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java b/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java
index 41b2983..1eec923 100755
--- a/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java
+++ b/src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java
@@ -582,10 +582,28 @@ protected void copyProxyCookie(HttpServletRequest servletRequest,
HttpServletResponse servletResponse, String headerValue) {
for (HttpCookie cookie : HttpCookie.parse(headerValue)) {
Cookie servletCookie = createProxyCookie(servletRequest, cookie);
+ String sameSite = parseSameSite(headerValue);
+ if (sameSite != null) {
+ try {
+ servletCookie.setAttribute("SameSite", sameSite); // Servlet 6.0+
+ } catch (NoSuchMethodError ignored) {
+ // SameSite not preserved on older versions (e.g. Servlet 5.0)
+ }
+ }
servletResponse.addCookie(servletCookie);
}
}
+ private static String parseSameSite(String headerValue) {
+ for (String part : headerValue.split(";")) {
+ String trimmed = part.trim();
+ if (trimmed.regionMatches(true, 0, "SameSite=", 0, 9)) {
+ return trimmed.substring(9).trim();
+ }
+ }
+ return null;
+ }
+
/**
* Creates a proxy cookie from the original cookie.
*
diff --git a/src/test/java/org/mitre/dsmiley/httpproxy/AcceptEncodingTest.java b/src/test/java/org/mitre/dsmiley/httpproxy/AcceptEncodingTest.java
index 04820e5..acc07d7 100644
--- a/src/test/java/org/mitre/dsmiley/httpproxy/AcceptEncodingTest.java
+++ b/src/test/java/org/mitre/dsmiley/httpproxy/AcceptEncodingTest.java
@@ -26,39 +26,39 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.startup.Tomcat;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class AcceptEncodingTest {
- private Server server;
- private ServletHandler servletHandler;
+ private Tomcat tomcat;
+ private Context ctx;
private int serverPort;
@Before
public void setUp() throws Exception {
- server = new Server(0);
- servletHandler = new ServletHandler();
- Handler serverHandler = servletHandler;
- server.setHandler(serverHandler);
- server.start();
-
- serverPort = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
+ tomcat = new Tomcat();
+ tomcat.setPort(0);
+ String tempDir = System.getProperty("java.io.tmpdir");
+ tomcat.setBaseDir( tempDir );
+ tomcat.getConnector();
+ ctx = tomcat.addContext("", tempDir );
+ tomcat.start();
+ serverPort = tomcat.getConnector().getLocalPort();
}
@After
public void tearDown() throws Exception {
- server.stop();
+ tomcat.stop();
+ tomcat.destroy();
serverPort = -1;
}
@@ -78,23 +78,25 @@ public void testHandlingAcceptEncodingHeader() throws Exception {
of the client needs to be passed through as is.
*/
- ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/acceptEncodingProxyHandleCompression/*");
- servletHolder.setInitParameter(ProxyServlet.P_LOG, "true");
- servletHolder.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/acceptEncoding/", serverPort));
- servletHolder.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.TRUE.toString());
+ Wrapper w1 = Tomcat.addServlet(ctx, "proxy1", ProxyServlet.class.getName());
+ w1.addInitParameter(ProxyServlet.P_LOG, "true");
+ w1.addInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/acceptEncoding/", serverPort));
+ w1.addInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.TRUE.toString());
+ ctx.addServletMappingDecoded("/acceptEncodingProxyHandleCompression/*", "proxy1");
- ServletHolder servletHolder2 = servletHandler.addServletWithMapping(ProxyServlet.class, "/acceptEncodingProxy/*");
- servletHolder2.setInitParameter(ProxyServlet.P_LOG, "true");
- servletHolder2.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/acceptEncoding/", serverPort));
- servletHolder2.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.FALSE.toString());
+ Wrapper w2 = Tomcat.addServlet(ctx, "proxy2", ProxyServlet.class.getName());
+ w2.addInitParameter(ProxyServlet.P_LOG, "true");
+ w2.addInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/acceptEncoding/", serverPort));
+ w2.addInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.FALSE.toString());
+ ctx.addServletMappingDecoded("/acceptEncodingProxy/*", "proxy2");
- ServletHolder dummyBackend = new ServletHolder(new HttpServlet() {
+ Tomcat.addServlet(ctx, "backend", new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getOutputStream().write(req.getHeader("Accept-Encoding").getBytes(StandardCharsets.UTF_8));
}
});
- servletHandler.addServletWithMapping(dummyBackend, "/acceptEncoding/*");
+ ctx.addServletMappingDecoded("/acceptEncoding/*", "backend");
HttpGet queryHandleCompression = new HttpGet(String.format("http://localhost:%d/acceptEncodingProxyHandleCompression/test", serverPort));
HttpGet query = new HttpGet(String.format("http://localhost:%d/acceptEncodingProxy/test", serverPort));
@@ -106,7 +108,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
try (CloseableHttpClient chc = HttpClientBuilder.create().disableContentCompression().build();
CloseableHttpResponse responseHandleCompression = chc.execute(queryHandleCompression);
- CloseableHttpResponse response = chc.execute(query);
+ CloseableHttpResponse response = chc.execute(query)
) {
try (InputStream is = response.getEntity().getContent()) {
byte[] readData = readBlock(is);
diff --git a/src/test/java/org/mitre/dsmiley/httpproxy/ChunkedTransferTest.java b/src/test/java/org/mitre/dsmiley/httpproxy/ChunkedTransferTest.java
index 91b538b..83ce79a 100644
--- a/src/test/java/org/mitre/dsmiley/httpproxy/ChunkedTransferTest.java
+++ b/src/test/java/org/mitre/dsmiley/httpproxy/ChunkedTransferTest.java
@@ -16,6 +16,7 @@
package org.mitre.dsmiley.httpproxy;
import java.io.IOException;
+import java.util.zip.GZIPOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@@ -23,37 +24,43 @@
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import jakarta.servlet.http.HttpServletResponseWrapper;
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.startup.Tomcat;
import org.apache.http.MalformedChunkCodingException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
-import org.eclipse.jetty.server.Handler;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.gzip.GzipHandler;
-import org.eclipse.jetty.servlet.ServletHandler;
-import org.eclipse.jetty.servlet.ServletHolder;
+import org.apache.tomcat.util.descriptor.web.FilterDef;
+import org.apache.tomcat.util.descriptor.web.FilterMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
-import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class ChunkedTransferTest {
@Parameters
public static List