diff --git a/maven-mboot/src/main/Base64.java b/maven-mboot/src/main/Base64.java new file mode 100644 index 0000000000..28bc85b795 --- /dev/null +++ b/maven-mboot/src/main/Base64.java @@ -0,0 +1,386 @@ +/* + * ==================================================================== + * 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.ByteArrayOutputStream; + +// import org.apache.commons.logging.Log; +// import org.apache.commons.logging.LogFactory; + +/** + * Encode/Decode Base-64. + * + * @author John Casey + */ +public final class Base64 +{ + + // private static final Log LOG = LogFactory.getLog( Base64.class ); + + private static final String CRLF = System.getProperty( "line.separator" ); + + private static final int LINE_END = 64; + + public static String encode( byte[] data ) + { + return Base64.encode( data, true ); + } + + public static String encode( byte[] data, boolean useLineDelimiter ) + { + if ( data == null ) + { + return null; + } + else if ( data.length == 0 ) + { + return ""; + } + + int padding = 3 - ( data.length % 3 ); + + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "padding = " + padding + "characters." ); + // } + + StringBuffer buffer = new StringBuffer(); + + for ( int i = 0; i < data.length; i += 3 ) + { + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "iteration base offset = " + i ); + // } + + int neutral = ( data[i] < 0 ? data[i] + 256 : data[i] ); + + int block = ( neutral & 0xff ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "after first byte, block = " + Integer.toBinaryString( block ) ); + // } + + boolean inLastSegment = false; + + block <<= 8; + if ( i + 1 < data.length ) + { + neutral = ( data[i + 1] < 0 ? data[i + 1] + 256 : data[i + 1] ); + block |= ( neutral & 0xff ); + } + else + { + inLastSegment = true; + } + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "after second byte, block = " + Integer.toBinaryString( block ) + "; inLastSegment = " + // + inLastSegment ); + // } + + block <<= 8; + if ( i + 2 < data.length ) + { + neutral = ( data[i + 2] < 0 ? data[i + 2] + 256 : data[i + 2] ); + block |= ( neutral & 0xff ); + } + else + { + inLastSegment = true; + } + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "after third byte, block = " + Integer.toBinaryString( block ) + "; inLastSegment = " + // + inLastSegment ); + // } + + char[] encoded = new char[4]; + int encIdx = 0; + encoded[0] = toBase64Char( ( block >>> 18 ) & 0x3f ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "first character = " + encoded[0] ); + // } + + encoded[1] = toBase64Char( ( block >>> 12 ) & 0x3f ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "second character = " + encoded[1] ); + // } + + if ( inLastSegment && padding > 1 ) + { + encoded[2] = '='; + } + else + { + encoded[2] = toBase64Char( ( block >>> 6 ) & 0x3f ); + } + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "third character = " + encoded[2] ); + // } + + if ( inLastSegment && padding > 0 ) + { + encoded[3] = '='; + } + else + { + encoded[3] = toBase64Char( block & 0x3f ); + } + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "fourth character = " + encoded[3] ); + // } + + buffer.append( encoded ); + } + + if ( useLineDelimiter ) + { + return canonicalize( buffer.toString() ); + } + else + { + return buffer.toString(); + } + } + + public static byte[] decode( String src ) + { + return Base64.decode( src, true ); + } + + public static byte[] decode( String src, boolean useLineDelimiter ) + { + if ( src == null ) + { + return null; + } + else if ( src.length() < 1 ) + { + return new byte[0]; + } + + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "pre-canonicalization = \n" + src ); + // } + String data = src; + + if ( useLineDelimiter ) + { + data = deCanonicalize( src ); + } + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "post-canonicalization = \n" + data ); + // } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + char[] input = data.toCharArray(); + + int index = 0; + for ( int i = 0; i < input.length; i += 4 ) + { + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "iteration base offset = " + i ); + // } + + int block = ( toBase64Int( input[i] ) & 0x3f ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "block after first char [" + input[i] + "] = " + Integer.toBinaryString( block ) ); + // } + + block <<= 6; + block |= ( toBase64Int( input[i + 1] ) & 0x3f ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "block after second char [" + input[i + 1] + "] = " + Integer.toBinaryString( block ) ); + // } + + boolean inPadding = false; + boolean twoCharPadding = false; + block <<= 6; + if ( input[i + 2] != '=' ) + { + block |= ( toBase64Int( input[i + 2] ) & 0x3f ); + } + else + { + twoCharPadding = true; + inPadding = true; + } + + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "block after third char [" + input[i + 2] + "] = " + Integer.toBinaryString( block ) ); + // } + + block <<= 6; + if ( input[i + 3] != '=' ) + { + block |= ( toBase64Int( input[i + 3] ) & 0x3f ); + } + else + { + inPadding = true; + } + + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "block after fourth char [" + input[i + 3] + "] = " + Integer.toBinaryString( block ) ); + // } + + baos.write( ( block >>> 16 ) & 0xff ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "byte[" + ( index++ ) + "] = " + ( ( block >>> 16 ) & 0xff ) ); + // } + + if ( !inPadding || !twoCharPadding ) + { + baos.write( ( block >>> 8 ) & 0xff ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "byte[" + ( index++ ) + "] = " + ( ( block >>> 8 ) & 0xff ) ); + // } + } + + if ( !inPadding ) + { + baos.write( block & 0xff ); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "byte[" + ( index++ ) + "] = " + ( block & 0xff ) ); + // } + } + } + + byte[] result = baos.toByteArray(); + // if ( LOG.isDebugEnabled() ) + // { + // LOG.debug( "byte array is " + result.length + " bytes long." ); + // } + + return result; + } + + private static char toBase64Char( int input ) + { + if ( input > -1 && input < 26 ) + { + return ( char ) ( 'A' + input ); + } + else if ( input > 25 && input < 52 ) + { + return ( char ) ( 'a' + input - 26 ); + } + else if ( input > 51 && input < 62 ) + { + return ( char ) ( '0' + input - 52 ); + } + else if ( input == 62 ) + { + return '+'; + } + else if ( input == 63 ) + { + return '/'; + } + else + { + return '?'; + } + } + + private static int toBase64Int( char input ) + { + if ( input >= 'A' && input <= 'Z' ) + { + return input - 'A'; + } + else if ( input >= 'a' && input <= 'z' ) + { + return input + 26 - 'a'; + } + else if ( input >= '0' && input <= '9' ) + { + return input + 52 - '0'; + } + else if ( input == '+' ) + { + return 62; + } + else if ( input == '/' ) + { + return 63; + } + else + { + return 0; + } + } + + private static String deCanonicalize( String data ) + { + if ( data == null ) + { + return null; + } + + StringBuffer buffer = new StringBuffer( data.length() ); + for ( int i = 0; i < data.length(); i++ ) + { + char c = data.charAt( i ); + if ( c != '\r' && c != '\n' ) + { + buffer.append( c ); + } + } + + return buffer.toString(); + } + + private static String canonicalize( String data ) + { + StringBuffer buffer = new StringBuffer( ( int ) ( data.length() * 1.1 ) ); + + int col = 0; + for ( int i = 0; i < data.length(); i++ ) + { + if ( col == LINE_END ) + { + buffer.append( CRLF ); + col = 0; + } + + buffer.append( data.charAt( i ) ); + col++; + } + + buffer.append( CRLF ); + + return buffer.toString(); + } + +} diff --git a/maven-mboot/src/main/HttpUtils.java b/maven-mboot/src/main/HttpUtils.java index 29051c28e8..e04d753f23 100644 --- a/maven-mboot/src/main/HttpUtils.java +++ b/maven-mboot/src/main/HttpUtils.java @@ -1,4 +1,22 @@ +/* ==================================================================== + * 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; @@ -8,8 +26,31 @@ 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, @@ -35,6 +76,26 @@ protected PasswordAuthentication getPasswordAuthentication() } } + /** + * 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, @@ -44,7 +105,7 @@ public static void getFile( String url, String proxyUserName, String proxyPassword, boolean useChecksum ) - throws Exception + throws IOException { // Get the requested file. getFile( url, @@ -80,6 +141,24 @@ public static void getFile( String url, } } + /** + * 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, @@ -88,7 +167,58 @@ public static void getFile( String url, String proxyPort, String proxyUserName, String proxyPassword ) - throws Exception + 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]; @@ -97,23 +227,14 @@ public static void getFile( String url, URL source = new URL( parsedUrl ); - //set the timestamp to the file date. - long timestamp = 0; - boolean hasTimestamp = false; - if ( useTimestamp && destinationFile.exists() ) - { - timestamp = destinationFile.lastModified(); - hasTimestamp = true; - } - //set proxy connection useProxyUser( proxyHost, proxyPort, proxyUserName, proxyPassword ); //set up the URL connection URLConnection connection = source.openConnection(); + //modify the headers - //NB: things like user authentication could go in here too. - if ( useTimestamp && hasTimestamp ) + if ( timestamp >= 0 ) { connection.setIfModifiedSince( timestamp ); } @@ -121,22 +242,7 @@ public static void getFile( String url, if ( username != null || password != null ) { String up = username + ":" + password; - String encoding = null; - // check to see if sun's Base64 encoder is available. - try - { - sun.misc.BASE64Encoder encoder = - (sun.misc.BASE64Encoder) Class.forName( - "sun.misc.BASE64Encoder" ).newInstance(); - - encoding = encoder.encode( up.getBytes() ); - } - catch ( Exception ex ) - { - // Do nothing, as for MavenSession we will never use - // auth and we will eventually move over httpclient - // in the commons. - } + String encoding = Base64.encode(up.getBytes(), false); connection.setRequestProperty( "Authorization", "Basic " + encoding ); } @@ -146,6 +252,14 @@ public static void getFile( String url, 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; @@ -153,7 +267,12 @@ public static void getFile( String url, // test for 401 result (HTTP only) if ( httpConnection.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED ) { - throw new Exception( "Not authorized." ); + 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." ); } } @@ -163,6 +282,7 @@ public static void getFile( String url, // Some protocols (FTP) dont include dates, of course. InputStream is = null; + IOException isException = null; for ( int i = 0; i < 3; i++ ) { try @@ -172,20 +292,20 @@ public static void getFile( String url, } catch ( IOException ex ) { - // do nothing + isException = ex; } } if ( is == null ) { - if ( ignoreErrors ) - { - return; - } - - // This will never happen with maven's use of this class. - throw new Exception( "Can't get " + destinationFile.getName() + " to " + destinationFile ); + throw isException; } + if ( connection.getLastModified() <= timestamp && + connection.getLastModified() != 0 ) + { + return; + } + FileOutputStream fos = new FileOutputStream( destinationFile ); byte[] buffer = new byte[100 * 1024]; @@ -204,10 +324,9 @@ public static void getFile( String url, // 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 ( useTimestamp ) + if ( timestamp >= 0 ) { long remoteTimestamp = connection.getLastModified(); - if ( remoteTimestamp != 0 ) { touchFile( destinationFile, remoteTimestamp ); @@ -215,6 +334,15 @@ public static void getFile( String url, } } + /** + * 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]; @@ -231,18 +359,28 @@ static String[] parseUrl( String url ) int i = url.indexOf( "@" ); if ( i > 0 ) { - String s = url.substring( 7, i ); + 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] = "http://" + url.substring( i + 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 ) - throws Exception { long modifiedTime; @@ -256,7 +394,6 @@ private static boolean touchFile( File file, long timemillis ) } file.setLastModified( modifiedTime ); - return true; } }