]> source.dussan.org Git - archiva.git/blob
53a3d4658b45ed1d9206d8e1071120057e0041a0
[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.archiva.common.utils.FileUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
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;
36
37 import static org.apache.archiva.checksum.ChecksumValidationException.ValidationError.BAD_CHECKSUM_FILE_REF;
38
39 /**
40  * ChecksummedFile
41  * <p>Terminology:</p>
42  * <dl>
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.
47  * </dd>
48  * <dt>Reference File</dt>
49  * <dd>The file that is being referenced in the checksum file.</dd>
50  * </dl>
51  */
52 public class ChecksummedFile
53 {
54
55     private static Charset FILE_ENCODING = Charset.forName( "UTF-8" );
56
57     private final Logger log = LoggerFactory.getLogger( ChecksummedFile.class );
58
59     private static final Pattern METADATA_PATTERN = Pattern.compile( "maven-metadata-\\S*.xml" );
60
61     private final Path referenceFile;
62
63     /**
64      * Construct a ChecksummedFile object.
65      *
66      * @param referenceFile
67      */
68     public ChecksummedFile( final Path referenceFile )
69     {
70         this.referenceFile = referenceFile;
71     }
72
73
74     public static ChecksumReference getFromChecksumFile( Path checksumFile )
75     {
76         ChecksumAlgorithm alg = ChecksumAlgorithm.getByExtension( checksumFile );
77         ChecksummedFile file = new ChecksummedFile( getReferenceFile( checksumFile ) );
78         return new ChecksumReference( file, alg, checksumFile );
79     }
80
81     private static Path getReferenceFile( Path checksumFile )
82     {
83         String fileName = checksumFile.getFileName( ).toString( );
84         return checksumFile.resolveSibling( fileName.substring( 0, fileName.lastIndexOf( '.' ) ) );
85     }
86
87     /**
88      * Calculate the checksum based on a given checksum.
89      *
90      * @param checksumAlgorithm the algorithm to use.
91      * @return the checksum string for the file.
92      * @throws IOException if unable to calculate the checksum.
93      */
94     public String calculateChecksum( ChecksumAlgorithm checksumAlgorithm )
95         throws IOException
96     {
97
98         Checksum checksum = new Checksum( checksumAlgorithm );
99         checksum.update( referenceFile );
100         return checksum.getChecksum( );
101     }
102
103     /**
104      * Creates a checksum file of the provided referenceFile.
105      *
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.
109      */
110     public Path createChecksum( ChecksumAlgorithm checksumAlgorithm )
111         throws IOException
112     {
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 );
119         return checksumFile;
120     }
121
122     /**
123      * Get the checksum file for the reference file and hash.
124      *
125      * @param checksumAlgorithm the hash that we are interested in.
126      * @return the checksum file to return
127      */
128     public Path getChecksumFile( ChecksumAlgorithm checksumAlgorithm )
129     {
130         for ( String ext : checksumAlgorithm.getExt( ) )
131         {
132             Path file = referenceFile.resolveSibling( referenceFile.getFileName( ) + "." + checksumAlgorithm.getExt( ) );
133             if ( Files.exists( file ) )
134             {
135                 return file;
136             }
137         }
138         return referenceFile.resolveSibling( referenceFile.getFileName( ) + "." + checksumAlgorithm.getExt( ).get( 0 ) );
139     }
140
141     /**
142      * <p>
143      * Given a checksum file, check to see if the file it represents is valid according to the checksum.
144      * </p>
145      * <p>
146      * NOTE: Only supports single file checksums of type MD5 or SHA1.
147      * </p>
148      *
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.
152      */
153     public boolean isValidChecksum( ChecksumAlgorithm algorithm) throws ChecksumValidationException
154     {
155         return isValidChecksum( algorithm, false );
156     }
157     public boolean isValidChecksum( ChecksumAlgorithm algorithm, boolean throwExceptions )
158         throws ChecksumValidationException
159     {
160         return isValidChecksums( new ChecksumAlgorithm[]{algorithm} );
161     }
162
163     /**
164      * Of any checksum files present, validate that the reference file conforms
165      * the to the checksum.
166      *
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.
169      */
170     public boolean isValidChecksums( ChecksumAlgorithm algorithms[]) throws ChecksumValidationException
171     {
172         return isValidChecksums( algorithms, false );
173     }
174
175     /**
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
182      */
183     public boolean isValidChecksums( ChecksumAlgorithm algorithms[], boolean throwExceptions) throws ChecksumValidationException
184     {
185
186         List<Checksum> checksums = new ArrayList<>( algorithms.length );
187         // Create checksum object for each algorithm.
188         for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
189         {
190             Path checksumFile = getChecksumFile( checksumAlgorithm );
191
192             // Only add algorithm if checksum file exists.
193             if ( Files.exists( checksumFile ) )
194             {
195                 checksums.add( new Checksum( checksumAlgorithm ) );
196             }
197         }
198
199         // Any checksums?
200         if ( checksums.isEmpty( ) )
201         {
202             // No checksum objects, no checksum files, default to is invalid.
203             return false;
204         }
205
206         // Parse file once, for all checksums.
207         try
208         {
209             Checksum.update( checksums, referenceFile );
210         }
211         catch ( ChecksumValidationException e )
212         {
213             log.warn( "Unable to update checksum:{}", e.getMessage( ) );
214             if (throwExceptions) {
215                 throw e;
216             } else {
217                 return false;
218             }
219         }
220
221         boolean valid = true;
222
223         // check the checksum files
224         try
225         {
226             for ( Checksum checksum : checksums )
227             {
228                 ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm( );
229                 Path checksumFile = getChecksumFile( checksumAlgorithm );
230
231                 String expectedChecksum = parseChecksum( checksumFile, checksumAlgorithm, referenceFile.getFileName( ).toString( ), FILE_ENCODING );
232
233                 if ( !checksum.compare( expectedChecksum ) )
234                 {
235                     valid = false;
236                 }
237             }
238         }
239         catch ( ChecksumValidationException e )
240         {
241             log.warn( "Unable to read / parse checksum: {}", e.getMessage( ) );
242             if (throwExceptions) {
243                 throw e;
244             } else
245             {
246                 return false;
247             }
248         }
249
250         return valid;
251     }
252
253     public Path getReferenceFile( )
254     {
255         return referenceFile;
256     }
257
258     /**
259      * Fix or create checksum files for the reference file.
260      *
261      * @param algorithms the hashes to check for.
262      * @return true if checksums were created successfully.
263      */
264     public boolean fixChecksums( ChecksumAlgorithm[] algorithms )
265     {
266         List<Checksum> checksums = new ArrayList<>( algorithms.length );
267         // Create checksum object for each algorithm.
268         for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
269         {
270             checksums.add( new Checksum( checksumAlgorithm ) );
271         }
272
273         // Any checksums?
274         if ( checksums.isEmpty( ) )
275         {
276             // No checksum objects, no checksum files, default to is valid.
277             return true;
278         }
279
280         try
281         {
282             // Parse file once, for all checksums.
283             Checksum.update( checksums, referenceFile );
284         }
285         catch ( ChecksumValidationException e )
286         {
287             log.warn( e.getMessage( ), e );
288             return false;
289         }
290
291         boolean valid = true;
292
293         // check the hash files
294         for ( Checksum checksum : checksums )
295         {
296             ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm( );
297             try
298             {
299                 Path checksumFile = getChecksumFile( checksumAlgorithm );
300                 if ( Files.exists( checksumFile ) )
301                 {
302                     String expectedChecksum = parseChecksum( checksumFile, checksumAlgorithm, referenceFile.getFileName( ).toString( ), FILE_ENCODING );
303
304                     if ( !checksum.compare( expectedChecksum ) )
305                     {
306                         // create checksum (again)
307                         writeChecksumFile( checksumFile, FILE_ENCODING, checksum.getChecksum( ) );
308                     }
309                 }
310                 else
311                 {
312                     writeChecksumFile( checksumFile, FILE_ENCODING, checksum.getChecksum( ) );
313                 }
314             }
315             catch ( ChecksumValidationException e )
316             {
317                 log.warn( e.getMessage( ), e );
318                 valid = false;
319             }
320         }
321
322         return valid;
323
324     }
325
326     private void writeChecksumFile( Path checksumFile, Charset encoding, String checksumHex )
327     {
328         FileUtils.writeStringToFile( checksumFile, FILE_ENCODING, checksumHex + "  " + referenceFile.getFileName( ).toString( ) );
329     }
330
331     private boolean isValidChecksumPattern( String filename, String path )
332     {
333         // check if it is a remote metadata file
334
335         Matcher m = METADATA_PATTERN.matcher( path );
336         if ( m.matches( ) )
337         {
338             return filename.endsWith( path ) || ( "-".equals( filename ) ) || filename.endsWith( "maven-metadata.xml" );
339         }
340
341         return filename.endsWith( path ) || ( "-".equals( filename ) );
342     }
343
344     /**
345      * Parse a checksum string.
346      * <p>
347      * Validate the expected path, and expected checksum algorithm, then return
348      * the trimmed checksum hex string.
349      * </p>
350      *
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
354      * @return
355      * @throws IOException
356      */
357     public String parseChecksum( Path checksumFile, ChecksumAlgorithm expectedHash, String expectedPath, Charset encoding )
358         throws ChecksumValidationException
359     {
360         ChecksumFileContent fc = parseChecksumFile( checksumFile, expectedHash, encoding );
361         if ( fc.isFormatMatch() && !isValidChecksumPattern( fc.getFileReference( ), expectedPath ) )
362         {
363             throw new ChecksumValidationException(BAD_CHECKSUM_FILE_REF,
364                 "The file reference '" + fc.getFileReference( ) + "' in the checksum file does not match expected file: '" + expectedPath + "'" );
365         }
366         return fc.getChecksum( );
367     }
368
369     public ChecksumFileContent parseChecksumFile( Path checksumFile, ChecksumAlgorithm expectedHash, Charset encoding )
370     {
371         ChecksumFileContent fc = new ChecksumFileContent( );
372         String rawChecksumString = FileUtils.readFileToString( checksumFile, encoding );
373         String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim( );
374
375         // Free-BSD / openssl
376         String regex = expectedHash.getType( ) + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
377         Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
378         if ( m.matches( ) )
379         {
380             fc.setFileReference( m.group( 1 ) );
381             fc.setChecksum( m.group( 2 ) );
382             fc.setFormatMatch( true );
383         }
384         else
385         {
386             // GNU tools
387             m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
388             if ( m.matches( ) )
389             {
390                 fc.setFileReference( m.group( 2 ) );
391                 fc.setChecksum( m.group( 1 ) );
392                 fc.setFormatMatch( true );
393             }
394             else
395             {
396                 fc.setFileReference( "" );
397                 fc.setChecksum( trimmedChecksum );
398                 fc.setFormatMatch( false );
399             }
400         }
401         return fc;
402     }
403 }