You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by jo...@apache.org on 2008/04/07 06:08:38 UTC

svn commit: r645362 - in /archiva/sandbox/archiva-checksum: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/archiva/ src/main/java/org/apache/archiva/checksum/ src/main/resources/ src/test/ src/tes...

Author: joakime
Date: Sun Apr  6 21:08:30 2008
New Revision: 645362

URL: http://svn.apache.org/viewvc?rev=645362&view=rev
Log:
Commiting first take of effort to consolidate the various checksum / hashing functions
present in the archiva and plexus codebases.


Added:
    archiva/sandbox/archiva-checksum/   (with props)
    archiva/sandbox/archiva-checksum/pom.xml   (with props)
    archiva/sandbox/archiva-checksum/src/
    archiva/sandbox/archiva-checksum/src/main/
    archiva/sandbox/archiva-checksum/src/main/java/
    archiva/sandbox/archiva-checksum/src/main/java/org/
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java   (with props)
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java   (with props)
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java   (with props)
    archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java   (with props)
    archiva/sandbox/archiva-checksum/src/main/resources/
    archiva/sandbox/archiva-checksum/src/test/
    archiva/sandbox/archiva-checksum/src/test/java/
    archiva/sandbox/archiva-checksum/src/test/java/org/
    archiva/sandbox/archiva-checksum/src/test/java/org/apache/
    archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/
    archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/
    archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java   (with props)
    archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java   (with props)
    archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java   (with props)
    archiva/sandbox/archiva-checksum/src/test/resources/
    archiva/sandbox/archiva-checksum/src/test/resources/examples/
    archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar   (with props)
    archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.md5
    archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.sha1
    archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar   (with props)
    archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.md5
    archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.sha1

Propchange: archiva/sandbox/archiva-checksum/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Sun Apr  6 21:08:30 2008
@@ -0,0 +1,21 @@
+target
+*~
+.*.swp
+*.log
+*.patch
+*.diff
+*.ipr
+*.iws
+*.iml
+.classpath
+.project
+.settings
+.m2eclipse
+.wtpmodules
+.xdoclet
+*.ipr
+*.iws
+*.iml
+CVS
+cobertura.ser
+jcoverage.ser

Added: archiva/sandbox/archiva-checksum/pom.xml
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/pom.xml?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/pom.xml (added)
+++ archiva/sandbox/archiva-checksum/pom.xml Sun Apr  6 21:08:30 2008
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2006 The Codehaus.
+  ~ 
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~ 
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~ 
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.archiva</groupId>
+    <artifactId>archiva-modules</artifactId>
+    <version>1.1-SNAPSHOT</version>
+  </parent>
+  <artifactId>archiva-checksum</artifactId>
+  <name>Archiva Checksum</name>
+  <version>1.0-SNAPSHOT</version>
+
+  <packaging>jar</packaging>
+
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-lang</groupId>
+      <artifactId>commons-lang</artifactId>
+      <version>2.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>1.3.2</version>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>

