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