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.commons.io.FileUtils;
23 import org.apache.commons.lang.StringUtils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.nio.file.Files;
31 import java.util.ArrayList;
32 import java.util.List;
33 import java.util.regex.Matcher;
34 import java.util.regex.Pattern;
41 * <dt>Checksum File</dt>
42 * <dd>The file that contains the previously calculated checksum value for the reference file.
43 * This is a text file with the extension ".sha1" or ".md5", and contains a single entry
44 * consisting of an optional reference filename, and a checksum string.
46 * <dt>Reference File</dt>
47 * <dd>The file that is being referenced in the checksum file.</dd>
52 public class ChecksummedFile
54 private final Logger log = LoggerFactory.getLogger( ChecksummedFile.class );
56 private final File referenceFile;
59 * Construct a ChecksummedFile object.
61 * @param referenceFile
63 public ChecksummedFile( final File referenceFile )
65 this.referenceFile = referenceFile;
69 * Calculate the checksum based on a given checksum.
71 * @param checksumAlgorithm the algorithm to use.
72 * @return the checksum string for the file.
73 * @throws IOException if unable to calculate the checksum.
75 public String calculateChecksum( ChecksumAlgorithm checksumAlgorithm )
79 try (InputStream fis = Files.newInputStream( referenceFile.toPath() ) )
81 Checksum checksum = new Checksum( checksumAlgorithm );
82 checksum.update( fis );
83 return checksum.getChecksum();
88 * Creates a checksum file of the provided referenceFile.
90 * @param checksumAlgorithm the hash to use.
91 * @return the checksum File that was created.
92 * @throws IOException if there was a problem either reading the referenceFile, or writing the checksum file.
94 public File createChecksum( ChecksumAlgorithm checksumAlgorithm )
97 File checksumFile = new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
98 String checksum = calculateChecksum( checksumAlgorithm );
99 FileUtils.writeStringToFile( checksumFile, checksum + " " + referenceFile.getName() );
104 * Get the checksum file for the reference file and hash.
106 * @param checksumAlgorithm the hash that we are interested in.
107 * @return the checksum file to return
109 public File getChecksumFile( ChecksumAlgorithm checksumAlgorithm )
111 return new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
116 * Given a checksum file, check to see if the file it represents is valid according to the checksum.
120 * NOTE: Only supports single file checksums of type MD5 or SHA1.
123 * @param algorithm the algorithms to check for.
124 * @return true if the checksum is valid for the file it represents. or if the checksum file does not exist.
125 * @throws IOException if the reading of the checksumFile or the file it refers to fails.
127 public boolean isValidChecksum( ChecksumAlgorithm algorithm )
130 return isValidChecksums( new ChecksumAlgorithm[]{ algorithm } );
134 * Of any checksum files present, validate that the reference file conforms
135 * the to the checksum.
137 * @param algorithms the algorithms to check for.
138 * @return true if the checksums report that the the reference file is valid, false if invalid.
140 public boolean isValidChecksums( ChecksumAlgorithm algorithms[] )
143 try (InputStream fis = Files.newInputStream( referenceFile.toPath() ))
145 List<Checksum> checksums = new ArrayList<>( algorithms.length );
146 // Create checksum object for each algorithm.
147 for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
149 File checksumFile = getChecksumFile( checksumAlgorithm );
151 // Only add algorithm if checksum file exists.
152 if ( checksumFile.exists() )
154 checksums.add( new Checksum( checksumAlgorithm ) );
159 if ( checksums.isEmpty() )
161 // No checksum objects, no checksum files, default to is invalid.
165 // Parse file once, for all checksums.
168 Checksum.update( checksums, fis );
170 catch ( IOException e )
172 log.warn( "Unable to update checksum:{}", e.getMessage() );
176 boolean valid = true;
178 // check the checksum files
181 for ( Checksum checksum : checksums )
183 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
184 File checksumFile = getChecksumFile( checksumAlgorithm );
186 String rawChecksum = FileUtils.readFileToString( checksumFile );
187 String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
189 if ( !StringUtils.equalsIgnoreCase( expectedChecksum, checksum.getChecksum() ) )
195 catch ( IOException e )
197 log.warn( "Unable to read / parse checksum: {}", e.getMessage() );
202 } catch ( IOException e )
204 log.warn( "Unable to read / parse checksum: {}", e.getMessage() );
210 * Fix or create checksum files for the reference file.
212 * @param algorithms the hashes to check for.
213 * @return true if checksums were created successfully.
215 public boolean fixChecksums( ChecksumAlgorithm[] algorithms )
217 List<Checksum> checksums = new ArrayList<>( algorithms.length );
218 // Create checksum object for each algorithm.
219 for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
221 checksums.add( new Checksum( checksumAlgorithm ) );
225 if ( checksums.isEmpty() )
227 // No checksum objects, no checksum files, default to is valid.
232 try (InputStream fis = Files.newInputStream( referenceFile.toPath() ))
234 // Parse file once, for all checksums.
235 Checksum.update( checksums, fis );
237 catch ( IOException e )
239 log.warn( e.getMessage(), e );
243 boolean valid = true;
245 // check the hash files
246 for ( Checksum checksum : checksums )
248 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
251 File checksumFile = getChecksumFile( checksumAlgorithm );
252 String actualChecksum = checksum.getChecksum();
254 if ( checksumFile.exists() )
256 String rawChecksum = FileUtils.readFileToString( checksumFile );
257 String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
259 if ( !StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum ) )
261 // create checksum (again)
262 FileUtils.writeStringToFile( checksumFile, actualChecksum + " " + referenceFile.getName() );
267 FileUtils.writeStringToFile( checksumFile, actualChecksum + " " + referenceFile.getName() );
270 catch ( IOException e )
272 log.warn( e.getMessage(), e );
281 private boolean isValidChecksumPattern( String filename, String path )
283 // check if it is a remote metadata file
284 Pattern pattern = Pattern.compile( "maven-metadata-\\S*.xml" );
285 Matcher m = pattern.matcher( path );
288 return filename.endsWith( path ) || ( "-".equals( filename ) ) || filename.endsWith( "maven-metadata.xml" );
291 return filename.endsWith( path ) || ( "-".equals( filename ) );
295 * Parse a checksum string.
297 * Validate the expected path, and expected checksum algorithm, then return
298 * the trimmed checksum hex string.
300 * @param rawChecksumString
301 * @param expectedHash
302 * @param expectedPath
304 * @throws IOException
306 public String parseChecksum( String rawChecksumString, ChecksumAlgorithm expectedHash, String expectedPath )
309 String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim();
311 // Free-BSD / openssl
312 String regex = expectedHash.getType() + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
313 Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
316 String filename = m.group( 1 );
317 if ( !isValidChecksumPattern( filename, expectedPath ) )
319 throw new IOException(
320 "Supplied checksum file '" + filename + "' does not match expected file: '" + expectedPath + "'" );
322 trimmedChecksum = m.group( 2 );
327 m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
330 String filename = m.group( 2 );
331 if ( !isValidChecksumPattern( filename, expectedPath ) )
333 throw new IOException(
334 "Supplied checksum file '" + filename + "' does not match expected file: '" + expectedPath
337 trimmedChecksum = m.group( 1 );
340 return trimmedChecksum;