api 'ch.qos.logback:logback-core'
api 'com.google.guava:guava'
api 'com.google.protobuf:protobuf-java'
+ api 'com.squareup.okhttp3:okhttp'
api 'commons-codec:commons-codec'
api 'commons-io:commons-io'
api 'commons-lang:commons-lang'
api 'org.sonarsource.update-center:sonar-update-center-common'
api 'org.springframework:spring-context'
api project(':sonar-plugin-api-impl')
+ api project(':sonar-ws')
compileOnlyApi 'com.google.code.findbugs:jsr305'
compileOnlyApi 'com.google.code.gson:gson'
*/
package org.sonar.core.util;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
-import java.net.Authenticator;
-import java.net.HttpURLConnection;
-import java.net.PasswordAuthentication;
-import java.net.Proxy;
-import java.net.ProxySelector;
import java.net.URI;
import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.List;
import java.util.Optional;
-import java.util.zip.GZIPInputStream;
import javax.annotation.Nullable;
import javax.inject.Inject;
-import org.apache.commons.codec.binary.Base64;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
import org.apache.commons.io.IOUtils;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Configuration;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.SonarException;
-import org.sonar.api.utils.log.Loggers;
+import org.sonarqube.ws.client.OkHttpClientBuilder;
import static org.apache.commons.io.FileUtils.copyInputStreamToFile;
import static org.sonar.core.util.FileUtils.deleteQuietly;
*/
public class DefaultHttpDownloader extends HttpDownloader {
- private final BaseHttpDownloader downloader;
- private final Integer readTimeout;
- private final Integer connectTimeout;
+ private final OkHttpClient client;
@Inject
public DefaultHttpDownloader(Server server, Configuration config) {
- this(server, config, null);
- }
-
- public DefaultHttpDownloader(Server server, Configuration config, @Nullable Integer readTimeout) {
- this(server, config, null, readTimeout);
+ this(server, config, null, null);
}
public DefaultHttpDownloader(Server server, Configuration config, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) {
- this.readTimeout = readTimeout;
- this.connectTimeout = connectTimeout;
- downloader = new BaseHttpDownloader(new AuthenticatorFacade(), config, server.getVersion());
- }
-
- public DefaultHttpDownloader(Configuration config) {
- this(config, null);
+ client = buildHttpClient(server, config, connectTimeout, readTimeout);
}
- public DefaultHttpDownloader(Configuration config, @Nullable Integer readTimeout) {
- this(config, null, readTimeout);
+ private static OkHttpClient buildHttpClient(Server server, Configuration config, @Nullable Integer connectTimeout,
+ @Nullable Integer readTimeout) {
+ OkHttpClientBuilder clientBuilder = new OkHttpClientBuilder()
+ .setFollowRedirects(true)
+ .setUserAgent(getUserAgent(server, config));
+ if (connectTimeout != null) {
+ clientBuilder
+ .setConnectTimeoutMs(connectTimeout);
+ }
+ if (readTimeout != null) {
+ clientBuilder
+ .setReadTimeoutMs(readTimeout);
+ }
+ return clientBuilder.build();
}
- public DefaultHttpDownloader(Configuration config, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) {
- this.readTimeout = readTimeout;
- this.connectTimeout = connectTimeout;
- downloader = new BaseHttpDownloader(new AuthenticatorFacade(), config, null);
+ private static String getUserAgent(Server server, Configuration config) {
+ Optional<String> serverId = config.get(CoreProperties.SERVER_ID);
+ if (serverId.isEmpty()) {
+ return String.format("SonarQube %s #", server.getVersion());
+ }
+ return String.format("SonarQube %s # %s", server.getVersion(), serverId.get());
}
@Override
protected String description(URI uri) {
- return String.format("%s (%s)", uri.toString(), getProxySynthesis(uri));
+ return uri.toString();
}
@Override
@Override
protected String readString(URI uri, Charset charset) {
- try {
- return IOUtils.toString(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(), charset);
+ try (Response response = executeCall(uri)) {
+ return IOUtils.toString(response.body().byteStream(), charset);
} catch (IOException e) {
throw failToDownload(uri, e);
}
@Override
public byte[] download(URI uri) {
- try {
- return ByteStreams.toByteArray(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput());
+ try (Response response = executeCall(uri)) {
+ return ByteStreams.toByteArray(response.body().byteStream());
} catch (IOException e) {
throw failToDownload(uri, e);
}
}
- public String getProxySynthesis(URI uri) {
- return BaseHttpDownloader.getProxySynthesis(uri);
- }
-
@Override
public InputStream openStream(URI uri) {
try {
- return downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput();
+ Response response = executeCall(uri);
+ return response.body().byteStream();
} catch (IOException e) {
throw failToDownload(uri, e);
}
@Override
public void download(URI uri, File toFile) {
- try {
- copyInputStreamToFile(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(), toFile);
+ try (Response response = executeCall(uri)) {
+ copyInputStreamToFile(response.body().byteStream(), toFile);
} catch (IOException e) {
deleteQuietly(toFile);
throw failToDownload(uri, e);
}
}
- private SonarException failToDownload(URI uri, IOException e) {
- throw new SonarException(String.format("Fail to download: %s (%s)", uri, getProxySynthesis(uri)), e);
- }
-
- /**
- * Facade to allow unit tests to verify calls to {@link Authenticator#setDefault(Authenticator)}.
- */
- static class AuthenticatorFacade {
- void setDefaultAuthenticator(Authenticator authenticator) {
- Authenticator.setDefault(authenticator);
- }
+ private Response executeCall(URI uri) throws IOException {
+ Request request = new Request.Builder().url(uri.toURL()).get().build();
+ return client.newCall(request).execute();
}
- static class BaseHttpDownloader {
-
- private static final String GET = "GET";
- private static final String HTTP_PROXY_USER = "http.proxyUser";
- private static final String HTTP_PROXY_PASSWORD = "http.proxyPassword";
-
- private String userAgent;
-
- BaseHttpDownloader(AuthenticatorFacade system, Configuration config, @Nullable String userAgent) {
- initProxy(system, config);
- initUserAgent(userAgent, config);
- }
-
- private static void initProxy(AuthenticatorFacade system, Configuration config) {
- // register credentials
- Optional<String> login = config.get(HTTP_PROXY_USER);
- if (login.isPresent()) {
- system.setDefaultAuthenticator(new ProxyAuthenticator(login.get(), config.get(HTTP_PROXY_PASSWORD).orElse(null)));
- }
- }
-
- private void initUserAgent(@Nullable String sonarVersion, Configuration settings) {
- Optional<String> serverId = settings.get(CoreProperties.SERVER_ID);
- userAgent = sonarVersion == null ? "SonarQube" : String.format("SonarQube %s # %s", sonarVersion, serverId.orElse(""));
- System.setProperty("http.agent", userAgent);
- }
-
- private static String getProxySynthesis(URI uri) {
- return getProxySynthesis(uri, ProxySelector.getDefault());
- }
-
- @VisibleForTesting
- static String getProxySynthesis(URI uri, ProxySelector proxySelector) {
- List<Proxy> proxies = proxySelector.select(uri);
- if (proxies.size() == 1 && proxies.get(0).type().equals(Proxy.Type.DIRECT)) {
- return "no proxy";
- }
-
- List<String> descriptions = Lists.newArrayList();
- for (Proxy proxy : proxies) {
- if (proxy.type() != Proxy.Type.DIRECT) {
- descriptions.add(proxy.type() + " proxy: " + proxy.address());
- }
- }
-
- return Joiner.on(", ").join(descriptions);
- }
-
- public HttpInputSupplier newInputSupplier(URI uri, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
- return newInputSupplier(uri, GET, connectTimeoutMillis, readTimeoutMillis);
- }
-
- public HttpInputSupplier newInputSupplier(URI uri, String requestMethod, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
- return newInputSupplier(uri, requestMethod, null, null, connectTimeoutMillis, readTimeoutMillis);
- }
-
- public HttpInputSupplier newInputSupplier(URI uri, String requestMethod, String login, String password, @Nullable Integer connectTimeoutMillis,
- @Nullable Integer readTimeoutMillis) {
- int read = readTimeoutMillis != null ? readTimeoutMillis : DEFAULT_READ_TIMEOUT_IN_MILLISECONDS;
- int connect = connectTimeoutMillis != null ? connectTimeoutMillis : DEFAULT_CONNECT_TIMEOUT_IN_MILLISECONDS;
- return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, connect, read);
- }
-
- private static class HttpInputSupplier {
- private final String login;
- private final String password;
- private final URI uri;
- private final String userAgent;
- private final int connectTimeoutMillis;
- private final int readTimeoutMillis;
- private final String requestMethod;
-
- HttpInputSupplier(URI uri, String requestMethod, String userAgent, String login, String password, int connectTimeoutMillis, int readTimeoutMillis) {
- this.uri = uri;
- this.requestMethod = requestMethod;
- this.userAgent = userAgent;
- this.login = login;
- this.password = password;
- this.readTimeoutMillis = readTimeoutMillis;
- this.connectTimeoutMillis = connectTimeoutMillis;
- }
-
- /**
- * @throws IOException any I/O error, not limited to the network connection
- * @throws HttpException if HTTP response code > 400
- */
- public InputStream getInput() throws IOException {
- Loggers.get(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri, ProxySelector.getDefault()) + ")");
- HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
- connection.setRequestMethod(requestMethod);
- HttpsTrust.INSTANCE.trust(connection);
-
- // allow both GZip and Deflate (ZLib) encodings
- connection.setRequestProperty("Accept-Encoding", "gzip");
- if (!Strings.isNullOrEmpty(login)) {
- String encoded = Base64.encodeBase64String((login + ":" + password).getBytes(StandardCharsets.UTF_8));
- connection.setRequestProperty("Authorization", "Basic " + encoded);
- }
- connection.setConnectTimeout(connectTimeoutMillis);
- connection.setReadTimeout(readTimeoutMillis);
- connection.setUseCaches(true);
- connection.setInstanceFollowRedirects(true);
- connection.setRequestProperty("User-Agent", userAgent);
-
- // establish connection, get response headers
- connection.connect();
-
- // obtain the encoding returned by the server
- String encoding = connection.getContentEncoding();
-
- int responseCode = connection.getResponseCode();
- if (responseCode >= 400) {
- InputStream errorResponse = null;
- try {
- errorResponse = connection.getErrorStream();
- if (errorResponse != null) {
- String errorResponseContent = IOUtils.toString(errorResponse);
- throw new HttpException(uri, responseCode, errorResponseContent);
- }
- throw new HttpException(uri, responseCode);
-
- } finally {
- IOUtils.closeQuietly(errorResponse);
- }
- }
-
- InputStream resultingInputStream;
- // create the appropriate stream wrapper based on the encoding type
- if ("gzip".equalsIgnoreCase(encoding)) {
- resultingInputStream = new GZIPInputStream(connection.getInputStream());
- } else {
- resultingInputStream = connection.getInputStream();
- }
- return resultingInputStream;
- }
- }
- }
-
- static class ProxyAuthenticator extends Authenticator {
- private final PasswordAuthentication auth;
-
- ProxyAuthenticator(String user, @Nullable String password) {
- auth = new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray());
- }
-
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- return auth;
- }
+ private static SonarException failToDownload(URI uri, IOException e) {
+ throw new SonarException(String.format("Fail to download: %s", uri), e);
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.core.util;
-
-import java.net.HttpURLConnection;
-import java.security.KeyManagementException;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
-import java.security.cert.X509Certificate;
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.X509TrustManager;
-
-/**
- * @since 4.0
- */
-class HttpsTrust {
-
- static final HttpsTrust INSTANCE = new HttpsTrust(new Ssl());
-
- static class Ssl {
- SSLSocketFactory newFactory(TrustManager... managers) throws NoSuchAlgorithmException, KeyManagementException {
- SSLContext context = SSLContext.getInstance("TLS");
- context.init(null, managers, new SecureRandom());
- return context.getSocketFactory();
- }
- }
-
- private final SSLSocketFactory socketFactory;
- private final HostnameVerifier hostnameVerifier;
-
- HttpsTrust(Ssl context) {
- this.socketFactory = createSocketFactory(context);
- this.hostnameVerifier = createHostnameVerifier();
- }
-
- void trust(HttpURLConnection connection) {
- if (connection instanceof HttpsURLConnection) {
- HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
- httpsConnection.setSSLSocketFactory(socketFactory);
- httpsConnection.setHostnameVerifier(hostnameVerifier);
- }
- }
-
- /**
- * Trust all certificates
- */
- private static SSLSocketFactory createSocketFactory(Ssl context) {
- try {
- return context.newFactory(new AlwaysTrustManager());
- } catch (Exception e) {
- throw new IllegalStateException("Fail to build SSL factory", e);
- }
- }
-
- /**
- * Trust all hosts
- */
- private static HostnameVerifier createHostnameVerifier() {
- return (hostname, session) -> true;
- }
-
- static class AlwaysTrustManager implements X509TrustManager {
- @Override
- public X509Certificate[] getAcceptedIssuers() {
- return new X509Certificate[0];
- }
-
- @Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) {
- // Do not check
- }
-
- @Override
- public void checkServerTrusted(X509Certificate[] chain, String authType) {
- // Do not check
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.core.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+import java.util.zip.GZIPOutputStream;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.simpleframework.http.Request;
+import org.simpleframework.http.Response;
+import org.simpleframework.http.core.Container;
+import org.simpleframework.http.core.ContainerServer;
+import org.simpleframework.transport.connect.SocketConnection;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.Server;
+import org.sonar.api.utils.SonarException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DefaultHttpDownloaderIT {
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+
+ @Rule
+ public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+ private static SocketConnection socketConnection;
+ private static String baseUrl;
+
+ @BeforeClass
+ public static void startServer() throws IOException {
+ socketConnection = new SocketConnection(new ContainerServer(new Container() {
+ public void handle(Request req, Response resp) {
+ try {
+ if (req.getPath().getPath().contains("/redirect/")) {
+ resp.setCode(303);
+ resp.setValue("Location", "/redirected");
+ } else if (req.getPath().getPath().contains("/timeout/")) {
+ try {
+ Thread.sleep(500);
+ writeDefaultResponse(req, resp);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ } else if (req.getPath().getPath().contains("/gzip/")) {
+ if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
+ throw new IllegalStateException("Should accept gzip");
+ }
+ resp.setValue("Content-Encoding", "gzip");
+ GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
+ gzipOutputStream.write("GZIP response".getBytes());
+ gzipOutputStream.close();
+ } else if (req.getPath().getPath().contains("/redirected")) {
+ resp.getPrintStream().append("redirected");
+ } else {
+ writeDefaultResponse(req, resp);
+ }
+
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ try {
+ resp.close();
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ }));
+ SocketAddress address = socketConnection.connect(new InetSocketAddress("localhost", 0));
+
+ baseUrl = String.format("http://%s:%d", ((InetSocketAddress) address).getAddress().getHostAddress(), ((InetSocketAddress) address).getPort());
+ }
+
+ private static PrintStream writeDefaultResponse(Request req, Response resp) throws IOException {
+ return resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
+ }
+
+ @AfterClass
+ public static void stopServer() throws IOException {
+ if (null != socketConnection) {
+ socketConnection.close();
+ }
+ }
+
+ @Test(timeout = 10000)
+ public void openStream_network_errors() throws IOException, URISyntaxException {
+ // non routable address
+ String url = "http://10.255.255.1";
+
+ assertThatThrownBy(() -> {
+ DefaultHttpDownloader downloader = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig(), 10, 10);
+ downloader.openStream(new URI(url));
+ })
+ .isInstanceOf(SonarException.class)
+ .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
+ @Override
+ public boolean matches(Object ex) {
+ return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
+ }
+
+ @Override
+ public void describeTo(Description arg0) {
+ }
+ });
+ }
+
+ @Test
+ public void downloadBytes() throws URISyntaxException {
+ byte[] bytes = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readBytes(new URI(baseUrl));
+ assertThat(bytes.length).isGreaterThan(10);
+ }
+
+ @Test
+ public void readString() throws URISyntaxException {
+ String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
+ assertThat(text.length()).isGreaterThan(10);
+ }
+
+ @Test
+ public void readGzipString() throws URISyntaxException {
+ String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
+ assertThat(text).isEqualTo("GZIP response");
+ }
+
+ @Test
+ public void readStringWithDefaultTimeout() throws URISyntaxException {
+ String text = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
+ assertThat(text.length()).isGreaterThan(10);
+ }
+
+ @Test
+ public void readStringWithTimeout() throws URISyntaxException {
+ assertThatThrownBy(
+ () -> new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig(), null, 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8))
+ .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
+ @Override
+ public boolean matches(Object ex) {
+ return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
+ }
+
+ @Override
+ public void describeTo(Description arg0) {
+ }
+ });
+ }
+
+ @Test
+ public void downloadToFile() throws URISyntaxException, IOException {
+ File toDir = temporaryFolder.newFolder();
+ File toFile = new File(toDir, "downloadToFile.txt");
+
+ new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
+ assertThat(toFile).exists();
+ assertThat(toFile.length()).isGreaterThan(10L);
+ }
+
+ @Test
+ public void shouldNotCreateFileIfFailToDownload() throws Exception {
+ File toDir = temporaryFolder.newFolder();
+ File toFile = new File(toDir, "downloadToFile.txt");
+
+ try {
+ new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).download(new URI("http://localhost:1"), toFile);
+ } catch (SonarException e) {
+ assertThat(toFile).doesNotExist();
+ }
+ }
+
+ @Test
+ public void userAgent_includes_version_and_SERVER_ID_when_server_is_provided() throws URISyntaxException, IOException {
+ Server server = mock(Server.class);
+ when(server.getVersion()).thenReturn("2.2");
+ MapSettings settings = new MapSettings();
+ settings.setProperty(CoreProperties.SERVER_ID, "blablabla");
+
+ InputStream stream = new DefaultHttpDownloader(server, settings.asConfig()).openStream(new URI(baseUrl));
+ Properties props = new Properties();
+ props.load(stream);
+ stream.close();
+
+ assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 # blablabla");
+ }
+
+ @Test
+ public void userAgent_includes_only_version_when_there_is_no_SERVER_ID_and_server_is_provided() throws URISyntaxException, IOException {
+ Server server = mock(Server.class);
+ when(server.getVersion()).thenReturn("2.2");
+
+ InputStream stream = new DefaultHttpDownloader(server, new MapSettings().asConfig()).openStream(new URI(baseUrl));
+ Properties props = new Properties();
+ props.load(stream);
+ stream.close();
+
+ assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 #");
+ }
+
+ @Test
+ public void followRedirect() throws URISyntaxException {
+ String content = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
+ assertThat(content).isEqualTo("redirected");
+ }
+
+ @Test
+ public void supported_schemes() {
+ assertThat(new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
+ }
+
+ @Test
+ public void uri_description() throws URISyntaxException {
+ String description = new DefaultHttpDownloader(mock(Server.class), new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
+ assertThat(description).isEqualTo("http://sonarsource.org");
+ }
+
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.core.util;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.InetSocketAddress;
-import java.net.PasswordAuthentication;
-import java.net.Proxy;
-import java.net.ProxySelector;
-import java.net.SocketAddress;
-import java.net.SocketTimeoutException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Properties;
-import java.util.zip.GZIPOutputStream;
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.TemporaryFolder;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.simpleframework.http.Request;
-import org.simpleframework.http.Response;
-import org.simpleframework.http.core.Container;
-import org.simpleframework.http.core.ContainerServer;
-import org.simpleframework.transport.connect.SocketConnection;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.config.internal.MapSettings;
-import org.sonar.api.platform.Server;
-import org.sonar.api.utils.SonarException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-public class DefaultHttpDownloaderTest {
-
- @Rule
- public TemporaryFolder temporaryFolder = new TemporaryFolder();
-
-
- @Rule
- public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
- private static SocketConnection socketConnection;
- private static String baseUrl;
-
- @BeforeClass
- public static void startServer() throws IOException {
- socketConnection = new SocketConnection(new ContainerServer(new Container() {
- public void handle(Request req, Response resp) {
- try {
- if (req.getPath().getPath().contains("/redirect/")) {
- resp.setCode(303);
- resp.setValue("Location", "/");
- } else {
- if (req.getPath().getPath().contains("/timeout/")) {
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- throw new IllegalStateException(e);
- }
- }
- if (req.getPath().getPath().contains("/gzip/")) {
- if (!"gzip".equals(req.getValue("Accept-Encoding"))) {
- throw new IllegalStateException("Should accept gzip");
- }
- resp.setValue("Content-Encoding", "gzip");
- GZIPOutputStream gzipOutputStream = new GZIPOutputStream(resp.getOutputStream());
- gzipOutputStream.write("GZIP response".getBytes());
- gzipOutputStream.close();
- } else {
- resp.getPrintStream().append("agent=" + req.getValues("User-Agent").get(0));
- }
- }
- } catch (IOException e) {
- throw new IllegalStateException(e);
- } finally {
- try {
- resp.close();
- } catch (IOException ignored) {
- }
- }
- }
- }));
- SocketAddress address = socketConnection.connect(new InetSocketAddress("localhost", 0));
-
- baseUrl = String.format("http://%s:%d", ((InetSocketAddress) address).getAddress().getHostAddress(), ((InetSocketAddress) address).getPort());
- }
-
- @AfterClass
- public static void stopServer() throws IOException {
- if (null != socketConnection) {
- socketConnection.close();
- }
- }
-
- @Test(timeout = 10000)
- public void openStream_network_errors() throws IOException, URISyntaxException {
- // non routable address
- String url = "http://10.255.255.1";
-
- assertThatThrownBy(() -> {
- DefaultHttpDownloader downloader = new DefaultHttpDownloader(new MapSettings().asConfig(), 10, 50000);
- downloader.openStream(new URI(url));
- })
- .isInstanceOf(SonarException.class)
- .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
- @Override
- public boolean matches(Object ex) {
- return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
- }
-
- @Override
- public void describeTo(Description arg0) {
- }
- });
- }
-
- @Test
- public void downloadBytes() throws URISyntaxException {
- byte[] bytes = new DefaultHttpDownloader(new MapSettings().asConfig()).readBytes(new URI(baseUrl));
- assertThat(bytes.length).isGreaterThan(10);
- }
-
- @Test
- public void readString() throws URISyntaxException {
- String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
- assertThat(text.length()).isGreaterThan(10);
- }
-
- @Test
- public void readGzipString() throws URISyntaxException {
- String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/gzip/"), StandardCharsets.UTF_8);
- assertThat(text).isEqualTo("GZIP response");
- }
-
- @Test
- public void readStringWithDefaultTimeout() throws URISyntaxException {
- String text = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
- assertThat(text.length()).isGreaterThan(10);
- }
-
- @Test
- public void readStringWithTimeout() throws URISyntaxException {
- assertThatThrownBy(() -> new DefaultHttpDownloader(new MapSettings().asConfig(), 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8))
- .isEqualToComparingFieldByField(new BaseMatcher<Exception>() {
- @Override
- public boolean matches(Object ex) {
- return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
- }
-
- @Override
- public void describeTo(Description arg0) {
- }
- });
- }
-
- @Test
- public void downloadToFile() throws URISyntaxException, IOException {
- File toDir = temporaryFolder.newFolder();
- File toFile = new File(toDir, "downloadToFile.txt");
-
- new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI(baseUrl), toFile);
- assertThat(toFile).exists();
- assertThat(toFile.length()).isGreaterThan(10L);
- }
-
- @Test
- public void shouldNotCreateFileIfFailToDownload() throws Exception {
- File toDir = temporaryFolder.newFolder();
- File toFile = new File(toDir, "downloadToFile.txt");
-
- try {
- int port = new InetSocketAddress("localhost", 0).getPort();
- new DefaultHttpDownloader(new MapSettings().asConfig()).download(new URI("http://localhost:" + port), toFile);
- } catch (SonarException e) {
- assertThat(toFile).doesNotExist();
- }
- }
-
- @Test
- public void userAgent_includes_version_and_SERVER_ID_when_server_is_provided() throws URISyntaxException, IOException {
- Server server = mock(Server.class);
- when(server.getVersion()).thenReturn("2.2");
- MapSettings settings = new MapSettings();
- settings.setProperty(CoreProperties.SERVER_ID, "blablabla");
-
- InputStream stream = new DefaultHttpDownloader(server, settings.asConfig()).openStream(new URI(baseUrl));
- Properties props = new Properties();
- props.load(stream);
- stream.close();
-
- assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 # blablabla");
- }
-
- @Test
- public void userAgent_includes_only_version_when_there_is_no_SERVER_ID_and_server_is_provided() throws URISyntaxException, IOException {
- Server server = mock(Server.class);
- when(server.getVersion()).thenReturn("2.2");
-
- InputStream stream = new DefaultHttpDownloader(server, new MapSettings().asConfig()).openStream(new URI(baseUrl));
- Properties props = new Properties();
- props.load(stream);
- stream.close();
-
- assertThat(props.getProperty("agent")).isEqualTo("SonarQube 2.2 #");
- }
-
- @Test
- public void userAgent_is_static_value_when_server_is_not_provided() throws URISyntaxException, IOException {
- InputStream stream = new DefaultHttpDownloader(new MapSettings().asConfig()).openStream(new URI(baseUrl));
- Properties props = new Properties();
- props.load(stream);
- stream.close();
-
- assertThat(props.getProperty("agent")).isEqualTo("SonarQube");
- }
-
- @Test
- public void followRedirect() throws URISyntaxException {
- String content = new DefaultHttpDownloader(new MapSettings().asConfig()).readString(new URI(baseUrl + "/redirect/"), StandardCharsets.UTF_8);
- assertThat(content).contains("agent");
- }
-
- @Test
- public void shouldGetDirectProxySynthesis() throws URISyntaxException {
- ProxySelector proxySelector = mock(ProxySelector.class);
- when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(Proxy.NO_PROXY));
- assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("no proxy");
- }
-
- @Test
- public void shouldGetProxySynthesis() throws URISyntaxException {
- ProxySelector proxySelector = mock(ProxySelector.class);
- when(proxySelector.select(any(URI.class))).thenReturn(Arrays.asList(new FakeProxy()));
- assertThat(DefaultHttpDownloader.BaseHttpDownloader.getProxySynthesis(new URI("http://an_url"), proxySelector)).isEqualTo("HTTP proxy: /123.45.67.89:4040");
- }
-
- @Test
- public void supported_schemes() {
- assertThat(new DefaultHttpDownloader(new MapSettings().asConfig()).getSupportedSchemes()).contains("http");
- }
-
- @Test
- public void uri_description() throws URISyntaxException {
- String description = new DefaultHttpDownloader(new MapSettings().asConfig()).description(new URI("http://sonarsource.org"));
- assertThat(description).matches("http://sonarsource.org \\(.*\\)");
- }
-
- @Test
- public void configure_http_proxy_credentials() {
- DefaultHttpDownloader.AuthenticatorFacade system = mock(DefaultHttpDownloader.AuthenticatorFacade.class);
- MapSettings settings = new MapSettings();
- settings.setProperty("https.proxyHost", "1.2.3.4");
- settings.setProperty("http.proxyUser", "the_login");
- settings.setProperty("http.proxyPassword", "the_passwd");
-
- new DefaultHttpDownloader.BaseHttpDownloader(system, settings.asConfig(), null);
-
- verify(system).setDefaultAuthenticator(argThat(authenticator -> {
- DefaultHttpDownloader.ProxyAuthenticator a = (DefaultHttpDownloader.ProxyAuthenticator) authenticator;
- PasswordAuthentication authentication = a.getPasswordAuthentication();
- return authentication.getUserName().equals("the_login") &&
- new String(authentication.getPassword()).equals("the_passwd");
- }));
- }
-
- private static class FakeProxy extends Proxy {
- FakeProxy() {
- super(Type.HTTP, new InetSocketAddress("123.45.67.89", 4040));
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.core.util;
-
-import java.io.IOException;
-import java.net.URL;
-import java.security.KeyManagementException;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.TrustManager;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class HttpsTrustTest {
- @Test
- public void trustAllHosts() throws Exception {
- HttpsURLConnection connection = newHttpsConnection();
- HttpsTrust.INSTANCE.trust(connection);
-
- assertThat(connection.getHostnameVerifier()).isNotNull();
- assertThat(connection.getHostnameVerifier().verify("foo", null)).isTrue();
- }
-
- @Test
- public void singleHostnameVerifier() throws Exception {
- HttpsURLConnection connection1 = newHttpsConnection();
- HttpsTrust.INSTANCE.trust(connection1);
- HttpsURLConnection connection2 = newHttpsConnection();
- HttpsTrust.INSTANCE.trust(connection2);
-
- assertThat(connection1.getHostnameVerifier()).isSameAs(connection2.getHostnameVerifier());
- }
-
- @Test
- public void trustAllCerts() throws Exception {
- HttpsURLConnection connection1 = newHttpsConnection();
- HttpsTrust.INSTANCE.trust(connection1);
-
- assertThat(connection1.getSSLSocketFactory()).isNotNull();
- assertThat(connection1.getSSLSocketFactory().getDefaultCipherSuites()).isNotEmpty();
- }
-
- @Test
- public void singleSslFactory() throws Exception {
- HttpsURLConnection connection1 = newHttpsConnection();
- HttpsTrust.INSTANCE.trust(connection1);
- HttpsURLConnection connection2 = newHttpsConnection();
- HttpsTrust.INSTANCE.trust(connection2);
-
- assertThat(connection1.getSSLSocketFactory()).isSameAs(connection2.getSSLSocketFactory());
- }
-
- @Test
- public void testAlwaysTrustManager() {
- HttpsTrust.AlwaysTrustManager manager = new HttpsTrust.AlwaysTrustManager();
- assertThat(manager.getAcceptedIssuers()).isEmpty();
- // does nothing
- manager.checkClientTrusted(null, null);
- manager.checkServerTrusted(null, null);
- }
-
- @Test
- public void failOnError() throws Exception {
- HttpsTrust.Ssl context = mock(HttpsTrust.Ssl.class);
- KeyManagementException cause = new KeyManagementException("foo");
- when(context.newFactory(any(TrustManager.class))).thenThrow(cause);
-
- try {
- new HttpsTrust(context);
- fail();
- } catch (IllegalStateException e) {
- assertThat(e.getMessage()).isEqualTo("Fail to build SSL factory");
- assertThat(e.getCause()).isSameAs(cause);
- }
- }
-
- private HttpsURLConnection newHttpsConnection() throws IOException {
- return (HttpsURLConnection) new URL("https://localhost").openConnection();
- }
-}