1 package org.apache.archiva.checksum;
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
22 import org.apache.archiva.common.utils.FileUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
26 import javax.xml.bind.ValidationException;
27 import java.io.IOException;
28 import java.nio.charset.Charset;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.nio.file.StandardOpenOption;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.regex.Matcher;
35 import java.util.regex.Pattern;
37 import static org.apache.archiva.checksum.ChecksumValidationException.ValidationError.BAD_CHECKSUM_FILE_REF;
43 * <dt>Checksum File</dt>
44 * <dd>The file that contains the previously calculated checksum value for the reference file.
45 * This is a text file with the extension ".sha1" or ".md5", and contains a single entry
46 * consisting of an optional reference filename, and a checksum string.
48 * <dt>Reference File</dt>
49 * <dd>The file that is being referenced in the checksum file.</dd>
52 public class ChecksummedFile
55 private static Charset FILE_ENCODING = Charset.forName( "UTF-8" );
57 private final Logger log = LoggerFactory.getLogger( ChecksummedFile.class );
59 private static final Pattern METADATA_PATTERN = Pattern.compile( "maven-metadata-\\S*.xml" );
61 private final Path referenceFile;
64 * Construct a ChecksummedFile object.
66 * @param referenceFile
68 public ChecksummedFile( final Path referenceFile )
70 this.referenceFile = referenceFile;
74 public static ChecksumReference getFromChecksumFile( Path checksumFile )
76 ChecksumAlgorithm alg = ChecksumAlgorithm.getByExtension( checksumFile );
77 ChecksummedFile file = new ChecksummedFile( getReferenceFile( checksumFile ) );
78 return new ChecksumReference( file, alg, checksumFile );
81 private static Path getReferenceFile( Path checksumFile )
83 String fileName = checksumFile.getFileName( ).toString( );
84 return checksumFile.resolveSibling( fileName.substring( 0, fileName.lastIndexOf( '.' ) ) );
88 * Calculate the checksum based on a given checksum.
90 * @param checksumAlgorithm the algorithm to use.
91 * @return the checksum string for the file.
92 * @throws IOException if unable to calculate the checksum.
94 public String calculateChecksum( ChecksumAlgorithm checksumAlgorithm )
98 Checksum checksum = new Checksum( checksumAlgorithm );
99 checksum.update( referenceFile );
100 return checksum.getChecksum( );
104 * Creates a checksum file of the provided referenceFile.
106 * @param checksumAlgorithm the hash to use.
107 * @return the checksum File that was created.
108 * @throws IOException if there was a problem either reading the referenceFile, or writing the checksum file.
110 public Path createChecksum( ChecksumAlgorithm checksumAlgorithm )
113 Path checksumFile = referenceFile.resolveSibling( referenceFile.getFileName( ) + "." + checksumAlgorithm.getExt( ).get( 0 ) );
114 Files.deleteIfExists( checksumFile );
115 String checksum = calculateChecksum( checksumAlgorithm );
116 Files.write( checksumFile, //
117 ( checksum + " " + referenceFile.getFileName( ).toString( ) ).getBytes( ), //
118 StandardOpenOption.CREATE_NEW );
123 * Get the checksum file for the reference file and hash.
125 * @param checksumAlgorithm the hash that we are interested in.
126 * @return the checksum file to return
128 public Path getChecksumFile( ChecksumAlgorithm checksumAlgorithm )
130 for ( String ext : checksumAlgorithm.getExt( ) )
132 Path file = referenceFile.resolveSibling( referenceFile.getFileName( ) + "." + checksumAlgorithm.getExt( ) );
133 if ( Files.exists( file ) )
138 return referenceFile.resolveSibling( referenceFile.getFileName( ) + "." + checksumAlgorithm.getExt( ).get( 0 ) );
143 * Given a checksum file, check to see if the file it represents is valid according to the checksum.
146 * NOTE: Only supports single file checksums of type MD5 or SHA1.
149 * @param algorithm the algorithms to check for.
150 * @return true if the checksum is valid for the file it represents. or if the checksum file does not exist.
151 * @throws IOException if the reading of the checksumFile or the file it refers to fails.
153 public boolean isValidChecksum( ChecksumAlgorithm algorithm) throws ChecksumValidationException
155 return isValidChecksum( algorithm, false );
157 public boolean isValidChecksum( ChecksumAlgorithm algorithm, boolean throwExceptions )
158 throws ChecksumValidationException
160 return isValidChecksums( new ChecksumAlgorithm[]{algorithm} );
164 * Of any checksum files present, validate that the reference file conforms
165 * the to the checksum.
167 * @param algorithms the algorithms to check for.
168 * @return true if the checksums report that the the reference file is valid, false if invalid.
170 public boolean isValidChecksums( ChecksumAlgorithm algorithms[]) throws ChecksumValidationException
172 return isValidChecksums( algorithms, false );
176 * Checks if the checksums are valid for the referenced file.
177 * This method throws only exceptions, if throwExceptions is true. Otherwise false will be returned instead.
178 * @param algorithms The algorithms to verify
179 * @param throwExceptions If true, exceptions will be thrown, otherwise false will be returned, if a exception occurred.
180 * @return True, if it is valid, otherwise false.
181 * @throws ChecksumValidationException
183 public boolean isValidChecksums( ChecksumAlgorithm algorithms[], boolean throwExceptions) throws ChecksumValidationException
186 List<Checksum> checksums = new ArrayList<>( algorithms.length );
187 // Create checksum object for each algorithm.
188 for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
190 Path checksumFile = getChecksumFile( checksumAlgorithm );
192 // Only add algorithm if checksum file exists.
193 if ( Files.exists( checksumFile ) )
195 checksums.add( new Checksum( checksumAlgorithm ) );
200 if ( checksums.isEmpty( ) )
202 // No checksum objects, no checksum files, default to is invalid.
206 // Parse file once, for all checksums.
209 Checksum.update( checksums, referenceFile );
211 catch ( ChecksumValidationException e )
213 log.warn( "Unable to update checksum:{}", e.getMessage( ) );
214 if (throwExceptions) {
221 boolean valid = true;
223 // check the checksum files
226 for ( Checksum checksum : checksums )
228 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm( );
229 Path checksumFile = getChecksumFile( checksumAlgorithm );
231 String expectedChecksum = parseChecksum( checksumFile, checksumAlgorithm, referenceFile.getFileName( ).toString( ), FILE_ENCODING );
233 if ( !checksum.compare( expectedChecksum ) )
239 catch ( ChecksumValidationException e )
241 log.warn( "Unable to read / parse checksum: {}", e.getMessage( ) );
242 if (throwExceptions) {
253 public Path getReferenceFile( )
255 return referenceFile;
259 * Fix or create checksum files for the reference file.
261 * @param algorithms the hashes to check for.
262 * @return true if checksums were created successfully.
264 public boolean fixChecksums( ChecksumAlgorithm[] algorithms )
266 List<Checksum> checksums = new ArrayList<>( algorithms.length );
267 // Create checksum object for each algorithm.
268 for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
270 checksums.add( new Checksum( checksumAlgorithm ) );
274 if ( checksums.isEmpty( ) )
276 // No checksum objects, no checksum files, default to is valid.
282 // Parse file once, for all checksums.
283 Checksum.update( checksums, referenceFile );
285 catch ( ChecksumValidationException e )
287 log.warn( e.getMessage( ), e );
291 boolean valid = true;
293 // check the hash files
294 for ( Checksum checksum : checksums )
296 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm( );
299 Path checksumFile = getChecksumFile( checksumAlgorithm );
300 if ( Files.exists( checksumFile ) )
302 String expectedChecksum = parseChecksum( checksumFile, checksumAlgorithm, referenceFile.getFileName( ).toString( ), FILE_ENCODING );
304 if ( !checksum.compare( expectedChecksum ) )
306 // create checksum (again)
307 writeChecksumFile( checksumFile, FILE_ENCODING, checksum.getChecksum( ) );
312 writeChecksumFile( checksumFile, FILE_ENCODING, checksum.getChecksum( ) );
315 catch ( ChecksumValidationException e )
317 log.warn( e.getMessage( ), e );
326 private void writeChecksumFile( Path checksumFile, Charset encoding, String checksumHex )
328 FileUtils.writeStringToFile( checksumFile, FILE_ENCODING, checksumHex + " " + referenceFile.getFileName( ).toString( ) );
331 private boolean isValidChecksumPattern( String filename, String path )
333 // check if it is a remote metadata file
335 Matcher m = METADATA_PATTERN.matcher( path );
338 return filename.endsWith( path ) || ( "-".equals( filename ) ) || filename.endsWith( "maven-metadata.xml" );
341 return filename.endsWith( path ) || ( "-".equals( filename ) );
345 * Parse a checksum string.
347 * Validate the expected path, and expected checksum algorithm, then return
348 * the trimmed checksum hex string.
351 * @param checksumFile The file where the checksum is stored
352 * @param expectedHash The checksum algorithm to check
353 * @param expectedPath The filename of the reference file
355 * @throws IOException
357 public String parseChecksum( Path checksumFile, ChecksumAlgorithm expectedHash, String expectedPath, Charset encoding )
358 throws ChecksumValidationException
360 ChecksumFileContent fc = parseChecksumFile( checksumFile, expectedHash, encoding );
361 if ( fc.isFormatMatch() && !isValidChecksumPattern( fc.getFileReference( ), expectedPath ) )
363 throw new ChecksumValidationException(BAD_CHECKSUM_FILE_REF,
364 "The file reference '" + fc.getFileReference( ) + "' in the checksum file does not match expected file: '" + expectedPath + "'" );
366 return fc.getChecksum( );
369 public ChecksumFileContent parseChecksumFile( Path checksumFile, ChecksumAlgorithm expectedHash, Charset encoding )
371 ChecksumFileContent fc = new ChecksumFileContent( );
372 String rawChecksumString = FileUtils.readFileToString( checksumFile, encoding );
373 String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim( );
375 // Free-BSD / openssl
376 String regex = expectedHash.getType( ) + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
377 Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
380 fc.setFileReference( m.group( 1 ) );
381 fc.setChecksum( m.group( 2 ) );
382 fc.setFormatMatch( true );
387 m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
390 fc.setFileReference( m.group( 2 ) );
391 fc.setChecksum( m.group( 1 ) );
392 fc.setFormatMatch( true );
396 fc.setFileReference( "" );
397 fc.setChecksum( trimmedChecksum );
398 fc.setFormatMatch( false );