001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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     */
020    package org.sonar.api.utils;
021    
022    import com.google.common.base.Joiner;
023    import com.google.common.collect.Lists;
024    import org.apache.commons.configuration.Configuration;
025    import org.apache.commons.configuration.PropertiesConfiguration;
026    import org.apache.commons.io.FileUtils;
027    import org.apache.commons.io.IOUtils;
028    import org.slf4j.LoggerFactory;
029    import org.sonar.api.BatchComponent;
030    import org.sonar.api.ServerComponent;
031    import org.sonar.api.platform.Server;
032    
033    import java.io.File;
034    import java.io.FileOutputStream;
035    import java.io.IOException;
036    import java.io.InputStream;
037    import java.net.*;
038    import java.util.List;
039    
040    /**
041     * This component downloads HTTP files
042     * 
043     * @since 2.2
044     */
045    public class HttpDownloader implements BatchComponent, ServerComponent {
046    
047      public static final int TIMEOUT_MILLISECONDS = 20 * 1000;
048    
049      private String userAgent;
050    
051      public HttpDownloader(Server server, Configuration configuration) {
052        this(configuration, server.getVersion());
053      }
054    
055      public HttpDownloader(Configuration configuration) {
056        this(configuration, null);
057      }
058    
059      /**
060       * Should be package protected for unit tests only, but public is still required for jacoco 0.2.
061       */
062      public HttpDownloader() {
063        this(new PropertiesConfiguration(), null);
064      }
065    
066      private HttpDownloader(Configuration configuration, String userAgent) {
067        initProxy(configuration);
068        initUserAgent(userAgent);
069      }
070    
071      private void initProxy(Configuration configuration) {
072        propagateProxySystemProperties(configuration);
073        if (requiresProxyAuthentication(configuration)) {
074          registerProxyCredentials(configuration);
075        }
076      }
077    
078      private void initUserAgent(String sonarVersion) {
079        userAgent = (sonarVersion == null ? "Sonar" : String.format("Sonar %s", sonarVersion));
080        System.setProperty("http.agent", userAgent);
081      }
082    
083      public String getProxySynthesis(URI uri) {
084        return getProxySynthesis(uri, ProxySelector.getDefault());
085      }
086    
087      static String getProxySynthesis(URI uri, ProxySelector proxySelector) {
088        List<String> descriptions = Lists.newArrayList();
089        List<Proxy> proxies = proxySelector.select(uri);
090        if (proxies.size() == 1 && proxies.get(0).type().equals(Proxy.Type.DIRECT)) {
091          descriptions.add("no proxy");
092        } else {
093          for (Proxy proxy : proxies) {
094            if (!proxy.type().equals(Proxy.Type.DIRECT)) {
095              descriptions.add("proxy: " + proxy.address().toString());
096            }
097          }
098        }
099        return Joiner.on(", ").join(descriptions);
100      }
101    
102      private void registerProxyCredentials(Configuration configuration) {
103        Authenticator.setDefault(new ProxyAuthenticator(configuration.getString("http.proxyUser"), configuration
104            .getString("http.proxyPassword")));
105      }
106    
107      private boolean requiresProxyAuthentication(Configuration configuration) {
108        return configuration.getString("http.proxyUser") != null;
109      }
110    
111      private void propagateProxySystemProperties(Configuration configuration) {
112        propagateSystemProperty(configuration, "http.proxyHost");
113        propagateSystemProperty(configuration, "http.proxyPort");
114        propagateSystemProperty(configuration, "http.nonProxyHosts");
115        propagateSystemProperty(configuration, "http.auth.ntlm.domain");
116        propagateSystemProperty(configuration, "socksProxyHost");
117        propagateSystemProperty(configuration, "socksProxyPort");
118      }
119    
120      private void propagateSystemProperty(Configuration configuration, String key) {
121        if (configuration.getString(key) != null) {
122          System.setProperty(key, configuration.getString(key));
123        }
124      }
125    
126      public void download(URI uri, File toFile) {
127        InputStream input = null;
128        FileOutputStream output = null;
129        try {
130          HttpURLConnection connection = newHttpConnection(uri);
131          output = new FileOutputStream(toFile, false);
132          input = connection.getInputStream();
133          IOUtils.copy(input, output);
134    
135        } catch (Exception e) {
136          IOUtils.closeQuietly(output);
137          FileUtils.deleteQuietly(toFile);
138          throw new SonarException("Fail to download the file: " + uri + " (" + getProxySynthesis(uri) + ")", e);
139    
140        } finally {
141          IOUtils.closeQuietly(input);
142          IOUtils.closeQuietly(output);
143        }
144      }
145    
146      public byte[] download(URI uri) {
147        InputStream input = null;
148        try {
149          HttpURLConnection connection = newHttpConnection(uri);
150          input = connection.getInputStream();
151          return IOUtils.toByteArray(input);
152    
153        } catch (Exception e) {
154          throw new SonarException("Fail to download the file: " + uri + " (" + getProxySynthesis(uri) + ")", e);
155    
156        } finally {
157          IOUtils.closeQuietly(input);
158        }
159      }
160    
161      public String downloadPlainText(URI uri, String encoding) {
162        InputStream input = null;
163        try {
164          HttpURLConnection connection = newHttpConnection(uri);
165          input = connection.getInputStream();
166          return IOUtils.toString(input, encoding);
167    
168        } catch (Exception e) {
169          throw new SonarException("Fail to download the file: " + uri + " (" + getProxySynthesis(uri) + ")", e);
170    
171        } finally {
172          IOUtils.closeQuietly(input);
173        }
174      }
175    
176      public InputStream openStream(URI uri) {
177        try {
178          HttpURLConnection connection = newHttpConnection(uri);
179          return connection.getInputStream();
180    
181        } catch (Exception e) {
182          throw new SonarException("Fail to download the file: " + uri + " (" + getProxySynthesis(uri) + ")", e);
183        }
184      }
185    
186      private HttpURLConnection newHttpConnection(URI uri) throws IOException {
187        LoggerFactory.getLogger(getClass()).debug("Download: " + uri + " (" + getProxySynthesis(uri) + ")");
188        HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();
189        connection.setConnectTimeout(TIMEOUT_MILLISECONDS);
190        connection.setReadTimeout(TIMEOUT_MILLISECONDS);
191        connection.setUseCaches(true);
192        connection.setInstanceFollowRedirects(true);
193        connection.setRequestProperty("User-Agent", userAgent);
194        return connection;
195      }
196    }
197    
198    class ProxyAuthenticator extends Authenticator {
199      private PasswordAuthentication auth;
200    
201      ProxyAuthenticator(String user, String password) {
202        auth = new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray());
203      }
204    
205      protected PasswordAuthentication getPasswordAuthentication() {
206        return auth;
207      }
208    }