Tests jsoup's client support for HTTPS (#2032)
Tests jsoup's client support for HTTPS
During test time, adds a HTTPS listener to the Jetty test server. Uses a local self-signed cert.
Also refactored the test proxy to use Jetty's ProxyServlet, and ConnectHandler to support TLS proxy tunneling.
diff --git a/CHANGES b/CHANGES
index b199135..ab2de95 100644
--- a/CHANGES
+++ b/CHANGES
@@ -30,6 +30,10 @@
DOMException. Now, said doctype is discarded, and the conversion continues.
* Build Improvement: added a local test proxy implementation, for proxy integration tests.
+ <https://github.com/jhy/jsoup/pull/2029>
+
+ * Build Improvement: added tests for HTTPS request support, using a local self-signed cert. Includes proxy tests.
+ <https://github.com/jhy/jsoup/pull/2032>
Release 1.16.2 [20-Oct-2023]
* Improvement: optimized the performance of complex CSS selectors, by adding a cost-based query planner. Evaluators
diff --git a/pom.xml b/pom.xml
index 60164f3..5a0e1dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -406,6 +406,14 @@
</dependency>
<dependency>
+ <!-- jetty proxy, for integration tests -->
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-proxy</artifactId>
+ <version>${jetty.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
<!-- javax.annotations.nonnull, with Apache 2 (not GPL) license. Build time only. -->
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
diff --git a/src/test/java/org/jsoup/integration/ConnectTest.java b/src/test/java/org/jsoup/integration/ConnectTest.java
index 1043489..424ba48 100644
--- a/src/test/java/org/jsoup/integration/ConnectTest.java
+++ b/src/test/java/org/jsoup/integration/ConnectTest.java
@@ -16,6 +16,8 @@
import org.jsoup.parser.XmlTreeBuilder;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import java.io.File;
import java.io.FileInputStream;
@@ -23,8 +25,10 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
+import java.nio.file.Files;
import java.util.List;
import java.util.Map;
+import java.util.stream.Stream;
import static org.jsoup.helper.HttpConnection.CONTENT_TYPE;
import static org.jsoup.helper.HttpConnection.MULTIPART_FORM_DATA;
@@ -51,6 +55,13 @@
assertEquals("Hello, World!", p.text());
}
+ @Test void canConnectToLocalTlsServer() throws IOException {
+ String url = HelloServlet.TlsUrl;
+ Document doc = Jsoup.connect(url).get();
+ Element p = doc.selectFirst("p");
+ assertEquals("Hello, World!", p.text());
+ }
+
@Test
public void fetchURl() throws IOException {
Document doc = Jsoup.parse(new URL(echoUrl), 10 * 1000);
@@ -283,16 +294,16 @@
/**
* Tests upload of content to a remote service.
*/
- @Test
- public void postFiles() throws IOException {
+ @ParameterizedTest @MethodSource("echoUrls") // http and https
+ public void postFiles(String url) throws IOException {
File thumb = ParseTest.getFile("/htmltests/thumb.jpg");
File html = ParseTest.getFile("/htmltests/large.html");
Document res = Jsoup
- .connect(EchoServlet.Url)
+ .connect(url)
.data("firstname", "Jay")
- .data("firstPart", thumb.getName(), new FileInputStream(thumb), "image/jpeg")
- .data("secondPart", html.getName(), new FileInputStream(html)) // defaults to "application-octetstream";
+ .data("firstPart", thumb.getName(), Files.newInputStream(thumb.toPath()), "image/jpeg")
+ .data("secondPart", html.getName(), Files.newInputStream(html.toPath())) // defaults to "application-octetstream";
.data("surname", "Soup")
.post();
@@ -786,4 +797,11 @@
assertEquals("%E9%8D%B5=%E5%80%A4", ihVal("Query String", doc));
assertEquals("鍵=値", URLDecoder.decode(ihVal("Query String", doc), DataUtil.UTF_8.name()));
}
+
+ /**
+ Provides HTTP and HTTPS EchoServlet URLs
+ */
+ private static Stream<String> echoUrls() {
+ return Stream.of(EchoServlet.Url, EchoServlet.TlsUrl);
+ }
}
diff --git a/src/test/java/org/jsoup/integration/ProxyTest.java b/src/test/java/org/jsoup/integration/ProxyTest.java
index 2650bf4..a02bb18 100644
--- a/src/test/java/org/jsoup/integration/ProxyTest.java
+++ b/src/test/java/org/jsoup/integration/ProxyTest.java
@@ -11,15 +11,17 @@
import org.jsoup.nodes.Element;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import java.io.IOException;
+import java.util.stream.Stream;
import static org.jsoup.integration.ConnectTest.ihVal;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
- Tests Jsoup.connect proxy support
- */
+ Tests Jsoup.connect proxy support */
public class ProxyTest {
private static String echoUrl;
private static TestServer.ProxySettings proxy;
@@ -30,18 +32,23 @@
proxy = ProxyServlet.ProxySettings;
}
- @Test void fetchViaProxy() throws IOException {
- Connection con = Jsoup.connect(HelloServlet.Url)
+ @ParameterizedTest @MethodSource("helloUrls")
+ void fetchViaProxy(String url) throws IOException {
+ Connection con = Jsoup.connect(url)
.proxy(proxy.hostname, proxy.port);
Connection.Response res = con.execute();
- assertVia(res);
+ if (url.startsWith("http:/")) assertVia(res); // HTTPS CONNECT won't have Via
Document doc = res.parse();
Element p = doc.expectFirst("p");
assertEquals("Hello, World!", p.text());
}
+ private static Stream<String> helloUrls() {
+ return Stream.of(HelloServlet.Url, HelloServlet.TlsUrl);
+ }
+
private static void assertVia(Connection.Response res) {
assertEquals(res.header("Via"), ProxyServlet.Via);
}
@@ -71,5 +78,11 @@
assertVia(largeRes);
assertEquals("Medium HTML", medRes.parse().title());
assertEquals("Large HTML", largeRes.parse().title());
+
+ Connection.Response smedRes = session.newRequest().url(FileServlet.tlsUrlTo("/htmltests/medium.html")).execute();
+ Connection.Response slargeRes = session.newRequest().url(FileServlet.tlsUrlTo("/htmltests/large.html")).execute();
+
+ assertEquals("Medium HTML", smedRes.parse().title());
+ assertEquals("Large HTML", slargeRes.parse().title());
}
}
diff --git a/src/test/java/org/jsoup/integration/TestServer.java b/src/test/java/org/jsoup/integration/TestServer.java
index a615cf8..67b7b84 100644
--- a/src/test/java/org/jsoup/integration/TestServer.java
+++ b/src/test/java/org/jsoup/integration/TestServer.java
@@ -1,75 +1,161 @@
package org.jsoup.integration;
+import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.jsoup.integration.servlets.BaseServlet;
import org.jsoup.integration.servlets.ProxyServlet;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
public class TestServer {
- private static final String localhost = "localhost";
- private static final Server jetty = newServer();
- private static final ServletHandler handler = new ServletHandler();
- static int port;
+ static int Port;
+ static int TlsPort;
- private static final Server proxy = newServer();
- private static final ServletHandler proxyHandler = new ServletHandler();
- private static final ProxySettings proxySettings = new ProxySettings();
+ private static final String Localhost = "localhost";
+ private static final String KeystorePassword = "hunter2";
+
+ private static final Server Jetty = newServer();
+ private static final ServletHandler JettyHandler = new ServletHandler();
+ private static final Server Proxy = newServer();
+ private static final HandlerWrapper ProxyHandler = new HandlerWrapper();
+ private static final ProxySettings ProxySettings = new ProxySettings();
+
private static Server newServer() {
- return new Server(new InetSocketAddress(localhost, 0));
+ return new Server(new InetSocketAddress(Localhost, 0));
}
static {
- jetty.setHandler(handler);
- proxy.setHandler(proxyHandler);
- proxyHandler.addServletWithMapping(ProxyServlet.class, "/*");
+ Jetty.setHandler(JettyHandler);
+ Proxy.setHandler(ProxyHandler);
+
+ // TLS setup:
+ try {
+ File keystoreFile = ParseTest.getFile("/local-cert/server.pfx");
+ if (!keystoreFile.exists()) throw new FileNotFoundException(keystoreFile.toString());
+ addHttpsConnector(keystoreFile, Jetty);
+ setupDefaultTrust(keystoreFile);
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
}
private TestServer() {
}
public static void start() {
- synchronized (jetty) {
- if (jetty.isStarted()) return;
+ synchronized (Jetty) {
+ if (Jetty.isStarted()) return;
try {
- jetty.start(); // jetty will safely no-op a start on an already running instance
- port = ((ServerConnector) jetty.getConnectors()[0]).getLocalPort();
+ Jetty.start();
+ Connector[] jcons = Jetty.getConnectors();
+ Port = ((ServerConnector) jcons[0]).getLocalPort();
+ TlsPort = ((ServerConnector) jcons[1]).getLocalPort();
- proxy.start();
- proxySettings.port = ((ServerConnector) proxy.getConnectors()[0]).getLocalPort();
+ ProxyHandler.setHandler(ProxyServlet.createHandler());
+ Proxy.start();
+ ProxySettings.port = ((ServerConnector) Proxy.getConnectors()[0]).getLocalPort();
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
- public static String map(Class<? extends BaseServlet> servletClass) {
- synchronized (jetty) {
- if (!jetty.isStarted())
+ public static ServletUrls map(Class<? extends BaseServlet> servletClass) {
+ synchronized (Jetty) {
+ if (!Jetty.isStarted())
start(); // if running out of the test cases
String path = "/" + servletClass.getSimpleName();
- handler.addServletWithMapping(servletClass, path + "/*");
- return "http://" + localhost + ":" + port + path;
+ JettyHandler.addServletWithMapping(servletClass, path + "/*");
+ String url = "http://" + Localhost + ":" + Port + path;
+ String tlsUrl = "https://" + Localhost + ":" + TlsPort + path;
+
+ return new ServletUrls(url, tlsUrl);
+ }
+ }
+
+ public static class ServletUrls {
+ public final String url;
+ public final String tlsUrl;
+
+ public ServletUrls(String url, String tlsUrl) {
+ this.url = url;
+ this.tlsUrl = tlsUrl;
}
}
public static ProxySettings proxySettings() {
- synchronized (jetty) {
- if (!jetty.isStarted())
- start(); // if running out of the test cases
+ synchronized (Jetty) {
+ if (!Jetty.isStarted())
+ start();
- return proxySettings;
+ return ProxySettings;
}
}
//public static String proxy
public static class ProxySettings {
- final String hostname = localhost;
+ final String hostname = Localhost;
int port;
}
+
+ private static void addHttpsConnector(File keystoreFile, Server server) {
+ // Cribbed from https://github.com/jetty/jetty.project/blob/jetty-9.4.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+ SslContextFactory sslContextFactory = new SslContextFactory.Server();
+ String path = keystoreFile.getAbsolutePath();
+ sslContextFactory.setKeyStorePath(path);
+ sslContextFactory.setKeyStorePassword(KeystorePassword);
+ sslContextFactory.setKeyManagerPassword(KeystorePassword);
+ sslContextFactory.setTrustStorePath(path);
+ sslContextFactory.setTrustStorePassword(KeystorePassword);
+
+ HttpConfiguration httpConfig = new HttpConfiguration();
+ httpConfig.setSecureScheme("https");
+ HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
+ httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+ ServerConnector sslConnector = new ServerConnector(
+ server,
+ new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
+ new HttpConnectionFactory(httpsConfig));
+ server.addConnector(sslConnector);
+ }
+
+ private static void setupDefaultTrust(File keystoreFile) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException, KeyManagementException {
+ // Configure HttpsUrlConnection (jsoup) to trust (only) this cert
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(Files.newInputStream(keystoreFile.toPath()), KeystorePassword.toCharArray());
+ TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ trustManagerFactory.init(trustStore);
+ TrustManager[] managers = trustManagerFactory.getTrustManagers();
+ SSLContext tls = SSLContext.getInstance("TLS");
+ tls.init(null, managers, null);
+ SSLSocketFactory socketFactory = tls.getSocketFactory();
+ HttpsURLConnection.setDefaultSSLSocketFactory(socketFactory);
+ }
}
diff --git a/src/test/java/org/jsoup/integration/servlets/CookieServlet.java b/src/test/java/org/jsoup/integration/servlets/CookieServlet.java
index fefe3fe..ef5955f 100644
--- a/src/test/java/org/jsoup/integration/servlets/CookieServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/CookieServlet.java
@@ -9,7 +9,13 @@
import java.io.PrintWriter;
public class CookieServlet extends BaseServlet {
- public static final String Url = TestServer.map(CookieServlet.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(CookieServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
public static final String SetCookiesParam = "setCookies";
public static final String LocationParam = "loc";
diff --git a/src/test/java/org/jsoup/integration/servlets/DeflateServlet.java b/src/test/java/org/jsoup/integration/servlets/DeflateServlet.java
index bf321c1..22cf4ea 100644
--- a/src/test/java/org/jsoup/integration/servlets/DeflateServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/DeflateServlet.java
@@ -10,7 +10,13 @@
import java.util.zip.DeflaterOutputStream;
public class DeflateServlet extends BaseServlet {
- public static final String Url = TestServer.map(DeflateServlet.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(DeflateServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
@Override
protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {
diff --git a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java
index 0039c04..6ff31a2 100644
--- a/src/test/java/org/jsoup/integration/servlets/EchoServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/EchoServlet.java
@@ -22,8 +22,14 @@
public class EchoServlet extends BaseServlet {
public static final String CodeParam = "code";
- public static final String Url = TestServer.map(EchoServlet.class);
private static final int DefaultCode = HttpServletResponse.SC_OK;
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(EchoServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
@Override
protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
@@ -114,7 +120,7 @@
// allow the servlet to run as a main program, for local test
public static void main(String[] args) {
TestServer.start();
- System.out.println(Url);
+ System.out.println("Listening on " + Url + " and " + TlsUrl);
}
private static boolean maybeEnableMultipart(HttpServletRequest req) {
diff --git a/src/test/java/org/jsoup/integration/servlets/FileServlet.java b/src/test/java/org/jsoup/integration/servlets/FileServlet.java
index db68e18..a97c43e 100644
--- a/src/test/java/org/jsoup/integration/servlets/FileServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/FileServlet.java
@@ -11,7 +11,13 @@
import java.nio.file.Files;
public class FileServlet extends BaseServlet {
- public static final String Url = TestServer.map(FileServlet.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(FileServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
public static final String ContentTypeParam = "contentType";
public static final String DefaultType = "text/html";
@@ -40,4 +46,8 @@
public static String urlTo(String path) {
return Url + path;
}
+
+ public static String tlsUrlTo(String path) {
+ return TlsUrl + path;
+ }
}
diff --git a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java
index 4c9f380..aaae1fa 100644
--- a/src/test/java/org/jsoup/integration/servlets/HelloServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/HelloServlet.java
@@ -7,7 +7,13 @@
import java.io.IOException;
public class HelloServlet extends BaseServlet {
- public static final String Url = TestServer.map(HelloServlet.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(HelloServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
@Override
protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException {
diff --git a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java
index 67554c5..26b2fef 100644
--- a/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/InterruptedServlet.java
@@ -8,7 +8,13 @@
import java.io.IOException;
public class InterruptedServlet extends BaseServlet {
- public static final String Url = TestServer.map(InterruptedServlet.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(InterruptedServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
public static final String Magnitude = "magnitude";
public static final String Larger = "larger";
diff --git a/src/test/java/org/jsoup/integration/servlets/ProxyServlet.java b/src/test/java/org/jsoup/integration/servlets/ProxyServlet.java
index 16a50a5..5fda42c 100644
--- a/src/test/java/org/jsoup/integration/servlets/ProxyServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/ProxyServlet.java
@@ -1,73 +1,37 @@
package org.jsoup.integration.servlets;
-import org.jsoup.Connection;
-import org.jsoup.Jsoup;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.proxy.AsyncProxyServlet;
+import org.eclipse.jetty.proxy.ConnectHandler;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.servlet.ServletHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
import org.jsoup.integration.TestServer;
-import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-public class ProxyServlet extends BaseServlet{
+public class ProxyServlet extends AsyncProxyServlet {
public static TestServer.ProxySettings ProxySettings = TestServer.proxySettings();
public static String Via = "1.1 jsoup test proxy";
+ public static Handler createHandler() {
+ // ConnectHandler wraps this ProxyServlet and handles CONNECT, which sets up a tunnel for HTTPS requests and is
+ // opaque to the proxy. The ProxyServlet handles simple HTTP requests.
+ ConnectHandler connectHandler = new ConnectHandler();
+ ServletHandler proxyHandler = new ServletHandler();
+ ServletHolder proxyServletHolder = new ServletHolder(ProxyServlet.class); // Holder wraps as it requires maxThreads initialization
+ proxyServletHolder.setAsyncSupported(true);
+ proxyServletHolder.setInitParameter("maxThreads", "8");
+ proxyHandler.addServletWithMapping(proxyServletHolder, "/*");
+ connectHandler.setHandler(proxyHandler);
+
+ return connectHandler;
+ }
+
@Override
- protected void doIt(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {
- StringBuffer urlBuf = req.getRequestURL();
- if (req.getQueryString() != null) {
- urlBuf.append('?').append(req.getQueryString());
- }
- String url = urlBuf.toString();
- //log("Proxying URL: " + url);
-
- Connection.Method method = Enum.valueOf(Connection.Method.class, req.getMethod());
- Connection fetch = Jsoup.connect(url)
- .method(method)
- .followRedirects(false)
- .ignoreHttpErrors(true);
-
- // request headers
- Enumeration<String> headerNames = req.getHeaderNames();
- while (headerNames.hasMoreElements()) {
- String name = headerNames.nextElement();
- Enumeration<String> values = req.getHeaders(name);
- while (values.hasMoreElements()) {
- String value = values.nextElement();
- //System.out.println("Header: " + name + " = " + value);
- fetch.header(name, value); // todo - this invocation will replace existing header, not add
- }
- }
-
- // execute
- Connection.Response fetchRes = fetch.execute();
- res.setStatus(fetchRes.statusCode());
-
- // write the response headers
- res.addHeader("Via", Via);
- for (Map.Entry<String, List<String>> entry : fetchRes.multiHeaders().entrySet()) {
- String header = entry.getKey();
- for (String value : entry.getValue()) {
- res.addHeader(header,value);
- }
- }
-
- // write the body
- ServletOutputStream outputStream = res.getOutputStream();
- BufferedInputStream inputStream = fetchRes.bodyStream();
- byte[] buffer = new byte[1024];
- int bytesRead;
- while ((bytesRead = inputStream.read(buffer)) != -1) {
- outputStream.write(buffer, 0, bytesRead);
- }
-
- outputStream.close();
- inputStream.close();
+ protected void onServerResponseHeaders(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse) {
+ super.onServerResponseHeaders(clientRequest, proxyResponse, serverResponse);
+ proxyResponse.addHeader("Via", Via);
}
}
diff --git a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java
index 1f90c2f..0a937b7 100644
--- a/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java
+++ b/src/test/java/org/jsoup/integration/servlets/RedirectServlet.java
@@ -8,7 +8,13 @@
import java.io.IOException;
public class RedirectServlet extends BaseServlet {
- public static final String Url = TestServer.map(RedirectServlet.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(RedirectServlet.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
public static final String LocationParam = "loc";
public static final String CodeParam = "code";
public static final String SetCookiesParam = "setCookies";
diff --git a/src/test/java/org/jsoup/integration/servlets/SlowRider.java b/src/test/java/org/jsoup/integration/servlets/SlowRider.java
index 84e876e..7298e0b 100644
--- a/src/test/java/org/jsoup/integration/servlets/SlowRider.java
+++ b/src/test/java/org/jsoup/integration/servlets/SlowRider.java
@@ -11,7 +11,13 @@
* Slowly, interminably writes output. For the purposes of testing timeouts and interrupts.
*/
public class SlowRider extends BaseServlet {
- public static final String Url = TestServer.map(SlowRider.class);
+ public static final String Url;
+ public static final String TlsUrl;
+ static {
+ TestServer.ServletUrls urls = TestServer.map(SlowRider.class);
+ Url = urls.url;
+ TlsUrl = urls.tlsUrl;
+ }
private static final int SleepTime = 2000;
public static final String MaxTimeParam = "maxTime";
diff --git a/src/test/resources/local-cert/README.md b/src/test/resources/local-cert/README.md
new file mode 100644
index 0000000..2d4cbe5
--- /dev/null
+++ b/src/test/resources/local-cert/README.md
@@ -0,0 +1,15 @@
+This directory contains resources for a self-signed TLS certificate, used in jsoup's local integration tests.
+
+Create the certificate:
+
+```sh
+openssl genrsa 2048 > server.key
+chmod 400 server.key
+openssl req -new -x509 -config cert.conf -nodes -sha256 -days 36135 -key server.key -out server.crt
+```
+
+Create the Java key store. Used by server, and trusted by client, in `TestServer.java`:
+```sh
+openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -name jsoup -passout pass:hunter2
+keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 -destkeystore server.pfx -deststoretype PKCS12 -srcstorepass hunter2 -deststorepass hunter2
+```
diff --git a/src/test/resources/local-cert/cert.conf b/src/test/resources/local-cert/cert.conf
new file mode 100644
index 0000000..c9019e4
--- /dev/null
+++ b/src/test/resources/local-cert/cert.conf
@@ -0,0 +1,13 @@
+[ req ]
+distinguished_name = subject
+x509_extensions = x509_ext
+prompt = no
+
+[ subject ]
+commonName = jsoup test server
+
+[ x509_ext ]
+subjectAltName = @alternate_names
+
+[ alternate_names ]
+DNS.1 = localhost
diff --git a/src/test/resources/local-cert/server.crt b/src/test/resources/local-cert/server.crt
new file mode 100644
index 0000000..27d549e
--- /dev/null
+++ b/src/test/resources/local-cert/server.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUKEHmb0P5j+5mNjNk/PTdW6t9UTcwDQYJKoZIhvcNAQEL
+BQAwHDEaMBgGA1UEAwwRanNvdXAgdGVzdCBzZXJ2ZXIwIBcNMjMxMTAyMjM0OTE1
+WhgPMjEyMjEwMDkyMzQ5MTVaMBwxGjAYBgNVBAMMEWpzb3VwIHRlc3Qgc2VydmVy
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvkvtYwy7jnSPYM59EVsR
+SjTO8WsXHVs/UJ+Ns+7RiTeb2hUOd4lh38TOh9Yri/7WI5Ejif64FL6b1KEWRe9+
+60QKIOB0+7DUpnXomisD6TytwV8R8BSEZ4vLbMUVizr95Ze+w6SzMPshSvHBMIbU
+RimtmY1jBglHytETRBjO1etG120R1M45GJfxV8rIDOgM6FksOnWLQeKzeGKBf0vs
+5MlTz/GDs/YpXydg779QOmJAQWj78EMdetwmUPwnpC0kaO3dnlD+mzDrfeSkorrp
+5UKij1k4s2tG+E/VIskGyuc/MSU6dc8/ECzuK7c/UjpUz9ohSfLwhSGdjnx0qjXm
+kwIDAQABozcwNTAUBgNVHREEDTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFAWRk6Jd
+PJrlw3uJKEG7JLku9SwsMA0GCSqGSIb3DQEBCwUAA4IBAQAxEXk5d0ACzaxtOF9+
+/XF3Zt8X/eXxyoQUaG2PyfJkN1rnO7zyx/oPIIAckaZev0eFVwOk3M5K4xxYar/Y
+DqdioKwH8qAy4kk7sdCnTU8jlkUMcFqYCt7rLcDviugjg0VO6bYLrq++oeOuDybs
+M7J3CgzPAppSpRoTgss3bGzHt87rWJ2XcHxbE8Gg2GtoZnFpcSHkx40EdlDWN8dm
+/mZlMxjVFdktz9dpqtR4Q4cAbHETomJOHC2AnhEi3PjuYhGHMbIRgtIg0XX4H/0u
+eHVvkb9xJ3SmmdidYTDlOFzLon8NqSZmmt6EDpDio62bDem49jUtnYmxJKXAxhL0
+jnwQ
+-----END CERTIFICATE-----
diff --git a/src/test/resources/local-cert/server.key b/src/test/resources/local-cert/server.key
new file mode 100644
index 0000000..70c498c
--- /dev/null
+++ b/src/test/resources/local-cert/server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+S+1jDLuOdI9g
+zn0RWxFKNM7xaxcdWz9Qn42z7tGJN5vaFQ53iWHfxM6H1iuL/tYjkSOJ/rgUvpvU
+oRZF737rRAog4HT7sNSmdeiaKwPpPK3BXxHwFIRni8tsxRWLOv3ll77DpLMw+yFK
+8cEwhtRGKa2ZjWMGCUfK0RNEGM7V60bXbRHUzjkYl/FXysgM6AzoWSw6dYtB4rN4
+YoF/S+zkyVPP8YOz9ilfJ2Dvv1A6YkBBaPvwQx163CZQ/CekLSRo7d2eUP6bMOt9
+5KSiuunlQqKPWTiza0b4T9UiyQbK5z8xJTp1zz8QLO4rtz9SOlTP2iFJ8vCFIZ2O
+fHSqNeaTAgMBAAECggEACY0zFaEqetyD49aJdYkOJZzf9EMtTlZpp6jSioEGuG33
+nysmZj6ZkItG2I+Z8PVyFyfuUjtcTwJAPRx2yzzZsIJiRcMubAG0ssRBUBevoxHe
+INIeSuAkwzPDmqqLycjEvLTwqM5IBkHcqm/XBBIIbpsh8Q6lNUTa+yWiY20hWKBX
+7I+mNg9qTsGkYCthZVBgkpmg3DCCX4l8hraHhev3KgdpaILaDSVqjd1IBwJ9ynJc
+mJ0/pvIVO7dwxJ7t7b+vNp8iJQjPlOZmz6hWKyFMhxnkOcri3OBYcr1JMkVZ38RD
+OjKhaaCnhhSH+IxwLxQQAs//S+EN3l6kOngN5cZ/aQKBgQDiEqp7kT7nAPRMq9Af
+okomKnQIpAuEfOauzH02PGkVYawCulWmr+FqdUZxz5SgPEp55IyTfD6iPaSb6QcO
+QuH3PvtZyVQv1ZrExquvd/3lS/cQwaDzV4YG46fBbw9K72BHkVV7dkxm+0p4Imid
+2XLRqT86difx1etovb7fzMXsCwKBgQDXfNh5Gk250Upyh3+7FDYr7bOvc0l9y/Xn
+eODM/yRI3MLaGTUXu90MK50AsOqxedvs4x5NvqG/n2Cr536b9C0tr09CfHeGsOMG
+OEfzxMrRv78ItBF7vLELYz1szi6JEZCeK1whgJ1osrTGWAhWkMTIErh3UOfZGgYG
+qFQGRFP8mQKBgG7FlqNVV+z4mru2tBPMAWkSBCj3uG0ChkXADNo2X4cKhK4Rf0Zd
+h6YSMKIzhC+/Wv6+7eKWTlpQugdq9voV64KqaZ5k98s4bs1cS2N+9/kSb8zWE3co
+u5NEmT4+nM+q2xI2NBx6qpULLEIRGhG+KnRw6XpLyubEWsTHtG8UdyZhAoGAVdm5
+bNYb7VICtQpiyyfMRUgYdGgb+XBO8f9ooINt81Fwl++/BUulT3n4vRO/DSIdio0Z
+v6OZUXyvyQ0blgp8DV1w2G46OIE0kX/OusHGhDY+Z7tF0+RjLMRG7pheVeGXmkxw
+EjDphZLdDsB34fUfUQ6US4UCOa5yhCiAAVcrltECgYEAlYNAELPKAcmWd+4G8Fr6
+07dIgJHZ7W45eZwwUwva9t09J/9d4wq7X4GaX98Jejdeh4nTHnBWX49m6EgQ0ccH
+4jcIvTj61aBuDNiW8p85O5gpBrCneFowFHsPElhG2nFSFhGtIST8fkiy5sBwxMFM
+1nauFIaX8tP0NxQDw+PvdDc=
+-----END PRIVATE KEY-----
diff --git a/src/test/resources/local-cert/server.p12 b/src/test/resources/local-cert/server.p12
new file mode 100644
index 0000000..e6804a0
--- /dev/null
+++ b/src/test/resources/local-cert/server.p12
Binary files differ
diff --git a/src/test/resources/local-cert/server.pfx b/src/test/resources/local-cert/server.pfx
new file mode 100644
index 0000000..746aef2
--- /dev/null
+++ b/src/test/resources/local-cert/server.pfx
Binary files differ