Propchange: archiva/sandbox/archiva-checksum/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/pom.xml
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Propchange: archiva/sandbox/archiva-checksum/pom.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java (added)
+++ archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,349 @@
+package org.apache.archiva.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * ChecksumFile
+ *
+ * <dl>
+ *   <lh>Terminology:</lh>
+ *   <dt>Checksum File</dt>
+ *   <dd>The file that contains the previously calculated checksum value for the reference file.
+ *       This is a text file with the extension ".sha1" or ".md5", and contains a single entry
+ *       consisting of an optional reference filename, and a checksum string.
+ *   </dd>
+ *   <dt>Reference File</dt>
+ *   <dd>The file that is being referenced in the checksum file.</dd>
+ * </dl>
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class ChecksumFile
+{
+    private Logger log = LoggerFactory.getLogger( ChecksumFile.class );
+
+    /**
+     * Using a given checksum.
+     * 
+     * @param hash the hash to use.
+     * @param referenceFile the file to calculate the checksum for.
+     * @return the checksum string for the file.
+     * @throws IOException if unable to calculate the checksum.
+     */
+    public String calculateChecksum( Hash hash, File referenceFile )
+        throws IOException
+    {
+        FileInputStream fis = null;
+        try
+        {
+            Hasher hasher = new Hasher( hash );
+            fis = new FileInputStream( referenceFile );
+            hasher.update( fis );
+            return hasher.getChecksum();
+        }
+        finally
+        {
+            IOUtils.closeQuietly( fis );
+        }
+    }
+
+    /**
+     * Creates a checksum file of the provided referenceFile.
+     * 
+     * @param referenceFile the file to checksum.
+     * @param hash the hash to use.
+     * @return the checksum File that was created.
+     * @throws IOException if there was a problem either reading the referenceFile, or writing the checksum file.
+     */
+    public File createChecksum( File referenceFile, Hash hash )
+        throws IOException
+    {
+        File checksumFile = new File( referenceFile.getAbsolutePath() + "." + hash.getExt() );
+        String checksum = calculateChecksum( hash, referenceFile );
+        FileUtils.writeStringToFile( checksumFile, checksum + "  " + referenceFile.getName() );
+        return checksumFile;
+    }
+
+    /**
+     * Get the checksum file for the reference file and hash.
+     * 
+     * @param hash the hash that we are interested in.
+     * @param referenceFile the reference file to work with.
+     * @return the checksum file to return
+     */
+    public File getChecksumFile( Hash hash, File referenceFile )
+    {
+        return new File( referenceFile.getAbsolutePath() + "." + hash.getExt() );
+    }
+
+    /**
+     * <p>
+     * Given a checksum file, check to see if the file it represents is valid according to the checksum.
+     * </p>
+     * 
+     * <p>
+     * NOTE: Only supports single file checksums of type MD5 or SHA1.
+     * </p>
+     * 
+     * @param checksumFile the checksum file (must end in ".sha1" or ".md5")
+     * @return true if the checksum is valid for the file it represents.
+     * @throws DigesterException if there is a digester problem during the check of the reference file. 
+     * @throws FileNotFoundException if the checksumFile itself or the file it refers to is not found.
+     * @throws IOException if the reading of the checksumFile or the file it refers to fails.
+     */
+    public boolean isValidChecksum( File checksumFile )
+        throws FileNotFoundException, IOException
+    {
+        if ( !checksumFile.exists() )
+        {
+            throw new FileNotFoundException( "Unable to find checksum file " + checksumFile.getAbsolutePath() );
+        }
+
+        if ( !checksumFile.isFile() )
+        {
+            throw new IOException( "Unable to load checksum from non-file " + checksumFile.getAbsolutePath() );
+        }
+
+        Hash hash = Hash.getHashByExtension( checksumFile );
+
+        File parentDir = checksumFile.getParentFile();
+        String filename = FilenameUtils.removeExtension( checksumFile.getName() );
+
+        // Reference file is the file that the checksum file is refering to.
+        File referenceFile = new File( parentDir, filename );
+
+        String rawChecksum = FileUtils.readFileToString( checksumFile );
+        String expectedChecksum = parseChecksum( rawChecksum, hash, referenceFile.getName() );
+
+        String actualChecksum = calculateChecksum( hash, referenceFile );
+
+        return StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum );
+    }
+
+    /**
+     * Of any checksum files present, validate that the reference file conforms
+     * the to the checksum.   
+     * 
+     * @param hashes the hashes to check for.
+     * @param referenceFile the reference file to cehck.
+     * @return true if the checksums report that the the reference file is valid.
+     * @throws IOException if unable to validate the checksums.
+     */
+    public boolean isValidChecksums( Hash hashes[], File referenceFile )
+        throws IOException
+    {
+        FileInputStream fis = null;
+        try
+        {
+            List<Hasher> hashers = new ArrayList<Hasher>();
+            // Create hasher for each hash.
+            for ( Hash hash : hashes )
+            {
+                File checksumFile = getChecksumFile( hash, referenceFile );
+
+                // Only add hasher if checksum file exists.
+                if ( checksumFile.exists() )
+                {
+                    hashers.add( new Hasher( hash ) );
+                }
+            }
+
+            // Any hashers?
+            if ( hashers.isEmpty() )
+            {
+                // No hashers, no checksum files, default to is valid.
+                return true;
+            }
+
+            // Parse file once, for all hashers.
+            fis = new FileInputStream( referenceFile );
+            Hasher.update( hashers, fis );
+
+            boolean valid = true;
+
+            // check the hash files
+            for ( Hasher hasher : hashers )
+            {
+                Hash hash = hasher.getHash();
+                File checksumFile = getChecksumFile( hash, referenceFile );
+
+                String rawChecksum = FileUtils.readFileToString( checksumFile );
+                String expectedChecksum = parseChecksum( rawChecksum, hash, referenceFile.getName() );
+
+                if ( StringUtils.equalsIgnoreCase( expectedChecksum, hasher.getChecksum() ) == false )
+                {
+                    valid = false;
+                }
+            }
+
+            return valid;
+        }
+        finally
+        {
+            IOUtils.closeQuietly( fis );
+        }
+    }
+
+    /**
+     * Fix or create checksum files for the reference file.
+     * 
+     * @param hashes the hashes to check for.
+     * @param referenceFile the reference file to check against.
+     * @return true if checksums were created successfully.
+     */
+    public boolean fixChecksums( Hash hashes[], File referenceFile )
+    {
+        List<Hasher> hashers = new ArrayList<Hasher>();
+        // Create hasher for each hash.
+        for ( Hash hash : hashes )
+        {
+            hashers.add( new Hasher( hash ) );
+        }
+
+        // Any hashers?
+        if ( hashers.isEmpty() )
+        {
+            // No hashers, no checksum files, default to is valid.
+            return true;
+        }
+
+        FileInputStream fis = null;
+        try
+        {
+            // Parse file once, for all hashers.
+            fis = new FileInputStream( referenceFile );
+            Hasher.update( hashers, fis );
+        }
+        catch ( IOException e )
+        {
+            log.warn( e.getMessage(), e );
+            return false;
+        }
+        finally
+        {
+            IOUtils.closeQuietly( fis );
+        }
+
+        boolean valid = true;
+
+        // check the hash files
+        for ( Hasher hasher : hashers )
+        {
+            Hash hash = hasher.getHash();
+            try
+            {
+                File checksumFile = getChecksumFile( hash, referenceFile );
+                String actualChecksum = hasher.getChecksum();
+
+                if ( checksumFile.exists() )
+                {
+                    String rawChecksum = FileUtils.readFileToString( checksumFile );
+                    String expectedChecksum = parseChecksum( rawChecksum, hash, referenceFile.getName() );
+
+                    if ( StringUtils.equalsIgnoreCase( expectedChecksum, actualChecksum ) == false )
+                    {
+                        // create checksum (again)
+                        FileUtils.writeStringToFile( checksumFile, actualChecksum + "  " + referenceFile.getName() );
+                    }
+                }
+                else
+                {
+                    FileUtils.writeStringToFile( checksumFile, actualChecksum + "  " + referenceFile.getName() );
+                }
+            }
+            catch ( IOException e )
+            {
+                log.warn( e.getMessage(), e );
+                valid = false;
+            }
+        }
+
+        return valid;
+
+    }
+
+    private boolean isValidChecksumPattern( String filename, String path )
+    {
+        return filename.endsWith( path ) || ( "-".equals( filename ) );
+    }
+
+    /**
+     * Parse a checksum string.
+     * 
+     * Validate the expected path, and expected checksum algorithm, then return
+     * the trimmed checksum hex string. 
+     * 
+     * @param rawChecksumString
+     * @param expectedHash
+     * @param expectedPath
+     * @return
+     * @throws IOException
+     */
+    public String parseChecksum( String rawChecksumString, Hash expectedHash, String expectedPath )
+        throws IOException
+    {
+        String trimmedChecksum = rawChecksumString.replace( '\n', ' ' ).trim();
+
+        // Free-BSD / openssl
+        String regex = expectedHash.getType() + "\\s*\\(([^)]*)\\)\\s*=\\s*([a-fA-F0-9]+)";
+        Matcher m = Pattern.compile( regex ).matcher( trimmedChecksum );
+        if ( m.matches() )
+        {
+            String filename = m.group( 1 );
+            if ( !isValidChecksumPattern( filename, expectedPath ) )
+            {
+                throw new IOException( "Supplied checksum does not match checksum pattern" );
+            }
+            trimmedChecksum = m.group( 2 );
+        }
+        else
+        {
+            // GNU tools
+            m = Pattern.compile( "([a-fA-F0-9]+)\\s+\\*?(.+)" ).matcher( trimmedChecksum );
+            if ( m.matches() )
+            {
+                String filename = m.group( 2 );
+                if ( !isValidChecksumPattern( filename, expectedPath ) )
+                {
+                    throw new IOException( "Supplied checksum does not match checksum pattern" );
+                }
+                trimmedChecksum = m.group( 1 );
+            }
+        }
+        return trimmedChecksum;
+    }
+}

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/ChecksumFile.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java (added)
+++ archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,96 @@
+package org.apache.archiva.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+
+import org.apache.commons.io.FilenameUtils;
+
+/**
+ * Enumeration of available Hash techniques.
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public enum Hash {
+    SHA1("SHA-1", "sha1", "SHA1"),
+    MD5("MD5", "md5", "MD5");
+
+    public static Hash getHashByExtension( File file )
+    {
+        String ext = FilenameUtils.getExtension( file.getName() ).toLowerCase();
+        if ( Hash.SHA1.getExt().equals( ext ) )
+        {
+            return Hash.SHA1;
+        }
+        else if ( Hash.MD5.getExt().equals( ext ) )
+        {
+            return Hash.MD5;
+        }
+
+        throw new IllegalArgumentException( "Filename " + file.getName() + " has no associated extension." );
+    }
+
+    /**
+     * The MessageDigest algorithm for this hash.
+     */
+    private String algorithm;
+
+    /**
+     * The file extension for this Hash.
+     */
+    private String ext;
+
+    /**
+     * The checksum type, the key that you see in checksum files.
+     */
+    private String type;
+
+    /**
+     * Construct a Hash
+     * 
+     * @param algorithm the MessageDigest algorithm
+     * @param ext the file extension.
+     * @param type the checksum type.
+     */
+    private Hash( String algorithm, String ext, String type )
+    {
+        this.algorithm = algorithm;
+        this.ext = ext;
+        this.type = type;
+    }
+
+    public String getAlgorithm()
+    {
+        return algorithm;
+    }
+
+    public String getExt()
+    {
+        return ext;
+    }
+
+    public String getType()
+    {
+        return type;
+    }
+    
+    
+}

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hash.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java (added)
+++ archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,104 @@
+package org.apache.archiva.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.NullOutputStream;
+
+/**
+ * Hasher - simple hashing routines. 
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class Hasher
+{
+    private static final int BUFFER_SIZE = 32768;
+
+    public static void update( List<Hasher> hashers, InputStream stream )
+        throws IOException
+    {
+        byte[] buffer = new byte[BUFFER_SIZE];
+        int size = stream.read( buffer, 0, BUFFER_SIZE );
+        while ( size >= 0 )
+        {
+            for ( Hasher hasher : hashers )
+            {
+                hasher.update( buffer, 0, size );
+            }
+            size = stream.read( buffer, 0, BUFFER_SIZE );
+        }
+    }
+
+    protected final MessageDigest md;
+
+    private Hash hash;
+
+    public Hasher( Hash hash )
+    {
+        this.hash = hash;
+        try
+        {
+            md = MessageDigest.getInstance( hash.getAlgorithm() );
+        }
+        catch ( NoSuchAlgorithmException e )
+        {
+            throw new IllegalStateException( "Unable to initialize MessageDigest algorithm " + hash.getAlgorithm()
+                + " : " + e.getMessage(), e );
+        }
+    }
+
+    public String getChecksum()
+    {
+        return Hex.encode( md.digest() );
+    }
+
+    public Hash getHash()
+    {
+        return this.hash;
+    }
+
+    public void reset()
+    {
+        md.reset();
+    }
+
+    public Hasher update( byte[] buffer, int offset, int size )
+    {
+        md.update( buffer, 0, size );
+        return this;
+    }
+
+    public Hasher update( InputStream stream )
+        throws IOException
+    {
+        DigestInputStream dig = new DigestInputStream( stream, md );
+        IOUtils.copy( dig, new NullOutputStream() );
+
+        return this;
+    }
+}

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hasher.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java (added)
+++ archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,32 @@
+package org.apache.archiva.checksum;
+
+/**
+ * Hex - simple hex conversions. 
+ *
+ * @version $Id$
+ */
+public class Hex
+{
+    private static final byte[] DIGITS = "0123456789abcdef".getBytes();
+
+    public static String encode( byte[] data )
+    {
+        int l = data.length;
+
+        byte[] raw = new byte[l * 2];
+
+        for ( int i = 0, j = 0; i < l; i++ )
+        {
+            raw[j++] = DIGITS[( 0xF0 & data[i] ) >>> 4];
+            raw[j++] = DIGITS[0x0F & data[i]];
+        }
+
+        return new String( raw );
+    }
+
+    public static String encode( String raw )
+    {
+        return encode( raw.getBytes() );
+    }
+
+}

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/main/java/org/apache/archiva/checksum/Hex.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java (added)
+++ archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,77 @@
+package org.apache.archiva.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+/**
+ * AbstractChecksumTestCase
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public abstract class AbstractChecksumTestCase
+    extends TestCase
+{
+    private File basedir;
+
+    public File getBasedir()
+    {
+        if ( basedir == null )
+        {
+            String sysprop = System.getProperty( "basedir" );
+            if ( sysprop != null )
+            {
+                basedir = new File( sysprop );
+            }
+            else
+            {
+                basedir = new File( System.getProperty( "user.dir" ) );
+            }
+        }
+        return basedir;
+    }
+
+    public File getTestOutputDir()
+    {
+        File dir = new File( getBasedir(), "target/test-output/" + getName() );
+        if ( dir.exists() == false )
+        {
+            if ( dir.mkdirs() == false )
+            {
+                fail( "Unable to create test output directory: " + dir.getAbsolutePath() );
+            }
+        }
+        return dir;
+    }
+
+    public File getTestResource( String filename )
+    {
+        File dir = new File( getBasedir(), "src/test/resources" );
+        File file = new File( dir, filename );
+        if ( file.exists() == false )
+        {
+            fail( "Test Resource does not exist: " + file.getAbsolutePath() );
+        }
+        return file;
+    }
+}

Propchange: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/AbstractChecksumTestCase.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java (added)
+++ archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,118 @@
+package org.apache.archiva.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * ChecksumFileTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class ChecksumFileTest
+    extends AbstractChecksumTestCase
+{
+    /**  SHA1 checksum from www.ibiblio.org/maven2, incuding file path */
+    private static final String SERVLETAPI_SHA1 = "bcc82975c0f9c681fcb01cc38504c992553e93ba";
+
+    public void testCalculateChecksumSha1()
+        throws IOException
+    {
+        ChecksumFile checksumFile = new ChecksumFile();
+        File testfile = getTestResource( "examples/redback-authz-open.jar" );
+        String expectedChecksum = "2bb14b388973351b0a4dfe11d171965f59cc61a1";
+        String actualChecksum = checksumFile.calculateChecksum( Hash.SHA1, testfile );
+        assertEquals( expectedChecksum, actualChecksum );
+    }
+
+    public void testCalculateChecksumMd5()
+        throws IOException
+    {
+        ChecksumFile checksumFile = new ChecksumFile();
+        File testfile = getTestResource( "examples/redback-authz-open.jar" );
+        String expectedChecksum = "f42047fe2e177ac04d0df7aa44d408be";
+        String actualChecksum = checksumFile.calculateChecksum( Hash.MD5, testfile );
+        assertEquals( expectedChecksum, actualChecksum );
+    }
+
+    public void testParseChecksum()
+        throws IOException
+    {
+        String expected = SERVLETAPI_SHA1
+            + "  /home/projects/maven/repository-staging/to-ibiblio/maven2/servletapi/servletapi/2.4/servletapi-2.4.pom";
+
+        ChecksumFile checksumFile = new ChecksumFile();
+        String s = checksumFile.parseChecksum( expected, Hash.SHA1, "servletapi/servletapi/2.4/servletapi-2.4.pom" );
+        assertEquals( "Checksum doesn't match", SERVLETAPI_SHA1, s );
+
+    }
+
+    public void testParseChecksumAltDash1()
+        throws IOException
+    {
+        String expected = SERVLETAPI_SHA1 + "  -";
+        ChecksumFile checksumFile = new ChecksumFile();
+        String s = checksumFile.parseChecksum( expected, Hash.SHA1, "servletapi/servletapi/2.4/servletapi-2.4.pom" );
+        assertEquals( "Checksum doesn't match", SERVLETAPI_SHA1, s );
+    }
+
+    public void testParseChecksumAltDash2()
+        throws IOException
+    {
+        String expected = "SHA1(-)=" + SERVLETAPI_SHA1;
+        ChecksumFile checksumFile = new ChecksumFile();
+        String s = checksumFile.parseChecksum( expected, Hash.SHA1, "servletapi/servletapi/2.4/servletapi-2.4.pom" );
+        assertEquals( "Checksum doesn't match", SERVLETAPI_SHA1, s );
+    }
+
+    public void testGetChecksumFile()
+    {
+        ChecksumFile checksumFile = new ChecksumFile();
+        assertEquals( "test.jar.sha1", checksumFile.getChecksumFile( Hash.SHA1, new File( "test.jar" ) ).getName() );
+    }
+
+    public void testCreateChecksum()
+        throws IOException
+    {
+        File testableJar = createTestableJar( "examples/redback-authz-open.jar" );
+        ChecksumFile checksumFile = new ChecksumFile();
+        checksumFile.createChecksum( testableJar, Hash.SHA1 );
+        File hashFile = checksumFile.getChecksumFile( Hash.SHA1, testableJar );
+        assertTrue( "Hash file should exist.", hashFile.exists() );
+        String hashContents = FileUtils.readFileToString( hashFile );
+        hashContents = StringUtils.trim( hashContents );
+        assertEquals( "2bb14b388973351b0a4dfe11d171965f59cc61a1  redback-authz-open.jar", hashContents );
+    }
+
+    private File createTestableJar( String filename )
+        throws IOException
+    {
+        File srcFile = getTestResource( filename );
+        File destFile = new File( getTestOutputDir(), srcFile.getName() );
+        FileUtils.copyFile( srcFile, destFile );
+        return destFile;
+    }
+
+}

