blob: d3db26292d2288ba791a1914f7e08b2ff2408ac5 [file] [log] [blame]
package org.eclipse.aether.connector.basic;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.IOException;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy;
import org.eclipse.aether.spi.connector.layout.RepositoryLayout.Checksum;
import org.eclipse.aether.spi.io.FileProcessor;
import org.eclipse.aether.transfer.ChecksumFailureException;
import org.eclipse.aether.util.ChecksumUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Performs checksum validation for a downloaded file.
*/
final class ChecksumValidator
{
interface ChecksumFetcher
{
boolean fetchChecksum( URI remote, File local )
throws Exception;
}
private static final Logger LOGGER = LoggerFactory.getLogger( ChecksumValidator.class );
private final File dataFile;
private final Collection<File> tempFiles;
private final FileProcessor fileProcessor;
private final ChecksumFetcher checksumFetcher;
private final ChecksumPolicy checksumPolicy;
private final Collection<Checksum> checksums;
private final Map<File, Object> checksumFiles;
ChecksumValidator( File dataFile, FileProcessor fileProcessor,
ChecksumFetcher checksumFetcher, ChecksumPolicy checksumPolicy,
Collection<Checksum> checksums )
{
this.dataFile = dataFile;
this.tempFiles = new HashSet<File>();
this.fileProcessor = fileProcessor;
this.checksumFetcher = checksumFetcher;
this.checksumPolicy = checksumPolicy;
this.checksums = checksums;
checksumFiles = new HashMap<File, Object>();
}
public ChecksumCalculator newChecksumCalculator( File targetFile )
{
if ( checksumPolicy != null )
{
return ChecksumCalculator.newInstance( targetFile, checksums );
}
return null;
}
public void validate( Map<String, ?> actualChecksums, Map<String, ?> inlinedChecksums )
throws ChecksumFailureException
{
if ( checksumPolicy == null )
{
return;
}
if ( inlinedChecksums != null && validateInlinedChecksums( actualChecksums, inlinedChecksums ) )
{
return;
}
if ( validateExternalChecksums( actualChecksums ) )
{
return;
}
checksumPolicy.onNoMoreChecksums();
}
private boolean validateInlinedChecksums( Map<String, ?> actualChecksums, Map<String, ?> inlinedChecksums )
throws ChecksumFailureException
{
for ( Map.Entry<String, ?> entry : inlinedChecksums.entrySet() )
{
String algo = entry.getKey();
Object calculated = actualChecksums.get( algo );
if ( !( calculated instanceof String ) )
{
continue;
}
String actual = String.valueOf( calculated );
String expected = entry.getValue().toString();
checksumFiles.put( getChecksumFile( algo ), expected );
if ( !isEqualChecksum( expected, actual ) )
{
checksumPolicy.onChecksumMismatch( algo, ChecksumPolicy.KIND_UNOFFICIAL,
new ChecksumFailureException( expected, actual ) );
}
else if ( checksumPolicy.onChecksumMatch( algo, ChecksumPolicy.KIND_UNOFFICIAL ) )
{
return true;
}
}
return false;
}
private boolean validateExternalChecksums( Map<String, ?> actualChecksums )
throws ChecksumFailureException
{
for ( Checksum checksum : checksums )
{
String algo = checksum.getAlgorithm();
Object calculated = actualChecksums.get( algo );
if ( calculated instanceof Exception )
{
checksumPolicy.onChecksumError( algo, 0, new ChecksumFailureException( (Exception) calculated ) );
continue;
}
try
{
File checksumFile = getChecksumFile( checksum.getAlgorithm() );
File tmp = createTempFile( checksumFile );
try
{
if ( !checksumFetcher.fetchChecksum( checksum.getLocation(), tmp ) )
{
continue;
}
}
catch ( Exception e )
{
checksumPolicy.onChecksumError( algo, 0, new ChecksumFailureException( e ) );
continue;
}
String actual = String.valueOf( calculated );
String expected = ChecksumUtils.read( tmp );
checksumFiles.put( checksumFile, tmp );
if ( !isEqualChecksum( expected, actual ) )
{
checksumPolicy.onChecksumMismatch( algo, 0, new ChecksumFailureException( expected, actual ) );
}
else if ( checksumPolicy.onChecksumMatch( algo, 0 ) )
{
return true;
}
}
catch ( IOException e )
{
checksumPolicy.onChecksumError( algo, 0, new ChecksumFailureException( e ) );
}
}
return false;
}
private static boolean isEqualChecksum( String expected, String actual )
{
return expected.equalsIgnoreCase( actual );
}
private File getChecksumFile( String algorithm )
{
String ext = algorithm.replace( "-", "" ).toLowerCase( Locale.ENGLISH );
return new File( dataFile.getPath() + '.' + ext );
}
private File createTempFile( File path )
throws IOException
{
File file =
File.createTempFile( path.getName() + "-"
+ UUID.randomUUID().toString().replace( "-", "" ).substring( 0, 8 ), ".tmp", path.getParentFile() );
tempFiles.add( file );
return file;
}
private void clearTempFiles()
{
for ( File file : tempFiles )
{
if ( !file.delete() && file.exists() )
{
LOGGER.debug( "Could not delete temporary file {}", file );
}
}
tempFiles.clear();
}
public void retry()
{
checksumPolicy.onTransferRetry();
checksumFiles.clear();
clearTempFiles();
}
public boolean handle( ChecksumFailureException exception )
{
return checksumPolicy.onTransferChecksumFailure( exception );
}
public void commit()
{
for ( Map.Entry<File, Object> entry : checksumFiles.entrySet() )
{
File checksumFile = entry.getKey();
Object tmp = entry.getValue();
try
{
if ( tmp instanceof File )
{
fileProcessor.move( (File) tmp, checksumFile );
tempFiles.remove( tmp );
}
else
{
fileProcessor.write( checksumFile, String.valueOf( tmp ) );
}
}
catch ( IOException e )
{
LOGGER.debug( "Failed to write checksum file {}: {}", checksumFile, e.getMessage(), e );
}
}
checksumFiles.clear();
}
public void close()
{
clearTempFiles();
}
}