Skip to content

Commit 85ae54a

Browse files
author
Nils Christian Ehmke
committed
[Issue-259] SameSite attribute is not preserved
1 parent cfdc960 commit 85ae54a

8 files changed

Lines changed: 142 additions & 38 deletions

File tree

CHANGES.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
# Version 2.1 (unreleased)
1+
# Version 3.0 (unreleased)
2+
3+
Servlet API 6.0.0 is now the minimum servlet API supported.
4+
5+
\#259: The SameSite attribute in cookies is now preserved.
26

37
# Version 2.0 released on 2023-06-28
48

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ If you need to support _older_ HttpClient versions:
3232
* use 1.8 version of this proxy for HttpClient versions 4.1 and 4.2
3333
* use 1.12 version of this proxy for HttpClient versions 4.3 and 4.4
3434

35+
36+
As of version 3.0 of the proxy, the proxy requires version 6.0.0 of the servlet API.
37+
3538
As of version 2.0 of the proxy, the proxy switched to the `jakarta servlet-api`, while nonetheless retaining support for the `javax servlet-api` version for those that need it. To use that, specify the `javax` classifier on the dependency declaration as follows:
3639
```xml
3740
<dependency>

pom.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
<groupId>org.mitre.dsmiley.httpproxy</groupId>
55
<artifactId>smiley-http-proxy-servlet</artifactId>
6-
<version>2.1-SNAPSHOT</version>
6+
<version>3.0-SNAPSHOT</version>
77
<packaging>jar</packaging>
88

99
<name>Smiley's HTTP Proxy Servlet</name>
@@ -57,7 +57,7 @@
5757
<dependency>
5858
<groupId>jakarta.servlet</groupId>
5959
<artifactId>jakarta.servlet-api</artifactId>
60-
<version>5.0.0</version>
60+
<version>6.0.0</version>
6161
<scope>provided</scope>
6262
</dependency>
6363

@@ -103,9 +103,9 @@
103103
</dependency>
104104

105105
<dependency>
106-
<groupId>org.eclipse.jetty</groupId>
107-
<artifactId>jetty-servlet</artifactId>
108-
<version>11.0.15</version>
106+
<groupId>org.eclipse.jetty.ee10</groupId>
107+
<artifactId>jetty-ee10-servlet</artifactId>
108+
<version>12.0.9</version>
109109
<scope>test</scope>
110110
</dependency>
111111
</dependencies>

src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,10 +582,24 @@ protected void copyProxyCookie(HttpServletRequest servletRequest,
582582
HttpServletResponse servletResponse, String headerValue) {
583583
for (HttpCookie cookie : HttpCookie.parse(headerValue)) {
584584
Cookie servletCookie = createProxyCookie(servletRequest, cookie);
585+
String sameSite = parseSameSite(headerValue);
586+
if (sameSite != null) {
587+
servletCookie.setAttribute("SameSite", sameSite);
588+
}
585589
servletResponse.addCookie(servletCookie);
586590
}
587591
}
588592

593+
private static String parseSameSite(String headerValue) {
594+
for (String part : headerValue.split(";")) {
595+
String trimmed = part.trim();
596+
if (trimmed.regionMatches(true, 0, "SameSite=", 0, 9)) {
597+
return trimmed.substring(9).trim();
598+
}
599+
}
600+
return null;
601+
}
602+
589603
/**
590604
* Creates a proxy cookie from the original cookie.
591605
*

src/test/java/org/mitre/dsmiley/httpproxy/AcceptEncodingTest.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,27 +30,26 @@
3030
import org.apache.http.client.methods.HttpGet;
3131
import org.apache.http.impl.client.CloseableHttpClient;
3232
import org.apache.http.impl.client.HttpClientBuilder;
33-
import org.eclipse.jetty.server.Handler;
3433
import org.eclipse.jetty.server.Server;
3534
import org.eclipse.jetty.server.ServerConnector;
36-
import org.eclipse.jetty.servlet.ServletHandler;
37-
import org.eclipse.jetty.servlet.ServletHolder;
35+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
36+
import org.eclipse.jetty.ee10.servlet.ServletHolder;
3837
import org.junit.After;
3938
import org.junit.Before;
4039
import org.junit.Test;
4140

4241
public class AcceptEncodingTest {
4342

4443
private Server server;
45-
private ServletHandler servletHandler;
44+
private ServletContextHandler context;
4645
private int serverPort;
4746

4847
@Before
4948
public void setUp() throws Exception {
5049
server = new Server(0);
51-
servletHandler = new ServletHandler();
52-
Handler serverHandler = servletHandler;
53-
server.setHandler(serverHandler);
50+
context = new ServletContextHandler();
51+
context.setContextPath("/");
52+
server.setHandler(context);
5453
server.start();
5554

5655
serverPort = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
@@ -78,12 +77,12 @@ public void testHandlingAcceptEncodingHeader() throws Exception {
7877
of the client needs to be passed through as is.
7978
*/
8079

81-
ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/acceptEncodingProxyHandleCompression/*");
80+
ServletHolder servletHolder = context.addServlet(ProxyServlet.class, "/acceptEncodingProxyHandleCompression/*");
8281
servletHolder.setInitParameter(ProxyServlet.P_LOG, "true");
8382
servletHolder.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/acceptEncoding/", serverPort));
8483
servletHolder.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.TRUE.toString());
8584

86-
ServletHolder servletHolder2 = servletHandler.addServletWithMapping(ProxyServlet.class, "/acceptEncodingProxy/*");
85+
ServletHolder servletHolder2 = context.addServlet(ProxyServlet.class, "/acceptEncodingProxy/*");
8786
servletHolder2.setInitParameter(ProxyServlet.P_LOG, "true");
8887
servletHolder2.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/acceptEncoding/", serverPort));
8988
servletHolder2.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.FALSE.toString());
@@ -94,7 +93,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
9493
resp.getOutputStream().write(req.getHeader("Accept-Encoding").getBytes(StandardCharsets.UTF_8));
9594
}
9695
});
97-
servletHandler.addServletWithMapping(dummyBackend, "/acceptEncoding/*");
96+
context.addServlet(dummyBackend, "/acceptEncoding/*");
9897

9998
HttpGet queryHandleCompression = new HttpGet(String.format("http://localhost:%d/acceptEncodingProxyHandleCompression/test", serverPort));
10099
HttpGet query = new HttpGet(String.format("http://localhost:%d/acceptEncodingProxy/test", serverPort));

src/test/java/org/mitre/dsmiley/httpproxy/ChunkedTransferTest.java

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@
3232
import org.apache.http.client.methods.HttpGet;
3333
import org.apache.http.impl.client.CloseableHttpClient;
3434
import org.apache.http.impl.client.HttpClientBuilder;
35-
import org.eclipse.jetty.server.Handler;
35+
3636

3737
import org.eclipse.jetty.server.Server;
3838
import org.eclipse.jetty.server.ServerConnector;
3939
import org.eclipse.jetty.server.handler.gzip.GzipHandler;
40-
import org.eclipse.jetty.servlet.ServletHandler;
41-
import org.eclipse.jetty.servlet.ServletHolder;
40+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
41+
import org.eclipse.jetty.ee10.servlet.ServletHolder;
4242
import org.junit.After;
4343
import org.junit.Before;
4444
import org.junit.Test;
@@ -62,7 +62,7 @@ public static List<Object[]> data() {
6262
}
6363

6464
private Server server;
65-
private ServletHandler servletHandler;
65+
private ServletContextHandler context;
6666
private int serverPort;
6767
private boolean supportBackendCompression;
6868
private boolean handleCompressionApacheClient;
@@ -75,17 +75,16 @@ public ChunkedTransferTest(boolean supportBackendCompression, boolean handleComp
7575
@Before
7676
public void setUp() throws Exception {
7777
server = new Server(0);
78-
servletHandler = new ServletHandler();
79-
Handler serverHandler = servletHandler;
80-
if(supportBackendCompression) {
78+
context = new ServletContextHandler();
79+
context.setContextPath("/");
80+
if (supportBackendCompression) {
8181
GzipHandler gzipHandler = new GzipHandler();
82-
gzipHandler.setHandler(serverHandler);
82+
gzipHandler.setHandler(context);
8383
gzipHandler.setSyncFlush(true);
84-
serverHandler = gzipHandler;
84+
server.setHandler(gzipHandler);
8585
} else {
86-
serverHandler = servletHandler;
86+
server.setHandler(context);
8787
}
88-
server.setHandler(serverHandler);
8988
server.start();
9089

9190
serverPort = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
@@ -119,7 +118,7 @@ public void testChunkedTransfer() throws Exception {
119118
final byte[] data1 = "event: message\ndata: Dummy Data1\n\n".getBytes(StandardCharsets.UTF_8);
120119
final byte[] data2 = "event: message\ndata: Dummy Data2\n\n".getBytes(StandardCharsets.UTF_8);
121120

122-
ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/chatProxied/*");
121+
ServletHolder servletHolder = context.addServlet(ProxyServlet.class, "/chatProxied/*");
123122
servletHolder.setInitParameter(ProxyServlet.P_LOG, "true");
124123
servletHolder.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/chat/", serverPort));
125124
servletHolder.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.toString(handleCompressionApacheClient));
@@ -145,7 +144,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
145144
}
146145
}
147146
});
148-
servletHandler.addServletWithMapping(dummyBackend, "/chat/*");
147+
context.addServlet(dummyBackend, "/chat/*");
149148

150149
HttpGet url = new HttpGet(String.format("http://localhost:%d/chatProxied/test", serverPort));
151150

@@ -179,7 +178,7 @@ when the closing of the proxy (frontend) connection is detected.
179178
final byte[] data1 = "event: message\ndata: Dummy Data1\n\n".getBytes(StandardCharsets.UTF_8);
180179
final byte[] data2 = "event: message\ndata: Dummy Data2\n\n".getBytes(StandardCharsets.UTF_8);
181180

182-
ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/chatProxied/*");
181+
ServletHolder servletHolder = context.addServlet(ProxyServlet.class, "/chatProxied/*");
183182
servletHolder.setInitParameter(ProxyServlet.P_LOG, "true");
184183
servletHolder.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/chat/", serverPort));
185184
servletHolder.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.toString(handleCompressionApacheClient));
@@ -215,7 +214,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
215214
}
216215
}
217216
});
218-
servletHandler.addServletWithMapping(dummyBackend, "/chat/*");
217+
context.addServlet(dummyBackend, "/chat/*");
219218

220219
HttpGet url = new HttpGet(String.format("http://localhost:%d/chatProxied/test", serverPort));
221220

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package org.mitre.dsmiley.httpproxy;
15+
16+
import static org.junit.Assert.assertNotNull;
17+
import static org.junit.Assert.assertTrue;
18+
19+
import jakarta.servlet.ServletException;
20+
import jakarta.servlet.http.HttpServlet;
21+
import jakarta.servlet.http.HttpServletRequest;
22+
import jakarta.servlet.http.HttpServletResponse;
23+
import java.io.IOException;
24+
import org.apache.http.Header;
25+
import org.apache.http.client.methods.CloseableHttpResponse;
26+
import org.apache.http.client.methods.HttpGet;
27+
import org.apache.http.impl.client.CloseableHttpClient;
28+
import org.apache.http.impl.client.HttpClientBuilder;
29+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
30+
import org.eclipse.jetty.ee10.servlet.ServletHolder;
31+
import org.eclipse.jetty.server.Server;
32+
import org.eclipse.jetty.server.ServerConnector;
33+
import org.junit.After;
34+
import org.junit.Before;
35+
import org.junit.Test;
36+
37+
public class CookieSameSiteTest {
38+
39+
private Server server;
40+
private ServletContextHandler context;
41+
private int serverPort;
42+
43+
@Before
44+
public void setUp() throws Exception {
45+
server = new Server(0);
46+
context = new ServletContextHandler();
47+
context.setContextPath("/");
48+
server.setHandler(context);
49+
server.start();
50+
serverPort = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
51+
}
52+
53+
@After
54+
public void tearDown() throws Exception {
55+
server.stop();
56+
serverPort = -1;
57+
}
58+
59+
@Test
60+
public void testSameSiteAttributeIsPreserved() throws Exception {
61+
// Backend returns a cookie with SameSite=Strict
62+
ServletHolder backendHolder = new ServletHolder(new HttpServlet() {
63+
@Override
64+
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
65+
throws ServletException, IOException {
66+
resp.addHeader("Set-Cookie", "JSESSIONID=1234; Path=/backend; SameSite=Strict");
67+
}
68+
});
69+
context.addServlet(backendHolder, "/backend/*");
70+
71+
ServletHolder proxyHolder = context.addServlet(ProxyServlet.class, "/proxy/*");
72+
proxyHolder.setInitParameter(ProxyServlet.P_TARGET_URI,
73+
String.format("http://localhost:%d/backend/", serverPort));
74+
75+
HttpGet request = new HttpGet(String.format("http://localhost:%d/proxy/test", serverPort));
76+
try (CloseableHttpClient client = HttpClientBuilder.create().disableRedirectHandling().build();
77+
CloseableHttpResponse response = client.execute(request)) {
78+
Header setCookieHeader = response.getFirstHeader("Set-Cookie");
79+
assertNotNull("Set-Cookie header must be present", setCookieHeader);
80+
assertTrue("SameSite attribute must be preserved when proxying cookies",
81+
setCookieHeader.getValue().contains("SameSite=Strict"));
82+
}
83+
}
84+
}

src/test/java/org/mitre/dsmiley/httpproxy/ParallelConnectionsTest.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@
3838

3939
import org.eclipse.jetty.server.Server;
4040
import org.eclipse.jetty.server.ServerConnector;
41-
import org.eclipse.jetty.servlet.ServletHandler;
42-
import org.eclipse.jetty.servlet.ServletHolder;
41+
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
42+
import org.eclipse.jetty.ee10.servlet.ServletHolder;
4343
import org.junit.After;
4444
import static org.junit.Assert.assertEquals;
4545
import org.junit.Before;
@@ -48,14 +48,15 @@
4848
public class ParallelConnectionsTest {
4949

5050
private Server server;
51-
private ServletHandler servletHandler;
51+
private ServletContextHandler context;
5252
private int serverPort;
5353

5454
@Before
5555
public void setUp() throws Exception {
5656
server = new Server(0);
57-
servletHandler = new ServletHandler();
58-
server.setHandler(servletHandler);
57+
context = new ServletContextHandler();
58+
context.setContextPath("/");
59+
server.setHandler(context);
5960
server.start();
6061

6162
serverPort = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
@@ -79,7 +80,7 @@ public void testHandlingMultipleConnectionsSameRoute() throws Exception {
7980

8081
int parallelConnectionsToTest = 10;
8182

82-
ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/sampleBackendProxied/*");
83+
ServletHolder servletHolder = context.addServlet(ProxyServlet.class, "/sampleBackendProxied/*");
8384
servletHolder.setInitParameter(ProxyServlet.P_LOG, "true");
8485
servletHolder.setInitParameter(ProxyServlet.P_MAXCONNECTIONS, Integer.toString(parallelConnectionsToTest));
8586
servletHolder.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/sampleBackend/", serverPort));
@@ -106,7 +107,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
106107
}
107108
}
108109
});
109-
servletHandler.addServletWithMapping(dummyBackend, "/sampleBackend/*");
110+
context.addServlet(dummyBackend, "/sampleBackend/*");
110111

111112
URL url = new URL(String.format("http://localhost:%d/sampleBackendProxied/test", serverPort));
112113

0 commit comments

Comments
 (0)