001/* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020package org.sonar.api.utils; 021 022import com.google.common.annotations.VisibleForTesting; 023 024import com.google.common.base.Joiner; 025import com.google.common.collect.ImmutableList; 026import com.google.common.collect.Lists; 027import com.google.common.io.ByteStreams; 028import com.google.common.io.CharStreams; 029import com.google.common.io.Files; 030import com.google.common.io.InputSupplier; 031import org.apache.commons.io.FileUtils; 032import org.slf4j.LoggerFactory; 033import org.sonar.api.BatchComponent; 034import org.sonar.api.ServerComponent; 035import org.sonar.api.config.Settings; 036import org.sonar.api.platform.Server; 037 038import java.io.File; 039import java.io.IOException; 040import java.io.InputStream; 041import java.net.Authenticator; 042import java.net.HttpURLConnection; 043import java.net.PasswordAuthentication; 044import java.net.Proxy; 045import java.net.ProxySelector; 046import java.net.URI; 047import java.nio.charset.Charset; 048import java.util.List; 049 050/** 051 * This component downloads HTTP files 052 * 053 * @since 2.2 054 */ 055public class HttpDownloader extends UriReader.SchemeProcessor implements BatchComponent, ServerComponent { 056 057 public static final int TIMEOUT_MILLISECONDS = 20 * 1000; 058 private static final List<String> PROXY_SETTINGS = ImmutableList.of( 059 "http.proxyHost", "http.proxyPort", "http.nonProxyHosts", 060 "http.auth.ntlm.domain", "socksProxyHost", "socksProxyPort"); 061 062 private String userAgent; 063 064 public HttpDownloader(Server server, Settings settings) { 065 this(settings, server.getVersion()); 066 } 067 068 public HttpDownloader(Settings settings) { 069 this(settings, null); 070 } 071 072 private HttpDownloader(Settings settings, String userAgent) { 073 initProxy(settings); 074 initUserAgent(userAgent); 075 } 076 077 private void initProxy(Settings settings) { 078 propagateProxySystemProperties(settings); 079 if (requiresProxyAuthentication(settings)) { 080 registerProxyCredentials(settings); 081 } 082 } 083 084 private void initUserAgent(String sonarVersion) { 085 userAgent = (sonarVersion == null ? "Sonar" : String.format("Sonar %s", sonarVersion)); 086 System.setProperty("http.agent", userAgent); 087 } 088 089 public String getProxySynthesis(URI uri) { 090 return getProxySynthesis(uri, ProxySelector.getDefault()); 091 } 092 093 @VisibleForTesting 094 static String getProxySynthesis(URI uri, ProxySelector proxySelector) { 095 List<Proxy> proxies = proxySelector.select(uri); 096 if (proxies.size() == 1 && proxies.get(0).type().equals(Proxy.Type.DIRECT)) { 097 return "no proxy"; 098 } 099 100 List<String> descriptions = Lists.newArrayList(); 101 for (Proxy proxy : proxies) { 102 if (proxy.type() != Proxy.Type.DIRECT) { 103 descriptions.add("proxy: " + proxy.address()); 104 } 105 } 106 107 return Joiner.on(", ").join(descriptions); 108 } 109 110 private void registerProxyCredentials(Settings settings) { 111 Authenticator.setDefault(new ProxyAuthenticator(settings.getString("http.proxyUser"), settings 112 .getString("http.proxyPassword"))); 113 } 114 115 private boolean requiresProxyAuthentication(Settings settings) { 116 return settings.getString("http.proxyUser") != null; 117 } 118 119 private void propagateProxySystemProperties(Settings settings) { 120 for (String key : PROXY_SETTINGS) { 121 if (settings.getString(key) != null) { 122 System.setProperty(key, settings.getString(key)); 123 } 124 } 125 } 126 127 @Override 128 String description(URI uri) { 129 return String.format("%s (%s)", uri.toString(), getProxySynthesis(uri)); 130 } 131 132 @Override 133 String[] getSupportedSchemes() { 134 return new String[] {"http", "https"}; 135 } 136 137 @Override 138 byte[] readBytes(URI uri) { 139 try { 140 return ByteStreams.toByteArray(new HttpInputSupplier(uri)); 141 } catch (IOException e) { 142 throw failToDownload(uri, e); 143 } 144 } 145 146 @Override 147 String readString(URI uri, Charset charset) { 148 try { 149 return CharStreams.toString(CharStreams.newReaderSupplier(new HttpInputSupplier(uri), charset)); 150 } catch (IOException e) { 151 throw failToDownload(uri, e); 152 } 153 } 154 155 public byte[] download(URI uri) { 156 return readBytes(uri); 157 } 158 159 public String downloadPlainText(URI uri, String encoding) { 160 return readString(uri, Charset.forName(encoding)); 161 } 162 163 public InputStream openStream(URI uri) { 164 try { 165 return new HttpInputSupplier(uri).getInput(); 166 } catch (Exception e) { 167 throw new SonarException("Fail to download the file: " + uri + " (" + getProxySynthesis(uri) + ")", e); 168 } 169 } 170 171 public void download(URI uri, File toFile) { 172 try { 173 Files.copy(new HttpInputSupplier(uri), toFile); 174 } catch (IOException e) { 175 FileUtils.deleteQuietly(toFile); 176 throw failToDownload(uri, e); 177 } 178 } 179 180 private SonarException failToDownload(URI uri, IOException e) { 181 return new SonarException(String.format("Fail to download the file: %s (%s)", uri, getProxySynthesis(uri)), e); 182 } 183 184 class HttpInputSupplier implements InputSupplier<InputStream> { 185 private final URI uri; 186 187 HttpInputSupplier(URI uri) { 188 this.uri = uri; 189 } 190 191 public InputStream getInput() throws IOException { 192 LoggerFactory.getLogger(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri) + ")"); 193 194 HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection(); 195 connection.setConnectTimeout(TIMEOUT_MILLISECONDS); 196 connection.setReadTimeout(TIMEOUT_MILLISECONDS); 197 connection.setUseCaches(true); 198 connection.setInstanceFollowRedirects(true); 199 connection.setRequestProperty("User-Agent", userAgent); 200 return connection.getInputStream(); 201 } 202 } 203} 204 205class ProxyAuthenticator extends Authenticator { 206 private PasswordAuthentication auth; 207 208 ProxyAuthenticator(String user, String password) { 209 auth = new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray()); 210 } 211 212 @Override 213 protected PasswordAuthentication getPasswordAuthentication() { 214 return auth; 215 } 216}