Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Version 2.1 (unreleased)
# Version 3.0 (unreleased)

Servlet API 6.0.0 is now the minimum servlet API supported.

\#259: The SameSite attribute in cookies is now preserved.

# Version 2.0 released on 2023-06-28

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ If you need to support _older_ HttpClient versions:
* use 1.8 version of this proxy for HttpClient versions 4.1 and 4.2
* use 1.12 version of this proxy for HttpClient versions 4.3 and 4.4


As of version 3.0 of the proxy, the proxy requires version 6.0.0 of the servlet API.

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:
```xml
<dependency>
Expand Down
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<groupId>org.mitre.dsmiley.httpproxy</groupId>
<artifactId>smiley-http-proxy-servlet</artifactId>
<version>2.1-SNAPSHOT</version>
<version>3.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>Smiley's HTTP Proxy Servlet</name>
Expand Down Expand Up @@ -57,7 +57,7 @@
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>

Expand Down Expand Up @@ -103,9 +103,9 @@
</dependency>

<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>11.0.15</version>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://jakartaee.github.io/platform/jakartaee10/JakartaEE10ReleasePlan shows servlet 6 only requires Java 11. But this PR adds Jetty dependencies that elevate to Java 17.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dsmiley ,

According to https://jetty.org/download.html the lowest jetty version to support servlet 6.0 is jetty 12, which in turn does indeed require Java 17. I don't think that this is a problem, as it is just a test dependency. Another idea would be to use Tomcat 10.1.x, which would support Servlet 6.0 but still Java 11.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #262.

<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-servlet</artifactId>
<version>12.0.9</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/mitre/dsmiley/httpproxy/ProxyServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,24 @@ 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) {
servletCookie.setAttribute("SameSite", sameSite);
}
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.
*
Expand Down
19 changes: 9 additions & 10 deletions src/test/java/org/mitre/dsmiley/httpproxy/AcceptEncodingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,27 +30,26 @@
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.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class AcceptEncodingTest {

private Server server;
private ServletHandler servletHandler;
private ServletContextHandler context;
private int serverPort;

@Before
public void setUp() throws Exception {
server = new Server(0);
servletHandler = new ServletHandler();
Handler serverHandler = servletHandler;
server.setHandler(serverHandler);
context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
server.start();

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

ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/acceptEncodingProxyHandleCompression/*");
ServletHolder servletHolder = context.addServlet(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());

ServletHolder servletHolder2 = servletHandler.addServletWithMapping(ProxyServlet.class, "/acceptEncodingProxy/*");
ServletHolder servletHolder2 = context.addServlet(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());
Expand All @@ -94,7 +93,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
resp.getOutputStream().write(req.getHeader("Accept-Encoding").getBytes(StandardCharsets.UTF_8));
}
});
servletHandler.addServletWithMapping(dummyBackend, "/acceptEncoding/*");
context.addServlet(dummyBackend, "/acceptEncoding/*");

HttpGet queryHandleCompression = new HttpGet(String.format("http://localhost:%d/acceptEncodingProxyHandleCompression/test", serverPort));
HttpGet query = new HttpGet(String.format("http://localhost:%d/acceptEncodingProxy/test", serverPort));
Expand Down
29 changes: 14 additions & 15 deletions src/test/java/org/mitre/dsmiley/httpproxy/ChunkedTransferTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,13 @@
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.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -62,7 +62,7 @@ public static List<Object[]> data() {
}

private Server server;
private ServletHandler servletHandler;
private ServletContextHandler context;
private int serverPort;
private boolean supportBackendCompression;
private boolean handleCompressionApacheClient;
Expand All @@ -75,17 +75,16 @@ public ChunkedTransferTest(boolean supportBackendCompression, boolean handleComp
@Before
public void setUp() throws Exception {
server = new Server(0);
servletHandler = new ServletHandler();
Handler serverHandler = servletHandler;
if(supportBackendCompression) {
context = new ServletContextHandler();
context.setContextPath("/");
if (supportBackendCompression) {
GzipHandler gzipHandler = new GzipHandler();
gzipHandler.setHandler(serverHandler);
gzipHandler.setHandler(context);
gzipHandler.setSyncFlush(true);
serverHandler = gzipHandler;
server.setHandler(gzipHandler);
} else {
serverHandler = servletHandler;
server.setHandler(context);
}
server.setHandler(serverHandler);
server.start();

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

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

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

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

ServletHolder servletHolder = servletHandler.addServletWithMapping(ProxyServlet.class, "/chatProxied/*");
ServletHolder servletHolder = context.addServlet(ProxyServlet.class, "/chatProxied/*");
servletHolder.setInitParameter(ProxyServlet.P_LOG, "true");
servletHolder.setInitParameter(ProxyServlet.P_TARGET_URI, String.format("http://localhost:%d/chat/", serverPort));
servletHolder.setInitParameter(ProxyServlet.P_HANDLECOMPRESSION, Boolean.toString(handleCompressionApacheClient));
Expand Down Expand Up @@ -215,7 +214,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
}
}
});
servletHandler.addServletWithMapping(dummyBackend, "/chat/*");
context.addServlet(dummyBackend, "/chat/*");

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

Expand Down
84 changes: 84 additions & 0 deletions src/test/java/org/mitre/dsmiley/httpproxy/CookieSameSiteTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mitre.dsmiley.httpproxy;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.apache.http.Header;
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.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class CookieSameSiteTest {

private Server server;
private ServletContextHandler context;
private int serverPort;

@Before
public void setUp() throws Exception {
server = new Server(0);
context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
server.start();
serverPort = ((ServerConnector) server.getConnectors()[0]).getLocalPort();
}

@After
public void tearDown() throws Exception {
server.stop();
serverPort = -1;
}

@Test
public void testSameSiteAttributeIsPreserved() throws Exception {
// Backend returns a cookie with SameSite=Strict
ServletHolder backendHolder = new ServletHolder(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.addHeader("Set-Cookie", "JSESSIONID=1234; Path=/backend; SameSite=Strict");
}
});
context.addServlet(backendHolder, "/backend/*");

ServletHolder proxyHolder = context.addServlet(ProxyServlet.class, "/proxy/*");
proxyHolder.setInitParameter(ProxyServlet.P_TARGET_URI,
String.format("http://localhost:%d/backend/", serverPort));

HttpGet request = new HttpGet(String.format("http://localhost:%d/proxy/test", serverPort));
try (CloseableHttpClient client = HttpClientBuilder.create().disableRedirectHandling().build();
CloseableHttpResponse response = client.execute(request)) {
Header setCookieHeader = response.getFirstHeader("Set-Cookie");
assertNotNull("Set-Cookie header must be present", setCookieHeader);
assertTrue("SameSite attribute must be preserved when proxying cookies",
setCookieHeader.getValue().contains("SameSite=Strict"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@

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.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.junit.After;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
Expand All @@ -48,14 +48,15 @@
public class ParallelConnectionsTest {

private Server server;
private ServletHandler servletHandler;
private ServletContextHandler context;
private int serverPort;

@Before
public void setUp() throws Exception {
server = new Server(0);
servletHandler = new ServletHandler();
server.setHandler(servletHandler);
context = new ServletContextHandler();
context.setContextPath("/");
server.setHandler(context);
server.start();

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

int parallelConnectionsToTest = 10;

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

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

Expand Down
Loading