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