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