Propchange: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/ChecksumFileTest.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java (added)
+++ archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java Sun Apr  6 21:08:30 2008
@@ -0,0 +1,59 @@
+package org.apache.archiva.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.File;
+
+import junit.framework.TestCase;
+
+/**
+ * HashTest
+ *
+ * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
+ * @version $Id$
+ */
+public class HashTest
+    extends TestCase
+{
+    public void testGetHashByExtensionSha1()
+    {
+        assertEquals( Hash.SHA1, Hash.getHashByExtension( new File( "something.jar.sha1" ) ) );
+        assertEquals( Hash.SHA1, Hash.getHashByExtension( new File( "OTHER.JAR.SHA1" ) ) );
+    }
+
+    public void testGetHashByExtensionMd5()
+    {
+        assertEquals( Hash.MD5, Hash.getHashByExtension( new File( "something.jar.md5" ) ) );
+        assertEquals( Hash.MD5, Hash.getHashByExtension( new File( "OTHER.JAR.MD5" ) ) );
+    }
+
+    public void testGetHashByExtensionInvalid()
+    {
+        try
+        {
+            Hash.getHashByExtension( new File( "something.jar" ) );
+            fail( "Expected " + IllegalArgumentException.class.getName() );
+        }
+        catch ( IllegalArgumentException e )
+        {
+            /* expected path */
+        }
+    }
+}

