From b6a1d4abe8bd865d64efd9f60ed721c6622eaaa3 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Sat, 10 Dec 2022 09:21:56 -0800 Subject: [PATCH 1/3] Provide native Java 11 HTTP client versions of `FormValidation#URLCheck` methods --- .../main/java/hudson/ProxyConfiguration.java | 4 +- .../main/java/hudson/util/FormValidation.java | 57 ++++++++++++++++++- .../java/hudson/util/FormValidationTest.java | 23 ++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/hudson/ProxyConfiguration.java b/core/src/main/java/hudson/ProxyConfiguration.java index dbe08043f19a..212d97bd4a9f 100644 --- a/core/src/main/java/hudson/ProxyConfiguration.java +++ b/core/src/main/java/hudson/ProxyConfiguration.java @@ -357,7 +357,7 @@ public static InputStream getInputStream(URL url) throws IOException { /** * Return a new {@link HttpClient} with Jenkins-specific default settings. * - *

Equivalent to {@code newHttpClientBuilder().build()}. + *

Equivalent to {@code newHttpClientBuilder().followRedirects(HttpClient.Redirect.NORMAL).build()}. * *

The Jenkins-specific default settings include a proxy server and proxy authentication (as * configured by {@link ProxyConfiguration}) and a connection timeout (as configured by {@link @@ -367,7 +367,7 @@ public static InputStream getInputStream(URL url) throws IOException { * @since TODO */ public static HttpClient newHttpClient() { - return newHttpClientBuilder().build(); + return newHttpClientBuilder().followRedirects(HttpClient.Redirect.NORMAL).build(); } /** diff --git a/core/src/main/java/hudson/util/FormValidation.java b/core/src/main/java/hudson/util/FormValidation.java index 931e47ccf0ed..fd8bc019a49b 100644 --- a/core/src/main/java/hudson/util/FormValidation.java +++ b/core/src/main/java/hudson/util/FormValidation.java @@ -44,14 +44,19 @@ import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Base64; import java.util.Collection; import java.util.List; import java.util.Locale; +import java.util.stream.Stream; import javax.servlet.ServletException; import jenkins.model.Jenkins; import jenkins.util.SystemProperties; @@ -461,10 +466,45 @@ public static FormValidation validateBase64(String value, boolean allowWhitespac * This allows the check method to call various utility methods in a concise syntax. */ public abstract static class URLCheck { + /** + * Open the given URI and read text content from it. This method honors the Content-type + * header. + * + * @throws IOException if the URI scheme is not supported, the connection was interrupted, + * or the response was an error + */ + protected Stream open(URI uri) throws IOException { + HttpClient httpClient = ProxyConfiguration.newHttpClient(); + HttpRequest httpRequest; + try { + httpRequest = ProxyConfiguration.newHttpRequestBuilder(uri).GET().build(); + } catch (IllegalArgumentException e) { + throw new IOException(e); + } + java.net.http.HttpResponse> httpResponse; + try { + httpResponse = + httpClient.send(httpRequest, java.net.http.HttpResponse.BodyHandlers.ofLines()); + } catch (InterruptedException e) { + throw new IOException(e); + } + if (httpResponse.statusCode() != HttpURLConnection.HTTP_OK) { + throw new IOException( + "Server returned HTTP response code " + + httpResponse.statusCode() + + " for URI " + + uri); + } + return httpResponse.body(); + } + /** * Opens the given URL and reads text content from it. * This method honors Content-type header. + * + * @deprecated use {@link #open(URI)} */ + @Deprecated protected BufferedReader open(URL url) throws IOException { // use HTTP content type to find out the charset. URLConnection con = ProxyConfiguration.open(url); @@ -475,11 +515,24 @@ protected BufferedReader open(URL url) throws IOException { new InputStreamReader(con.getInputStream(), getCharset(con))); } + /** + * Find the string literal from the given stream of lines. + * + * @return true if found, false otherwise + */ + protected boolean findText(Stream in, String literal) { + try (in) { + return in.anyMatch(line -> line.contains(literal)); + } + } + /** * Finds the string literal from the given reader. * @return * true if found, false otherwise. + * @deprecated use {@link #findText(Stream, String)} */ + @Deprecated protected boolean findText(BufferedReader in, String literal) throws IOException { String line; while ((line = in.readLine()) != null) @@ -499,9 +552,9 @@ protected FormValidation handleIOException(String url, IOException e) throws IOE // any invalid URL comes here if (e.getMessage().equals(url)) // Sun JRE (and probably others too) often return just the URL in the error. - return error("Unable to connect " + url); + return error("Unable to connect " + url, e); else - return error(e.getMessage()); + return error(e.getMessage(), e); } /** diff --git a/test/src/test/java/hudson/util/FormValidationTest.java b/test/src/test/java/hudson/util/FormValidationTest.java index 071867f77ed2..b14712104206 100644 --- a/test/src/test/java/hudson/util/FormValidationTest.java +++ b/test/src/test/java/hudson/util/FormValidationTest.java @@ -4,6 +4,9 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import java.io.IOException; +import java.net.URI; +import javax.servlet.ServletException; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; @@ -31,4 +34,24 @@ public void testValidateExecutableWithoutFix() { String failMessage = "There's no such executable git in PATH:"; assertThat(actual, not(is(FormValidation.error(failMessage)))); } + + @Test + public void testUrlCheck() throws IOException, ServletException { + FormValidation.URLCheck urlCheck = new FormValidation.URLCheck() { + @Override + protected FormValidation check() throws ServletException, IOException { + String uri = "https://www.jenkins.io/"; + try { + if (findText(open(URI.create(uri)), "Jenkins")) { + return FormValidation.ok(); + } else { + return FormValidation.error("This is a valid URI but it does not look like Jenkins"); + } + } catch (IOException e) { + return handleIOException(uri, e); + } + } + }; + assertThat(urlCheck.check(), is(FormValidation.ok())); + } } From a9297b7d849a5320d7cd7f06548df9c3920d3968 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Sat, 10 Dec 2022 09:30:34 -0800 Subject: [PATCH 2/3] Test does not need JenkinsRule so can be in core/src/test/java/ --- .../java/hudson/util/FormValidationTest.java | 24 +++++++++++++++++++ .../java/hudson/util/FormValidationTest.java | 23 ------------------ 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/core/src/test/java/hudson/util/FormValidationTest.java b/core/src/test/java/hudson/util/FormValidationTest.java index 1862244d5e53..2d7f2f2576f2 100644 --- a/core/src/test/java/hudson/util/FormValidationTest.java +++ b/core/src/test/java/hudson/util/FormValidationTest.java @@ -27,11 +27,15 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.IOException; +import java.net.URI; import java.util.Arrays; +import javax.servlet.ServletException; import org.junit.Test; /** @@ -116,4 +120,24 @@ public void formValidationException() { FormValidation fv = FormValidation.error(new Exception(" Date: Sat, 10 Dec 2022 09:39:47 -0800 Subject: [PATCH 3/3] Add since tags --- core/src/main/java/hudson/util/FormValidation.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/hudson/util/FormValidation.java b/core/src/main/java/hudson/util/FormValidation.java index fd8bc019a49b..3b221affef27 100644 --- a/core/src/main/java/hudson/util/FormValidation.java +++ b/core/src/main/java/hudson/util/FormValidation.java @@ -472,6 +472,7 @@ public abstract static class URLCheck { * * @throws IOException if the URI scheme is not supported, the connection was interrupted, * or the response was an error + * @since TODO */ protected Stream open(URI uri) throws IOException { HttpClient httpClient = ProxyConfiguration.newHttpClient(); @@ -519,6 +520,7 @@ protected BufferedReader open(URL url) throws IOException { * Find the string literal from the given stream of lines. * * @return true if found, false otherwise + * @since TODO */ protected boolean findText(Stream in, String literal) { try (in) {