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
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.regex.Matcher;
28 import java.util.regex.Pattern;
30 import org.apache.commons.io.FileUtils;
31 import org.apache.commons.io.IOUtils;
32 import org.apache.commons.lang.StringUtils;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
40 * <lh>Terminology:</lh>
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>
50 * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
53 public class ChecksummedFile
55 private Logger log = LoggerFactory.getLogger( ChecksummedFile.class );
57 private final File referenceFile;
60 * Construct a ChecksummedFile object.
62 * @param referenceFile
64 public ChecksummedFile( final File referenceFile )
66 this.referenceFile = referenceFile;
70 * Calculate the checksum based on a given checksum.
72 * @param checksumAlgorithm the algorithm to use.
73 * @return the checksum string for the file.
74 * @throws IOException if unable to calculate the checksum.
76 public String calculateChecksum( ChecksumAlgorithm checksumAlgorithm )
79 FileInputStream fis = null;
82 Checksum checksum = new Checksum( checksumAlgorithm );
83 fis = new FileInputStream( referenceFile );
84 checksum.update( fis );
85 return checksum.getChecksum();
89 IOUtils.closeQuietly( fis );
94 * Creates a checksum file of the provided referenceFile.
95 * @param checksumAlgorithm the hash to use.
97 * @return the checksum File that was created.
98 * @throws IOException if there was a problem either reading the referenceFile, or writing the checksum file.
100 public File createChecksum( ChecksumAlgorithm checksumAlgorithm )
103 File checksumFile = new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
104 String checksum = calculateChecksum( checksumAlgorithm );
105 FileUtils.writeStringToFile( checksumFile, checksum + " " + referenceFile.getName() );
110 * Get the checksum file for the reference file and hash.
112 * @param checksumAlgorithm the hash that we are interested in.
113 * @return the checksum file to return
115 public File getChecksumFile( ChecksumAlgorithm checksumAlgorithm )
117 return new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
122 * Given a checksum file, check to see if the file it represents is valid according to the checksum.
126 * NOTE: Only supports single file checksums of type MD5 or SHA1.
129 * @param checksumFile the algorithms to check for.
130 * @return true if the checksum is valid for the file it represents. or if the checksum file does not exist.
131 * @throws IOException if the reading of the checksumFile or the file it refers to fails.
133 public boolean isValidChecksum( ChecksumAlgorithm algorithm )
136 return isValidChecksums( new ChecksumAlgorithm[] { algorithm } );
140 * Of any checksum files present, validate that the reference file conforms
141 * the to the checksum.
143 * @param algorithms the algorithms to check for.
144 * @return true if the checksums report that the the reference file is valid, false if invalid.
146 public boolean isValidChecksums( ChecksumAlgorithm algorithms[] )
148 FileInputStream fis = null;
151 List<Checksum> checksums = new ArrayList<Checksum>();
152 // Create checksum object for each algorithm.
153 for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
155 File checksumFile = getChecksumFile( checksumAlgorithm );
157 // Only add algorithm if checksum file exists.
158 if ( checksumFile.exists() )
160 checksums.add( new Checksum( checksumAlgorithm ) );
165 if ( checksums.isEmpty() )
167 // No checksum objects, no checksum files, default to is invalid.
171 // Parse file once, for all checksums.
174 fis = new FileInputStream( referenceFile );
175 Checksum.update( checksums, fis );
177 catch ( IOException e )
179 log.warn( "Unable to update checksum:" + e.getMessage() );
183 boolean valid = true;
185 // check the checksum files
188 for ( Checksum checksum : checksums )
190 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
191 File checksumFile = getChecksumFile( checksumAlgorithm );
193 String rawChecksum = FileUtils.readFileToString( checksumFile );
194 String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
196 if ( StringUtils.equalsIgnoreCase( expectedChecksum, checksum.getChecksum() ) == false )
202 catch ( IOException e )
204 log.warn( "Unable to read / parse checksum: " + e.getMessage() );
212 IOUtils.closeQuietly( fis );
217 * Fix or create checksum files for the reference file.
219 * @param algorithms the hashes to check for.
220 * @return true if checksums were created successfully.
222 public boolean fixChecksums( ChecksumAlgorithm algorithms[] )
224 List<Checksum> checksums = new ArrayList<Checksum>();
225 // Create checksum object for each algorithm.
226 for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
228 checksums.add( new Checksum( checksumAlgorithm ) );
232 if ( checksums.isEmpty() )
234 // No checksum objects, no checksum files, default to is valid.
238 FileInputStream fis = null;
241 // Parse file once, for all checksums.
242 fis = new FileInputStream( referenceFile );
243 Checksum.update( checksums, fis );
245 catch ( IOException e )
247 log.warn( e.getMessage(), e );
252 IOUtils.closeQuietly( fis );
255 boolean valid = true;
257 // check the hash files
258 for ( Checksum checksum : checksums )
260 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
263 File checksumFile = getChecksumFile( checksumAlgorithm );
264 String actualChecksum = checksum.getChecksum();
266 if ( checksumFile.exists() )
268 String rawChecksum = FileUtils.readFileToString( checksumFile );
269 String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
271 if ( StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum ) == false )
273 // create checksum (again)
274 FileUtils.writeStringToFile( checksumFile, actualChecksum + " " + referenceFile.getName() );
279 FileUtils.writeStringToFile( checksumFile, actualChecksum + " " + referenceFile.getName() );
282 catch ( IOException e )
284 log.warn( e.getMessage(), e );
293 private boolean isValidChecksumPattern( String filename, String path )
295 return filename.endsWith( path ) || ( "-".equals( filename ) );
299 * Parse a checksum string.
301 * Validate the expected path, and expected checksum algorithm, then return
302 * the trimmed checksum hex string.
304 * @param rawChecksumString
305 * @param expectedHash
306 * @param expectedPath
308 * @throws IOException
310 public String parseChecksum( String rawChecksumString, ChecksumAlgorithm expectedHash, String expectedPath )
313 String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim();
315 // Free-BSD / openssl
316 String regex = expectedHash.getType() + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
317 Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
320 String filename = m.group( 1 );
321 if ( !isValidChecksumPattern( filename, expectedPath ) )
323 throw new IOException( "Supplied checksum file '" + filename + "' does not match expected file: '"
324 + expectedPath + "'" );
326 trimmedChecksum = m.group( 2 );
331 m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
334 String filename = m.group( 2 );
335 if ( !isValidChecksumPattern( filename, expectedPath ) )
337 throw new IOException( "Supplied checksum file '" + filename + "' does not match expected file: '"
338 + expectedPath + "'" );
340 trimmedChecksum = m.group( 1 );
343 return trimmedChecksum;