Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

DefaultHttpDownloader.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.core.util;
  21. import com.google.common.annotations.VisibleForTesting;
  22. import com.google.common.base.Joiner;
  23. import com.google.common.base.Strings;
  24. import com.google.common.collect.Lists;
  25. import com.google.common.io.ByteStreams;
  26. import java.io.File;
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.net.Authenticator;
  30. import java.net.HttpURLConnection;
  31. import java.net.PasswordAuthentication;
  32. import java.net.Proxy;
  33. import java.net.ProxySelector;
  34. import java.net.URI;
  35. import java.nio.charset.Charset;
  36. import java.nio.charset.StandardCharsets;
  37. import java.util.List;
  38. import java.util.Optional;
  39. import java.util.zip.GZIPInputStream;
  40. import javax.annotation.Nullable;
  41. import org.apache.commons.codec.binary.Base64;
  42. import org.apache.commons.io.IOUtils;
  43. import org.sonar.api.CoreProperties;
  44. import org.sonar.api.config.Configuration;
  45. import org.sonar.api.platform.Server;
  46. import org.sonar.api.utils.HttpDownloader;
  47. import org.sonar.api.utils.SonarException;
  48. import org.sonar.api.utils.log.Loggers;
  49. import static org.apache.commons.io.FileUtils.copyInputStreamToFile;
  50. import static org.sonar.core.util.FileUtils.deleteQuietly;
  51. /**
  52. * This component downloads HTTP files
  53. *
  54. * @since 2.2
  55. */
  56. public class DefaultHttpDownloader extends HttpDownloader {
  57. private final BaseHttpDownloader downloader;
  58. private final Integer readTimeout;
  59. private final Integer connectTimeout;
  60. public DefaultHttpDownloader(Server server, Configuration config) {
  61. this(server, config, null);
  62. }
  63. public DefaultHttpDownloader(Server server, Configuration config, @Nullable Integer readTimeout) {
  64. this(server, config, null, readTimeout);
  65. }
  66. public DefaultHttpDownloader(Server server, Configuration config, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) {
  67. this.readTimeout = readTimeout;
  68. this.connectTimeout = connectTimeout;
  69. downloader = new BaseHttpDownloader(new AuthenticatorFacade(), config, server.getVersion());
  70. }
  71. public DefaultHttpDownloader(Configuration config) {
  72. this(config, null);
  73. }
  74. public DefaultHttpDownloader(Configuration config, @Nullable Integer readTimeout) {
  75. this(config, null, readTimeout);
  76. }
  77. public DefaultHttpDownloader(Configuration config, @Nullable Integer connectTimeout, @Nullable Integer readTimeout) {
  78. this.readTimeout = readTimeout;
  79. this.connectTimeout = connectTimeout;
  80. downloader = new BaseHttpDownloader(new AuthenticatorFacade(), config, null);
  81. }
  82. @Override
  83. protected String description(URI uri) {
  84. return String.format("%s (%s)", uri.toString(), getProxySynthesis(uri));
  85. }
  86. @Override
  87. protected String[] getSupportedSchemes() {
  88. return new String[] {"http", "https"};
  89. }
  90. @Override
  91. protected byte[] readBytes(URI uri) {
  92. return download(uri);
  93. }
  94. @Override
  95. protected String readString(URI uri, Charset charset) {
  96. try {
  97. return IOUtils.toString(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(), charset);
  98. } catch (IOException e) {
  99. throw failToDownload(uri, e);
  100. }
  101. }
  102. @Override
  103. public String downloadPlainText(URI uri, String encoding) {
  104. return readString(uri, Charset.forName(encoding));
  105. }
  106. @Override
  107. public byte[] download(URI uri) {
  108. try {
  109. return ByteStreams.toByteArray(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput());
  110. } catch (IOException e) {
  111. throw failToDownload(uri, e);
  112. }
  113. }
  114. public String getProxySynthesis(URI uri) {
  115. return BaseHttpDownloader.getProxySynthesis(uri);
  116. }
  117. @Override
  118. public InputStream openStream(URI uri) {
  119. try {
  120. return downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput();
  121. } catch (IOException e) {
  122. throw failToDownload(uri, e);
  123. }
  124. }
  125. @Override
  126. public void download(URI uri, File toFile) {
  127. try {
  128. copyInputStreamToFile(downloader.newInputSupplier(uri, this.connectTimeout, this.readTimeout).getInput(), toFile);
  129. } catch (IOException e) {
  130. deleteQuietly(toFile);
  131. throw failToDownload(uri, e);
  132. }
  133. }
  134. private SonarException failToDownload(URI uri, IOException e) {
  135. throw new SonarException(String.format("Fail to download: %s (%s)", uri, getProxySynthesis(uri)), e);
  136. }
  137. /**
  138. * Facade to allow unit tests to verify calls to {@link Authenticator#setDefault(Authenticator)}.
  139. */
  140. static class AuthenticatorFacade {
  141. void setDefaultAuthenticator(Authenticator authenticator) {
  142. Authenticator.setDefault(authenticator);
  143. }
  144. }
  145. static class BaseHttpDownloader {
  146. private static final String GET = "GET";
  147. private static final String HTTP_PROXY_USER = "http.proxyUser";
  148. private static final String HTTP_PROXY_PASSWORD = "http.proxyPassword";
  149. private String userAgent;
  150. BaseHttpDownloader(AuthenticatorFacade system, Configuration config, @Nullable String userAgent) {
  151. initProxy(system, config);
  152. initUserAgent(userAgent, config);
  153. }
  154. private static void initProxy(AuthenticatorFacade system, Configuration config) {
  155. // register credentials
  156. Optional<String> login = config.get(HTTP_PROXY_USER);
  157. if (login.isPresent()) {
  158. system.setDefaultAuthenticator(new ProxyAuthenticator(login.get(), config.get(HTTP_PROXY_PASSWORD).orElse(null)));
  159. }
  160. }
  161. private void initUserAgent(@Nullable String sonarVersion, Configuration settings) {
  162. Optional<String> serverId = settings.get(CoreProperties.SERVER_ID);
  163. userAgent = sonarVersion == null ? "SonarQube" : String.format("SonarQube %s # %s", sonarVersion, serverId.orElse(""));
  164. System.setProperty("http.agent", userAgent);
  165. }
  166. private static String getProxySynthesis(URI uri) {
  167. return getProxySynthesis(uri, ProxySelector.getDefault());
  168. }
  169. @VisibleForTesting
  170. static String getProxySynthesis(URI uri, ProxySelector proxySelector) {
  171. List<Proxy> proxies = proxySelector.select(uri);
  172. if (proxies.size() == 1 && proxies.get(0).type().equals(Proxy.Type.DIRECT)) {
  173. return "no proxy";
  174. }
  175. List<String> descriptions = Lists.newArrayList();
  176. for (Proxy proxy : proxies) {
  177. if (proxy.type() != Proxy.Type.DIRECT) {
  178. descriptions.add(proxy.type() + " proxy: " + proxy.address());
  179. }
  180. }
  181. return Joiner.on(", ").join(descriptions);
  182. }
  183. public HttpInputSupplier newInputSupplier(URI uri, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
  184. return newInputSupplier(uri, GET, connectTimeoutMillis, readTimeoutMillis);
  185. }
  186. public HttpInputSupplier newInputSupplier(URI uri, String requestMethod, @Nullable Integer connectTimeoutMillis, @Nullable Integer readTimeoutMillis) {
  187. return newInputSupplier(uri, requestMethod, null, null, connectTimeoutMillis, readTimeoutMillis);
  188. }
  189. public HttpInputSupplier newInputSupplier(URI uri, String requestMethod, String login, String password, @Nullable Integer connectTimeoutMillis,
  190. @Nullable Integer readTimeoutMillis) {
  191. int read = readTimeoutMillis != null ? readTimeoutMillis : DEFAULT_READ_TIMEOUT_IN_MILLISECONDS;
  192. int connect = connectTimeoutMillis != null ? connectTimeoutMillis : DEFAULT_CONNECT_TIMEOUT_IN_MILLISECONDS;
  193. return new HttpInputSupplier(uri, requestMethod, userAgent, login, password, connect, read);
  194. }
  195. private static class HttpInputSupplier {
  196. private final String login;
  197. private final String password;
  198. private final URI uri;
  199. private final String userAgent;
  200. private final int connectTimeoutMillis;
  201. private final int readTimeoutMillis;
  202. private final String requestMethod;
  203. HttpInputSupplier(URI uri, String requestMethod, String userAgent, String login, String password, int connectTimeoutMillis, int readTimeoutMillis) {
  204. this.uri = uri;
  205. this.requestMethod = requestMethod;
  206. this.userAgent = userAgent;
  207. this.login = login;
  208. this.password = password;
  209. this.readTimeoutMillis = readTimeoutMillis;
  210. this.connectTimeoutMillis = connectTimeoutMillis;
  211. }
  212. /**
  213. * @throws IOException any I/O error, not limited to the network connection
  214. * @throws HttpException if HTTP response code > 400
  215. */
  216. public InputStream getInput() throws IOException {
  217. Loggers.get(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri, ProxySelector.getDefault()) + ")");
  218. HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
  219. connection.setRequestMethod(requestMethod);
  220. HttpsTrust.INSTANCE.trust(connection);
  221. // allow both GZip and Deflate (ZLib) encodings
  222. connection.setRequestProperty("Accept-Encoding", "gzip");
  223. if (!Strings.isNullOrEmpty(login)) {
  224. String encoded = Base64.encodeBase64String((login + ":" + password).getBytes(StandardCharsets.UTF_8));
  225. connection.setRequestProperty("Authorization", "Basic " + encoded);
  226. }
  227. connection.setConnectTimeout(connectTimeoutMillis);
  228. connection.setReadTimeout(readTimeoutMillis);
  229. connection.setUseCaches(true);
  230. connection.setInstanceFollowRedirects(true);
  231. connection.setRequestProperty("User-Agent", userAgent);
  232. // establish connection, get response headers
  233. connection.connect();
  234. // obtain the encoding returned by the server
  235. String encoding = connection.getContentEncoding();
  236. int responseCode = connection.getResponseCode();
  237. if (responseCode >= 400) {
  238. InputStream errorResponse = null;
  239. try {
  240. errorResponse = connection.getErrorStream();
  241. if (errorResponse != null) {
  242. String errorResponseContent = IOUtils.toString(errorResponse);
  243. throw new HttpException(uri, responseCode, errorResponseContent);
  244. }
  245. throw new HttpException(uri, responseCode);
  246. } finally {
  247. IOUtils.closeQuietly(errorResponse);
  248. }
  249. }
  250. InputStream resultingInputStream;
  251. // create the appropriate stream wrapper based on the encoding type
  252. if ("gzip".equalsIgnoreCase(encoding)) {
  253. resultingInputStream = new GZIPInputStream(connection.getInputStream());
  254. } else {
  255. resultingInputStream = connection.getInputStream();
  256. }
  257. return resultingInputStream;
  258. }
  259. }
  260. }
  261. static class ProxyAuthenticator extends Authenticator {
  262. private final PasswordAuthentication auth;
  263. ProxyAuthenticator(String user, @Nullable String password) {
  264. auth = new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray());
  265. }
  266. @Override
  267. protected PasswordAuthentication getPasswordAuthentication() {
  268. return auth;
  269. }
  270. }
  271. }