/* ==================================================================== * Copyright 2001-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ==================================================================== */ import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.PasswordAuthentication; import java.net.URL; import java.net.URLConnection; /** * Http utils for retrieving files. * * @author costin@dnt.ro * @author gg@grtmail.com (Added Java 1.1 style HTTP basic auth) * @author Jason van Zyl * @todo Need to add a timeout so we can flip to a backup repository. * @todo Download everything in a single session. * @todo Throw meaningful exception when authentication fails. */ public class HttpUtils { /** * Use a proxy to bypass the firewall with or without authentication * * @param proxyHost Proxy Host (if proxy is required), or null * @param proxyPort Proxy Port (if proxy is required), or null * @param proxyUserName Proxy Username (if authentification is required), * or null * @param proxyPassword Proxy Password (if authentification is required), * or null * @throws SecurityException if an operation is not authorized by the * SecurityManager */ public static void useProxyUser( final String proxyHost, final String proxyPort, final String proxyUserName, final String proxyPassword ) { if ( proxyHost != null && proxyPort != null ) { System.getProperties().put( "proxySet", "true" ); System.getProperties().put( "proxyHost", proxyHost ); System.getProperties().put( "proxyPort", proxyPort ); if ( proxyUserName != null ) { Authenticator.setDefault( new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( proxyUserName, proxyPassword == null ? new char[0] : proxyPassword.toCharArray() ); } } ); } } } /** * Retrieve a remote file. Throws an Exception on errors unless the * ifnoreErrors flag is set to True * * @param url the of the file to retrieve * @param destinationFile where to store it * @param ignoreErrors whether to ignore errors during I/O or throw an * exception when they happen * @param useTimestamp whether to check the modified timestamp on the * destinationFile against the remote source * @param proxyHost Proxy Host (if proxy is required), or null * @param proxyPort Proxy Port (if proxy is required), or null * @param proxyUserName Proxy Username (if authentification is required), * or null. * @param proxyPassword Proxy Password (if authentification is required), * or null. * @param useChecksum Flag to indicate the use of the checksum for the retrieved * artifact if it is available. * @throws IOException If an I/O exception occurs. */ public static void getFile( String url, File destinationFile, boolean ignoreErrors, boolean useTimestamp, String proxyHost, String proxyPort, String proxyUserName, String proxyPassword, boolean useChecksum ) throws IOException { // Get the requested file. getFile( url, destinationFile, ignoreErrors, useTimestamp, proxyHost, proxyPort, proxyUserName, proxyPassword ); // Get the checksum if requested. if ( useChecksum ) { File checksumFile = new File( destinationFile + ".md5" ); try { getFile( url + ".md5", checksumFile, ignoreErrors, useTimestamp, proxyHost, proxyPort, proxyUserName, proxyPassword ); } catch ( Exception e ) { // do nothing we will check later in the process // for the checksums. } } } /** * Retrieve a remote file. Throws an Exception on errors unless the * ifnoreErrors flag is set to True * * @param url the of the file to retrieve * @param destinationFile where to store it * @param ignoreErrors whether to ignore errors during I/O or throw an * exception when they happen * @param useTimestamp whether to check the modified timestamp on the * destinationFile against the remote source * @param proxyHost Proxy Host (if proxy is required), or null * @param proxyPort Proxy Port (if proxy is required), or null * @param proxyUserName Proxy Username (if authentification is required), * or null * @param proxyPassword Proxy Password (if authentification is required), * or null * @throws IOException If an I/O exception occurs. */ public static void getFile( String url, File destinationFile, boolean ignoreErrors, boolean useTimestamp, String proxyHost, String proxyPort, String proxyUserName, String proxyPassword ) throws IOException { //set the timestamp to the file date. long timestamp = -1; if ( useTimestamp && destinationFile.exists() ) { timestamp = destinationFile.lastModified(); } try { getFile( url, destinationFile, timestamp, proxyHost, proxyPort, proxyUserName, proxyPassword ); } catch ( IOException ex ) { if ( !ignoreErrors ) { throw ex; } } } /** * Retrieve a remote file. * * @param url the URL of the file to retrieve * @param destinationFile where to store it * @param timestamp if provided, the remote URL is only retrieved if it was * modified more recently than timestamp. Otherwise, negative value indicates that * the remote URL should be retrieved unconditionally. * @param proxyHost Proxy Host (if proxy is required), or null * @param proxyPort Proxy Port (if proxy is required), or null * @param proxyUserName Proxy Username (if authentification is required), * or null * @param proxyPassword Proxy Password (if authentification is required), * or null * @throws IOException If an I/O exception occurs. */ public static void getFile( String url, File destinationFile, long timestamp, String proxyHost, String proxyPort, String proxyUserName, String proxyPassword ) throws IOException { String[] s = parseUrl( url ); String username = s[0]; String password = s[1]; String parsedUrl = s[2]; URL source = new URL( parsedUrl ); //set proxy connection useProxyUser( proxyHost, proxyPort, proxyUserName, proxyPassword ); //set up the URL connection URLConnection connection = source.openConnection(); //modify the headers if ( timestamp >= 0 ) { connection.setIfModifiedSince( timestamp ); } // prepare Java 1.1 style credentials if ( username != null || password != null ) { String up = username + ":" + password; String encoding = Base64.encode( up.getBytes(), false ); connection.setRequestProperty( "Authorization", "Basic " + encoding ); } //connect to the remote site (may take some time) connection.connect(); //next test for a 304 result (HTTP only) if ( connection instanceof HttpURLConnection ) { HttpURLConnection httpConnection = (HttpURLConnection) connection; // although HTTPUrlConnection javadocs says FileNotFoundException should be // thrown on a 404 error, that certainly does not appear to be the case, so // test for 404 ourselves, and throw FileNotFoundException as needed if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND ) { throw new FileNotFoundException( url.toString() + " (HTTP Error: " + httpConnection.getResponseCode() + " " + httpConnection.getResponseMessage() + ")" ); } if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED ) { return; } // test for 401 result (HTTP only) if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED ) { throw new IOException( "Not authorized." ); } // test for 407 result (HTTP only) if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_PROXY_AUTH ) { throw new IOException( "Not authorized by proxy." ); } } // REVISIT: at this point even non HTTP connections may support the // if-modified-since behaviour - we just check the date of the // content and skip the write if it is not newer. // Some protocols (FTP) dont include dates, of course. InputStream is = null; IOException isException = null; for ( int i = 0; i < 3; i++ ) { try { is = connection.getInputStream(); break; } catch ( IOException ex ) { isException = ex; } } if ( is == null ) { throw isException; } if ( connection.getLastModified() <= timestamp && connection.getLastModified() != 0 ) { return; } FileOutputStream fos = new FileOutputStream( destinationFile ); byte[] buffer = new byte[100 * 1024]; int length; while ( ( length = is.read( buffer ) ) >= 0 ) { fos.write( buffer, 0, length ); System.out.print( "." ); } System.out.println(); fos.close(); is.close(); // if (and only if) the use file time option is set, then the // saved file now has its timestamp set to that of the downloaded // file if ( timestamp >= 0 ) { long remoteTimestamp = connection.getLastModified(); if ( remoteTimestamp != 0 ) { touchFile( destinationFile, remoteTimestamp ); } } } /** * Parse an url which might contain a username and password. If the * given url doesn't contain a username and password then return the * origin url unchanged. * * @param url The url to parse. * @return The username, password and url. * @throws RuntimeException if the url is (very) invalid */ static String[] parseUrl( String url ) { String[] parsedUrl = new String[3]; parsedUrl[0] = null; parsedUrl[1] = null; parsedUrl[2] = url; // We want to be able to deal with Basic Auth where the username // and password are part of the URL. An example of the URL string // we would like to be able to parse is like the following: // // http://username:password@repository.mycompany.com int i = url.indexOf( "@" ); if ( i > 0 ) { String protocol = url.substring( 0, url.indexOf( "://" ) ) + "://"; String s = url.substring( protocol.length(), i ); int j = s.indexOf( ":" ); parsedUrl[0] = s.substring( 0, j ); parsedUrl[1] = s.substring( j + 1 ); parsedUrl[2] = protocol + url.substring( i + 1 ); } return parsedUrl; } /** * set the timestamp of a named file to a specified time. * * @param file the file to touch * @param timemillis in milliseconds since the start of the era * @return true if it succeeded. False means that this is a java1.1 system * and that file times can not be set * @throws RuntimeException Thrown in unrecoverable error. Likely this * comes from file access failures. */ private static boolean touchFile( File file, long timemillis ) { long modifiedTime; if ( timemillis < 0 ) { modifiedTime = System.currentTimeMillis(); } else { modifiedTime = timemillis; } file.setLastModified( modifiedTime ); return true; } }