/*
* SonarQube
* Copyright (C) 2009-2017 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.Authenticator;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.ProxySelector;
import java.net.SocketAddress;
import java.net.SocketException;
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.hamcrest.TypeSafeMatcher;
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.ExpectedException;
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.transport.connect.SocketConnection;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.MapSettings;
import org.sonar.api.config.Settings;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.SonarException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.internal.matchers.ThrowableCauseMatcher.hasCause;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.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 ExpectedException thrown = ExpectedException.none();
@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 Container() {
public void handle(Request req, Response resp) {
try {
if (req.getPath().getPath().contains("/redirect/")) {
resp.setCode(303);
resp.add("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.set("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";
thrown.expect(SonarException.class);
thrown.expect(hasCause(new BaseMatcher<Exception>() {
@Override
public boolean matches(Object ex) {
return
// Java 8
ex instanceof NoRouteToHostException || ex instanceof SocketException
// Java 7 or before
|| ex instanceof SocketTimeoutException;
}
@Override
public void describeTo(Description arg0) {
}
}));
DefaultHttpDownloader downloader = new DefaultHttpDownloader(new MapSettings(), 10, 50000);
downloader.openStream(new URI(url));
}
@Test
public void downloadBytes() throws URISyntaxException {
byte[] bytes = new DefaultHttpDownloader(new MapSettings()).readBytes(new URI(baseUrl));
assertThat(bytes.length).isGreaterThan(10);
}
@Test
public void readString() throws URISyntaxException {
String text = new DefaultHttpDownloader(new MapSettings()).readString(new URI(baseUrl), StandardCharsets.UTF_8);
assertThat(text.length()).isGreaterThan(10);
}
@Test
public void readGzipString() throws URISyntaxException {
String text = new DefaultHttpDownloader(new MapSettings()).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()).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
assertThat(text.length()).isGreaterThan(10);
}
@Test
public void readStringWithTimeout() throws URISyntaxException {
thrown.expect(new BaseMatcher<Exception>() {
@Override
public boolean matches(Object ex) {
return ex instanceof SonarException && ((SonarException) ex).getCause() instanceof SocketTimeoutException;
}
@Override
public void describeTo(Description arg0) {
}
});
new DefaultHttpDownloader(new MapSettings(), 50).readString(new URI(baseUrl + "/timeout/"), StandardCharsets.UTF_8);
}
@Test
public void downloadToFile() throws URISyntaxException, IOException {
File toDir = temporaryFolder.newFolder();
File toFile = new File(toDir, "downloadToFile.txt");
new DefaultHttpDownloader(new MapSettings()).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()).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).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()).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()).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()).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.<Proxy>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()).getSupportedSchemes()).contains("http");
}
@Test
public void uri_description() throws URISyntaxException {
String description = new DefaultHttpDownloader(new MapSettings()).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);
Settings 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, null);
verify(system).setDefaultAuthenticator(argThat(new TypeSafeMatcher<Authenticator>() {
@Override
protected boolean matchesSafely(Authenticator authenticator) {
DefaultHttpDownloader.ProxyAuthenticator a = (DefaultHttpDownloader.ProxyAuthenticator) authenticator;
PasswordAuthentication authentication = a.getPasswordAuthentication();
return authentication.getUserName().equals("the_login") &&
new String(authentication.getPassword()).equals("the_passwd");
}
@Override
public void describeTo(Description description) {
}
}));
}
private static class FakeProxy extends Proxy {
FakeProxy() {
super(Type.HTTP, new InetSocketAddress("123.45.67.89", 4040));
}
}
}