From 6fa8c9ebd2c3758901879863c99b96934064bbe2 Mon Sep 17 00:00:00 2001 From: Andrei Kliuchnikau Date: Wed, 28 Feb 2024 15:15:02 +0300 Subject: [PATCH] Add ability to set custom HostnameVerifier --- src/main/java/org/jsoup/Connection.java | 20 ++++++++++++++ .../java/org/jsoup/helper/HttpConnection.java | 26 +++++++++++++++++-- .../org/jsoup/helper/HttpConnectionTest.java | 8 ++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/jsoup/Connection.java b/src/main/java/org/jsoup/Connection.java index 883844ebe8..8c5b043c2d 100644 --- a/src/main/java/org/jsoup/Connection.java +++ b/src/main/java/org/jsoup/Connection.java @@ -6,6 +6,7 @@ import org.jsoup.parser.StreamParser; import org.jspecify.annotations.Nullable; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedInputStream; import java.io.IOException; @@ -200,6 +201,13 @@ default Connection newRequest(URL url) { */ Connection sslSocketFactory(SSLSocketFactory sslSocketFactory); + /** + * Set a custom hostname verifier to verify the hostname during handshake + * @param hostnameVerifier hostname verifier + * @return this Connection, for chaining + */ + Connection hostnameVerifier(HostnameVerifier hostnameVerifier); + /** * Add a request data parameter. Request parameters are sent in the request query string for GETs, and in the * request body for POSTs. A request may have multiple values of the same name. @@ -725,6 +733,18 @@ interface Request extends Base { */ void sslSocketFactory(SSLSocketFactory sslSocketFactory); + /** + * Get the current hostname verifier, if any. + * @return hostname verifier if set, null otherwise + */ + @Nullable HostnameVerifier hostnameVerifier(); + + /** + * Set a custom hostname verifier to verify the hostname during handshake + * @param hostnameVerifier hostname verifier + */ + void hostnameVerifier(HostnameVerifier hostnameVerifier); + /** * Add a data parameter to the request * @param keyval data to add. diff --git a/src/main/java/org/jsoup/helper/HttpConnection.java b/src/main/java/org/jsoup/helper/HttpConnection.java index 1ca31d321b..61fb1db9b5 100644 --- a/src/main/java/org/jsoup/helper/HttpConnection.java +++ b/src/main/java/org/jsoup/helper/HttpConnection.java @@ -14,6 +14,7 @@ import org.jsoup.parser.TokenQueue; import org.jspecify.annotations.Nullable; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLSocketFactory; import java.io.BufferedInputStream; @@ -222,6 +223,12 @@ public Connection sslSocketFactory(SSLSocketFactory sslSocketFactory) { return this; } + @Override + public Connection hostnameVerifier(HostnameVerifier hostnameVerifier) { + req.hostnameVerifier(hostnameVerifier); + return this; + } + @Override public Connection data(String key, String filename, InputStream inputStream) { req.data(KeyVal.create(key, filename, inputStream)); @@ -608,6 +615,7 @@ public static class Request extends HttpConnection.Base impl private CookieManager cookieManager; private @Nullable RequestAuthenticator authenticator; private volatile boolean executing = false; + private @Nullable HostnameVerifier hostnameVerifier; Request() { super(); @@ -708,6 +716,14 @@ public void sslSocketFactory(SSLSocketFactory sslSocketFactory) { this.sslSocketFactory = sslSocketFactory; } + public HostnameVerifier hostnameVerifier() { + return hostnameVerifier; + } + + public void hostnameVerifier(HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + } + @Override public Connection.Request ignoreHttpErrors(boolean ignoreHttpErrors) { this.ignoreHttpErrors = ignoreHttpErrors; @@ -1062,8 +1078,14 @@ private static HttpURLConnection createConnection(HttpConnection.Request req) th conn.setConnectTimeout(req.timeout()); conn.setReadTimeout(req.timeout() / 2); // gets reduced after connection is made and status is read - if (req.sslSocketFactory() != null && conn instanceof HttpsURLConnection) - ((HttpsURLConnection) conn).setSSLSocketFactory(req.sslSocketFactory()); + if (conn instanceof HttpsURLConnection) { + HttpsURLConnection httpsConnection = (HttpsURLConnection) conn; + + if (req.sslSocketFactory() != null) + httpsConnection.setSSLSocketFactory(req.sslSocketFactory()); + if (req.hostnameVerifier() != null) + httpsConnection.setHostnameVerifier(req.hostnameVerifier()); + } if (req.authenticator != null) AuthenticationHandler.handler.enable(req.authenticator, conn); // removed in finally if (req.method().hasBody()) diff --git a/src/test/java/org/jsoup/helper/HttpConnectionTest.java b/src/test/java/org/jsoup/helper/HttpConnectionTest.java index 5757d1d27a..b9dbc06f6d 100644 --- a/src/test/java/org/jsoup/helper/HttpConnectionTest.java +++ b/src/test/java/org/jsoup/helper/HttpConnectionTest.java @@ -6,6 +6,7 @@ import org.jsoup.integration.ParseTest; import org.junit.jupiter.api.Test; +import javax.net.ssl.HostnameVerifier; import java.io.IOException; import java.net.Authenticator; import java.net.MalformedURLException; @@ -256,6 +257,13 @@ public void caseInsensitiveHeaders(Locale locale) { assertEquals("foo", con.request().requestBody()); } + @Test public void hostnameVerifier() { + Connection con = HttpConnection.connect("http://example.com/"); + HostnameVerifier hostnameVerifier = (hostname, session) -> false; + con.hostnameVerifier(hostnameVerifier); + assertEquals(hostnameVerifier, con.request().hostnameVerifier()); + } + @Test public void encodeUrl() throws MalformedURLException { URL url1 = new URL("https://test.com/foo%20bar/%5BOne%5D?q=white+space#frag"); URL url2 = new UrlBuilder(url1).build();