]> source.dussan.org Git - archiva.git/blob
d93ec53a3a537f3e880e945d10ce2b81e8916899
[archiva.git] /
1 package org.apache.archiva.checksum;
2
3 /*
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
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
22 import java.io.File;
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;
29
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;
35
36 /**
37  * ChecksummedFile
38  *
39  * <dl>
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.
45  *   </dd>
46  *   <dt>Reference File</dt>
47  *   <dd>The file that is being referenced in the checksum file.</dd>
48  * </dl>
49  *
50  * @author <a href="mailto:joakime@apache.org">Joakim Erdfelt</a>
51  * @version $Id$
52  */
53 public class ChecksummedFile
54 {
55     private Logger log = LoggerFactory.getLogger( ChecksummedFile.class );
56
57     private final File referenceFile;
58
59     /**
60      * Construct a ChecksummedFile object.
61      * 
62      * @param referenceFile
63      */
64     public ChecksummedFile( final File referenceFile )
65     {
66         this.referenceFile = referenceFile;
67     }
68
69     /**
70      * Calculate the checksum based on a given checksum.
71      * 
72      * @param checksumAlgorithm the algorithm to use.
73      * @return the checksum string for the file.
74      * @throws IOException if unable to calculate the checksum.
75      */
76     public String calculateChecksum( ChecksumAlgorithm checksumAlgorithm )
77         throws IOException
78     {
79         FileInputStream fis = null;
80         try
81         {
82             Checksum checksum = new Checksum( checksumAlgorithm );
83             fis = new FileInputStream( referenceFile );
84             checksum.update( fis );
85             return checksum.getChecksum();
86         }
87         finally
88         {
89             IOUtils.closeQuietly( fis );
90         }
91     }
92
93     /**
94      * Creates a checksum file of the provided referenceFile.
95      * @param checksumAlgorithm the hash to use.
96      * 
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.
99      */
100     public File createChecksum( ChecksumAlgorithm checksumAlgorithm )
101         throws IOException
102     {
103         File checksumFile = new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
104         String checksum = calculateChecksum( checksumAlgorithm );
105         FileUtils.writeStringToFile( checksumFile, checksum + "  " + referenceFile.getName() );
106         return checksumFile;
107     }
108
109     /**
110      * Get the checksum file for the reference file and hash.
111      * 
112      * @param checksumAlgorithm the hash that we are interested in.
113      * @return the checksum file to return
114      */
115     public File getChecksumFile( ChecksumAlgorithm checksumAlgorithm )
116     {
117         return new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
118     }
119
120     /**
121      * <p>
122      * Given a checksum file, check to see if the file it represents is valid according to the checksum.
123      * </p>
124      * 
125      * <p>
126      * NOTE: Only supports single file checksums of type MD5 or SHA1.
127      * </p>
128      * 
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.
132      */
133     public boolean isValidChecksum( ChecksumAlgorithm algorithm )
134         throws IOException
135     {
136         return isValidChecksums( new ChecksumAlgorithm[] { algorithm } );
137     }
138
139     /**
140      * Of any checksum files present, validate that the reference file conforms
141      * the to the checksum.   
142      * 
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.
145      */
146     public boolean isValidChecksums( ChecksumAlgorithm algorithms[] )
147     {
148         FileInputStream fis = null;
149         try
150         {
151             List<Checksum> checksums = new ArrayList<Checksum>();
152             // Create checksum object for each algorithm.
153             for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
154             {
155                 File checksumFile = getChecksumFile( checksumAlgorithm );
156
157                 // Only add algorithm if checksum file exists.
158                 if ( checksumFile.exists() )
159                 {
160                     checksums.add( new Checksum( checksumAlgorithm ) );
161                 }
162             }
163
164             // Any checksums?
165             if ( checksums.isEmpty() )
166             {
167                 // No checksum objects, no checksum files, default to is invalid.
168                 return false;
169             }
170
171             // Parse file once, for all checksums.
172             try
173             {
174                 fis = new FileInputStream( referenceFile );
175                 Checksum.update( checksums, fis );
176             }
177             catch ( IOException e )
178             {
179                 log.warn( "Unable to update checksum:" + e.getMessage() );
180                 return false;
181             }
182
183             boolean valid = true;
184
185             // check the checksum files
186             try
187             {
188                 for ( Checksum checksum : checksums )
189                 {
190                     ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
191                     File checksumFile = getChecksumFile( checksumAlgorithm );
192
193                     String rawChecksum = FileUtils.readFileToString( checksumFile );
194                     String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
195
196                     if ( StringUtils.equalsIgnoreCase( expectedChecksum, checksum.getChecksum() ) == false )
197                     {
198                         valid = false;
199                     }
200                 }
201             }
202             catch ( IOException e )
203             {
204                 log.warn( "Unable to read / parse checksum: " + e.getMessage() );
205                 return false;
206             }
207
208             return valid;
209         }
210         finally
211         {
212             IOUtils.closeQuietly( fis );
213         }
214     }
215
216     /**
217      * Fix or create checksum files for the reference file.
218      * 
219      * @param algorithms the hashes to check for.
220      * @return true if checksums were created successfully.
221      */
222     public boolean fixChecksums( ChecksumAlgorithm algorithms[] )
223     {
224         List<Checksum> checksums = new ArrayList<Checksum>();
225         // Create checksum object for each algorithm.
226         for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
227         {
228             checksums.add( new Checksum( checksumAlgorithm ) );
229         }
230
231         // Any checksums?
232         if ( checksums.isEmpty() )
233         {
234             // No checksum objects, no checksum files, default to is valid.
235             return true;
236         }
237
238         FileInputStream fis = null;
239         try
240         {
241             // Parse file once, for all checksums.
242             fis = new FileInputStream( referenceFile );
243             Checksum.update( checksums, fis );
244         }
245         catch ( IOException e )
246         {
247             log.warn( e.getMessage(), e );
248             return false;
249         }
250         finally
251         {
252             IOUtils.closeQuietly( fis );
253         }
254
255         boolean valid = true;
256
257         // check the hash files
258         for ( Checksum checksum : checksums )
259         {
260             ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
261             try
262             {
263                 File checksumFile = getChecksumFile( checksumAlgorithm );
264                 String actualChecksum = checksum.getChecksum();
265
266                 if ( checksumFile.exists() )
267                 {
268                     String rawChecksum = FileUtils.readFileToString( checksumFile );
269                     String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
270
271                     if ( StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum ) == false )
272                     {
273                         // create checksum (again)
274                         FileUtils.writeStringToFile( checksumFile, actualChecksum + "  " + referenceFile.getName() );
275                     }
276                 }
277                 else
278                 {
279                     FileUtils.writeStringToFile( checksumFile, actualChecksum + "  " + referenceFile.getName() );
280                 }
281             }
282             catch ( IOException e )
283             {
284                 log.warn( e.getMessage(), e );
285                 valid = false;
286             }
287         }
288
289         return valid;
290
291     }
292
293     private boolean isValidChecksumPattern( String filename, String path )
294     {
295         return filename.endsWith( path ) || ( "-".equals( filename ) );
296     }
297
298     /**
299      * Parse a checksum string.
300      * 
301      * Validate the expected path, and expected checksum algorithm, then return
302      * the trimmed checksum hex string. 
303      * 
304      * @param rawChecksumString
305      * @param expectedHash
306      * @param expectedPath
307      * @return
308      * @throws IOException
309      */
310     public String parseChecksum( String rawChecksumString, ChecksumAlgorithm expectedHash, String expectedPath )
311         throws IOException
312     {
313         String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim();
314
315         // Free-BSD / openssl
316         String regex = expectedHash.getType() + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
317         Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
318         if ( m.matches() )
319         {
320             String filename = m.group( 1 );
321             if ( !isValidChecksumPattern( filename, expectedPath ) )
322             {
323                 throw new IOException( "Supplied checksum file '" + filename + "' does not match expected file: '"
324                     + expectedPath + "'" );
325             }
326             trimmedChecksum = m.group( 2 );
327         }
328         else
329         {
330             // GNU tools
331             m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
332             if ( m.matches() )
333             {
334                 String filename = m.group( 2 );
335                 if ( !isValidChecksumPattern( filename, expectedPath ) )
336                 {
337                     throw new IOException( "Supplied checksum file '" + filename + "' does not match expected file: '"
338                         + expectedPath + "'" );
339                 }
340                 trimmedChecksum = m.group( 1 );
341             }
342         }
343         return trimmedChecksum;
344     }
345 }