Propchange: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: archiva/sandbox/archiva-checksum/src/test/java/org/apache/archiva/checksum/HashTest.java
------------------------------------------------------------------------------
    svn:keywords = "Author Date Id Revision"

Added: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar?rev=645362&view=auto
==============================================================================
Binary file - no diff available.

Propchange: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar
------------------------------------------------------------------------------
    svn:mime-type = application/java-archive

Added: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.md5
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.md5?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.md5 (added)
+++ archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.md5 Sun Apr  6 21:08:30 2008
@@ -0,0 +1 @@
+f42047fe2e177ac04d0df7aa44d408be  redback-authz-open.jar

Added: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.sha1
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.sha1?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.sha1 (added)
+++ archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-authz-open.jar.sha1 Sun Apr  6 21:08:30 2008
@@ -0,0 +1 @@
+2bb14b388973351b0a4dfe11d171965f59cc61a1  redback-authz-open.jar

Added: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar?rev=645362&view=auto
==============================================================================
Binary file - no diff available.

Propchange: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar
------------------------------------------------------------------------------
    svn:mime-type = application/java-archive

Added: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.md5
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.md5?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.md5 (added)
+++ archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.md5 Sun Apr  6 21:08:30 2008
@@ -0,0 +1 @@
+MD5 ("redback-keys-api.jar") = 5628d13ea40c238a049d8f104811485c

Added: archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.sha1
URL: http://svn.apache.org/viewvc/archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.sha1?rev=645362&view=auto
==============================================================================
--- archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.sha1 (added)
+++ archiva/sandbox/archiva-checksum/src/test/resources/examples/redback-keys-api.jar.sha1 Sun Apr  6 21:08:30 2008
@@ -0,0 +1 @@
+SHA1 ("redback-keys-api.jar") = 266548fcba2c32b3999291e8303d58813c3794e3