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;
}
}