Coverage Report - org.apache.archiva.checksum.ChecksummedFile
 
Classes in this File Line Coverage Branch Coverage Complexity
ChecksummedFile
0%
0/97
0%
0/40
0
 
 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  
  * @version $Id: ChecksummedFile.java 718864 2008-11-19 06:33:35Z brett $
 51  
  */
 52  
 public class ChecksummedFile
 53  
 {
 54  0
     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  0
     {
 65  0
         this.referenceFile = referenceFile;
 66  0
     }
 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  0
         FileInputStream fis = null;
 79  
         try
 80  
         {
 81  0
             Checksum checksum = new Checksum( checksumAlgorithm );
 82  0
             fis = new FileInputStream( referenceFile );
 83  0
             checksum.update( fis );
 84  0
             return checksum.getChecksum();
 85  
         }
 86  
         finally
 87  
         {
 88  0
             IOUtils.closeQuietly( fis );
 89  
         }
 90  
     }
 91  
 
 92  
     /**
 93  
      * Creates a checksum file of the provided referenceFile.
 94  
      * @param checksumAlgorithm the hash to use.
 95  
      * 
 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  0
         File checksumFile = new File( referenceFile.getAbsolutePath() + "." + checksumAlgorithm.getExt() );
 103  0
         String checksum = calculateChecksum( checksumAlgorithm );
 104  0
         FileUtils.writeStringToFile( checksumFile, checksum + "  " + referenceFile.getName() );
 105  0
         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  0
         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  
      * 
 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  0
         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  0
         FileInputStream fis = null;
 148  
         try
 149  
         {
 150  0
             List<Checksum> checksums = new ArrayList<Checksum>();
 151  
             // Create checksum object for each algorithm.
 152  0
             for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
 153  
             {
 154  0
                 File checksumFile = getChecksumFile( checksumAlgorithm );
 155  
 
 156  
                 // Only add algorithm if checksum file exists.
 157  0
                 if ( checksumFile.exists() )
 158  
                 {
 159  0
                     checksums.add( new Checksum( checksumAlgorithm ) );
 160  
                 }
 161  
             }
 162  
 
 163  
             // Any checksums?
 164  0
             if ( checksums.isEmpty() )
 165  
             {
 166  
                 // No checksum objects, no checksum files, default to is invalid.
 167  0
                 return false;
 168  
             }
 169  
 
 170  
             // Parse file once, for all checksums.
 171  
             try
 172  
             {
 173  0
                 fis = new FileInputStream( referenceFile );
 174  0
                 Checksum.update( checksums, fis );
 175  
             }
 176  0
             catch ( IOException e )
 177  
             {
 178  0
                 log.warn( "Unable to update checksum:" + e.getMessage() );
 179  0
                 return false;
 180  0
             }
 181  
 
 182  0
             boolean valid = true;
 183  
 
 184  
             // check the checksum files
 185  
             try
 186  
             {
 187  0
                 for ( Checksum checksum : checksums )
 188  
                 {
 189  0
                     ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
 190  0
                     File checksumFile = getChecksumFile( checksumAlgorithm );
 191  
 
 192  0
                     String rawChecksum = FileUtils.readFileToString( checksumFile );
 193  0
                     String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
 194  
 
 195  0
                     if ( StringUtils.equalsIgnoreCase( expectedChecksum, checksum.getChecksum() ) == false )
 196  
                     {
 197  0
                         valid = false;
 198  
                     }
 199  0
                 }
 200  
             }
 201  0
             catch ( IOException e )
 202  
             {
 203  0
                 log.warn( "Unable to read / parse checksum: " + e.getMessage() );
 204  0
                 return false;
 205  0
             }
 206  
 
 207  0
             return valid;
 208  
         }
 209  
         finally
 210  
         {
 211  0
             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  0
         List<Checksum> checksums = new ArrayList<Checksum>();
 224  
         // Create checksum object for each algorithm.
 225  0
         for ( ChecksumAlgorithm checksumAlgorithm : algorithms )
 226  
         {
 227  0
             checksums.add( new Checksum( checksumAlgorithm ) );
 228  
         }
 229  
 
 230  
         // Any checksums?
 231  0
         if ( checksums.isEmpty() )
 232  
         {
 233  
             // No checksum objects, no checksum files, default to is valid.
 234  0
             return true;
 235  
         }
 236  
 
 237  0
         FileInputStream fis = null;
 238  
         try
 239  
         {
 240  
             // Parse file once, for all checksums.
 241  0
             fis = new FileInputStream( referenceFile );
 242  0
             Checksum.update( checksums, fis );
 243  
         }
 244  0
         catch ( IOException e )
 245  
         {
 246  0
             log.warn( e.getMessage(), e );
 247  0
             return false;
 248  
         }
 249  
         finally
 250  
         {
 251  0
             IOUtils.closeQuietly( fis );
 252  0
         }
 253  
 
 254  0
         boolean valid = true;
 255  
 
 256  
         // check the hash files
 257  0
         for ( Checksum checksum : checksums )
 258  
         {
 259  0
             ChecksumAlgorithm checksumAlgorithm = checksum.getAlgorithm();
 260  
             try
 261  
             {
 262  0
                 File checksumFile = getChecksumFile( checksumAlgorithm );
 263  0
                 String actualChecksum = checksum.getChecksum();
 264  
 
 265  0
                 if ( checksumFile.exists() )
 266  
                 {
 267  0
                     String rawChecksum = FileUtils.readFileToString( checksumFile );
 268  0
                     String expectedChecksum = parseChecksum( rawChecksum, checksumAlgorithm, referenceFile.getName() );
 269  
 
 270  0
                     if ( StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum ) == false )
 271  
                     {
 272  
                         // create checksum (again)
 273  0
                         FileUtils.writeStringToFile( checksumFile, actualChecksum + "  " + referenceFile.getName() );
 274  
                     }
 275  0
                 }
 276  
                 else
 277  
                 {
 278  0
                     FileUtils.writeStringToFile( checksumFile, actualChecksum + "  " + referenceFile.getName() );
 279  
                 }
 280  
             }
 281  0
             catch ( IOException e )
 282  
             {
 283  0
                 log.warn( e.getMessage(), e );
 284  0
                 valid = false;
 285  0
             }
 286  0
         }
 287  
 
 288  0
         return valid;
 289  
 
 290  
     }
 291  
 
 292  
     private boolean isValidChecksumPattern( String filename, String path )
 293  
     {
 294  
         // check if it is a remote metadata file
 295  0
         Pattern pattern = Pattern.compile( "maven-metadata-\\S*.xml" );
 296  0
         Matcher m = pattern.matcher( path );
 297  0
         if( m.matches() )
 298  
         {
 299  0
             return filename.endsWith( path ) || ( "-".equals( filename ) ) 
 300  
                 || filename.endsWith( "maven-metadata.xml" );
 301  
         }
 302  
         
 303  0
         return filename.endsWith( path ) || ( "-".equals( filename ) );
 304  
     }
 305  
 
 306  
     /**
 307  
      * Parse a checksum string.
 308  
      * 
 309  
      * Validate the expected path, and expected checksum algorithm, then return
 310  
      * the trimmed checksum hex string. 
 311  
      * 
 312  
      * @param rawChecksumString
 313  
      * @param expectedHash
 314  
      * @param expectedPath
 315  
      * @return
 316  
      * @throws IOException
 317  
      */
 318  
     public String parseChecksum( String rawChecksumString, ChecksumAlgorithm expectedHash, String expectedPath )
 319  
         throws IOException
 320  
     {
 321  0
         String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim();
 322  
 
 323  
         // Free-BSD / openssl
 324  0
         String regex = expectedHash.getType() + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
 325  0
         Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
 326  0
         if ( m.matches() )
 327  
         {
 328  0
             String filename = m.group( 1 );
 329  0
             if ( !isValidChecksumPattern( filename, expectedPath ) )
 330  
             {
 331  0
                 throw new IOException( "Supplied checksum file '" + filename + "' does not match expected file: '"
 332  
                     + expectedPath + "'" );
 333  
             }
 334  0
             trimmedChecksum = m.group( 2 );
 335  0
         }
 336  
         else
 337  
         {
 338  
             // GNU tools
 339  0
             m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
 340  0
             if ( m.matches() )
 341  
             {
 342  0
                 String filename = m.group( 2 );
 343  0
                 if ( !isValidChecksumPattern( filename, expectedPath ) )
 344  
                 {
 345  0
                     throw new IOException( "Supplied checksum file '" + filename + "' does not match expected file: '"
 346  
                         + expectedPath + "'" );
 347  
                 }
 348  0
                 trimmedChecksum = m.group( 1 );
 349  
             }
 350  
         }
 351  0
         return trimmedChecksum;
 352  
     }
 353  
 }