From 9ded403f705ed3633efbf9ee7be6543ee89e0e7f Mon Sep 17 00:00:00 2001 From: Stefan Bodewig Date: Fri, 16 Nov 2001 12:30:58 +0000 Subject: [PATCH] checksum task and condition. I promise to add testcases later 8-) Submitted by: Magesh Umasankar git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@269929 13f79535-47bb-0310-9956-ffa450edef68 --- WHATSNEW | 4 +- docs/manual/CoreTasks/checksum.html | 151 +++++++ docs/manual/CoreTasks/condition.html | 7 + docs/manual/coretasklist.html | 1 + .../apache/tools/ant/taskdefs/Checksum.java | 416 ++++++++++++++++++ .../ant/taskdefs/condition/ConditionBase.java | 8 + .../tools/ant/taskdefs/defaults.properties | 1 + 7 files changed, 585 insertions(+), 3 deletions(-) create mode 100644 docs/manual/CoreTasks/checksum.html create mode 100644 src/main/org/apache/tools/ant/taskdefs/Checksum.java diff --git a/WHATSNEW b/WHATSNEW index d36192ca3..52f4cada7 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -41,9 +41,7 @@ Other changes: -------------- * New tasks bzip2 and bunzip2 to pack and unpack files using the - BZip2 alogrythm - -* New task replaceregexp + BZip2 alogrithm, replaceregexp, checksum * The attributes zipfile, jarfile, warfile and earfile (from the Zip, Jar, War and Ear tasks) have been deprecated and superseded by a diff --git a/docs/manual/CoreTasks/checksum.html b/docs/manual/CoreTasks/checksum.html new file mode 100644 index 000000000..4775c957c --- /dev/null +++ b/docs/manual/CoreTasks/checksum.html @@ -0,0 +1,151 @@ + + + + +Apache Ant User Manual + + + + +

Checksum

+

Description

+

+Generates checksum for files. This task can also be used to +perform checksum verifications. +

+

Parameters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AttributeDescriptionRequired
fileThe file to generate checksum for.One of either file or + at least one nested fileset element.
algorithmSpecifies the algorithm to be used to + compute the checksum. Defaults to "MD5". + Other popular algorithms like "SHA" may be used + as well. + No
providerSpecifies the provider of the algorithm.No
fileextThe generated checksum file's name will be the + original filename with "." and fileext added to it. + Defaults to the algorithm name being used. + No
propertySpecifies the name of the property to be set + with the generated checksum value. This cannot be specified + when fileext is being used or when the number of files + for which checksums is to be generated is greater than 1. + No
overwriteOverwrite existing files even if the destination + files are newer. Defaults to "no".No
verifypropertySpecifies the name of the property to be set + with "true" or "false" depending upon whether + the generated checksum matches the existing checksum. When + this is set, the generated checksum is not written to a file or + property, but rather, the content of the file or property is used to + check against the generated checksum. + No
+

Parameters specified as nested elements

+ +

fileset

+

+ FileSets are used to select files to + generate checksums for. +

+ +

Examples

+

Example 1

+
<checksum file="foo.bar"/>
+Generates a MD5 checksum for foo.bar and stores the checksum in the destination file +foo.bar.MD5. foo.bar.MD5 is overwritten only if foo.bar is newer than itself. + +

Example 2

+
<checksum file="foo.bar" forceOverwrite="yes"/>
+Generates a MD5 checksum for foo.bar and stores the checksum in foo.bar.MD5. +If foo.bar.MD5 already exists, it is overwritten. + +

Example 3

+
<checksum file="foo.bar" property="foobarMD5"/>
+Generates a MD5 checksum for foo.bar and stores it in the Project Property foobarMD5. + +

Example 4

+
<checksum file="foo.bar" verifyProperty="isMD5ok"/>
+Generates a MD5 checksum for foo.bar, compares it against foo.bar.MD5 and sets +isMD5ok to either true or false, depending upon the result. + +

Example 5

+
<checksum file="foo.bar" algorithm="SHA" fileext="asc"/>
+Generates a SHA checksum for foo.bar and stores the checksum in the destination file +foo.bar.asc. foo.bar.asc is overwritten only if foo.bar is newer than itself. + +

Example 6

+
+<checksum file="foo.bar" property="${md5}" verifyProperty="isEqual"/>
+
+Generates a MD5 checksum for foo.bar, compares it against the value of the property +md5, and sets isEqual to either true or false, depending upon the result. + +

Example 7

+
+<checksum>
+  <fileset dir=".">
+    <include name="foo*"/>
+  </fileset>
+</checksum>
+
+Works just like Example 1, but generates a .MD5 file for every file that begins with the name foo. + +

Example 8

+
+<condition property="isChecksumEqual">
+  <checksum>
+    <fileset dir=".">
+      <include name="foo.bar"/>
+    </fileset>
+  </checksum>
+</condition>
+
+Works like Example 4, but sets isChecksumEqual to either true or false. +This example demonstrates use with the Condition task. + + +

Note:

+When working with more than one file, if condition and/or verifyproperty is used, +the result will be true only if the checksums matched correctly for all files being +considered. + +

Copyright © 2000,2001 Apache Software Foundation. All rights +Reserved.

+ + + + diff --git a/docs/manual/CoreTasks/condition.html b/docs/manual/CoreTasks/condition.html index f7e728495..5cf0017c4 100644 --- a/docs/manual/CoreTasks/condition.html +++ b/docs/manual/CoreTasks/condition.html @@ -138,6 +138,13 @@ are redundant and will be ignored.

+

checksum

+ +

This condition is identical to the Checksum task, all attributes and nested +elements of that task are supported, the property and overwrite +attributes are redundant and will be ignored.

+

Examples

   <condition property="javamail.complete">
diff --git a/docs/manual/coretasklist.html b/docs/manual/coretasklist.html
index cb37a7e98..dfe2a3117 100644
--- a/docs/manual/coretasklist.html
+++ b/docs/manual/coretasklist.html
@@ -30,6 +30,7 @@
 Available
BUnzip2
BZip2
+Checksum
Chmod
Condition
Copy
diff --git a/src/main/org/apache/tools/ant/taskdefs/Checksum.java b/src/main/org/apache/tools/ant/taskdefs/Checksum.java new file mode 100644 index 000000000..aa4d0965f --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/Checksum.java @@ -0,0 +1,416 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Ant", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + */ +package org.apache.tools.ant.taskdefs; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Vector; +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.taskdefs.condition.Condition; +import org.apache.tools.ant.taskdefs.MatchingTask; +import org.apache.tools.ant.types.FileSet; + +/** + * This task can be used to create checksums for files. + * It can also be used to verify checksums. + * + * @author Magesh Umasankar + */ +public class Checksum extends MatchingTask implements Condition { + /** + * File for which checksum is to be calculated. + */ + private File file = null; + /** + * MessageDigest algorithm to be used. + */ + private String algorithm = "MD5"; + /** + * MessageDigest Algorithm provider + */ + private String provider = null; + /** + * File Extension that is be to used to create or identify + * destination file + */ + private String fileext; + /** + * Holds generated checksum and gets set as a Project Property. + */ + private String property; + /** + * Create new destination file? Defaults to false. + */ + private boolean forceOverwrite; + /** + * Contains the result of a checksum verification. ("true" or "false") + */ + private String verifyProperty; + /** + * Vector to hold source file sets. + */ + private Vector filesets = new Vector(); + /** + * Stores SourceFile, DestFile pairs and SourceFile, Property String pairs. + */ + private Hashtable includeFileMap = new Hashtable(); + /** + * Message Digest instance + */ + private MessageDigest messageDigest; + /** + * is this task being used as a nested condition element? + */ + private boolean isCondition; + + /** + * Sets the file for which the checksum is to be calculated. + */ + public void setFile(File file) { + this.file = file; + } + + /** + * Sets the MessageDigest algorithm to be used + * to calculate the checksum. + */ + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + /** + * Sets the MessageDigest algorithm provider to be used + * to calculate the checksum. + */ + public void setProvider(String provider) { + this.provider = provider; + } + + /** + * Sets the File Extension that is be to used to + * create or identify destination file + */ + public void setFileext(String fileext) { + this.fileext = fileext; + } + + /** + * Sets the property to hold the generated checksum + */ + public void setProperty(String property) { + this.property = property; + } + + /** + * Sets verify property. This project property holds + * the result of a checksum verification - "true" or "false" + */ + public void setVerifyproperty(String verifyProperty) { + this.verifyProperty = verifyProperty; + } + + /** + * Overwrite existing file irrespective of whether it is newer than + * the source file? Defaults to false. + */ + public void setForceOverwrite(boolean forceOverwrite) { + this.forceOverwrite = forceOverwrite; + } + + /** + * Adds a set of files (nested fileset attribute). + */ + public void addFileset(FileSet set) { + filesets.addElement(set); + } + + /** + * Calculate the checksum(s). + */ + public void execute() throws BuildException { + boolean value = validateAndExecute(); + if (verifyProperty != null) { + project.setProperty(verifyProperty, + new Boolean(value).toString()); + } + } + + /** + * Calculate the checksum(s) + * + * @return Returns true if the checksum verification test passed, + * false otherwise. + */ + public boolean eval() throws BuildException { + isCondition = true; + return validateAndExecute(); + } + + /** + * Validate attributes and get down to business. + */ + private boolean validateAndExecute() throws BuildException { + + if (file == null && filesets.size() == 0) { + throw new BuildException( + "Specify at least one source - a file or a fileset."); + } + + if (file != null && file.exists() && file.isDirectory()) { + throw new BuildException( + "Checksum cannot be generated for directories"); + } + + if (property != null && fileext != null) { + throw new BuildException( + "Property and FileExt cannot co-exist."); + } + + if (property != null) { + if (forceOverwrite) { + throw new BuildException( + "ForceOverwrite cannot be used when Property is specified"); + } + + if (file != null) { + if (filesets.size() > 0) { + throw new BuildException( + "Multiple files cannot be used when Property is specified"); + } + } else { + if (filesets.size() > 1) { + throw new BuildException( + "Multiple files cannot be used when Property is specified"); + } + } + } + + if (verifyProperty != null) { + isCondition = true; + } + + if (verifyProperty != null && forceOverwrite) { + throw new BuildException( + "VerifyProperty and ForceOverwrite cannot co-exist."); + } + + if (isCondition && forceOverwrite) { + throw new BuildException( + "ForceOverwrite cannot be used when conditions are being used."); + } + + if (fileext == null) { + fileext = "." + algorithm; + } else if (fileext.trim().length() == 0) { + throw new BuildException( + "File extension when specified must not be an empty string"); + } + + messageDigest = null; + if (provider != null) { + try { + messageDigest = MessageDigest.getInstance(algorithm, provider); + } catch (NoSuchAlgorithmException noalgo) { + throw new BuildException(noalgo, location); + } catch (NoSuchProviderException noprovider) { + throw new BuildException(noprovider, location); + } + } else { + try { + messageDigest = MessageDigest.getInstance(algorithm); + } catch (NoSuchAlgorithmException noalgo) { + throw new BuildException(noalgo, location); + } + } + + if (messageDigest == null) { + throw new BuildException("Unable to create Message Digest", + location); + } + + addToIncludeFileMap(file); + + int sizeofFileSet = filesets.size(); + for (int i = 0; i < sizeofFileSet; i++) { + FileSet fs = (FileSet) filesets.elementAt(i); + DirectoryScanner ds = fs.getDirectoryScanner(project); + String[] srcFiles = ds.getIncludedFiles(); + for (int j = 0; j < srcFiles.length; j++) { + File src = new File(srcFiles[j]); + addToIncludeFileMap(src); + } + } + + return generateChecksums(); + } + + /** + * Add key-value pair to the hashtable upon which + * to later operate upon. + */ + private void addToIncludeFileMap(File file) throws BuildException { + if (file != null) { + if (file.exists()) { + if (property == null) { + File dest = new File(file.getParent(), file.getName() + fileext); + if (forceOverwrite || isCondition || + (file.lastModified() > dest.lastModified())) { + includeFileMap.put(file, dest); + } else { + log(file + " omitted as " + dest + " is up to date.", + Project.MSG_VERBOSE); + } + } else { + includeFileMap.put(file, property); + } + } else { + String message = "Could not find file " + + file.getAbsolutePath() + + " to generate checksum for."; + log(message); + throw new BuildException(message, location); + } + } + } + + /** + * Generate checksum(s) using the message digest created earlier. + */ + private boolean generateChecksums() throws BuildException { + boolean checksumMatches = true; + FileInputStream fis = null; + FileOutputStream fos = null; + try { + for (Enumeration e = includeFileMap.keys(); e.hasMoreElements();) { + messageDigest.reset(); + File src = (File) e.nextElement(); + fis = new FileInputStream(src); + DigestInputStream dis = new DigestInputStream(fis, + messageDigest); + while (dis.read() != -1); + dis.close(); + fis.close(); + fis = null; + byte[] fileDigest = messageDigest.digest (); + String checksum = ""; + for (int i = 0; i < fileDigest.length; i++) { + String hexStr = Integer.toHexString(0x00ff & fileDigest[i]); + if (hexStr.length() < 2) { + checksum += "0"; + } + checksum += hexStr; + } + //can either be a property name string or a file + Object destination = includeFileMap.get(src); + if (destination instanceof java.lang.String) { + String prop = (String) destination; + if (isCondition) { + checksumMatches = checksum.equals(property); + } else { + project.setProperty(prop, checksum); + } + } else if (destination instanceof java.io.File) { + if (isCondition) { + File existingFile = (File) destination; + if (existingFile.exists() && + existingFile.length() == checksum.length()) { + fis = new FileInputStream(existingFile); + DataInputStream edis = new DataInputStream(fis); + String suppliedChecksum = ""; + if (edis.available() > 0) { + suppliedChecksum = edis.readLine(); + } + fis.close(); + fis = null; + edis.close(); + checksumMatches = + checksum.equals(suppliedChecksum); + } else { + checksumMatches = false; + } + } else { + File dest = (File) destination; + fos = new FileOutputStream(dest); + fos.write(checksum.getBytes()); + } + } + } + } catch (Exception e) { + throw new BuildException(e, location); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) {} + } + if (fos != null) { + try { + fos.close(); + } catch (IOException e) {} + } + } + return checksumMatches; + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java b/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java index 6ccd1df50..1cb1edf9d 100644 --- a/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java +++ b/src/main/org/apache/tools/ant/taskdefs/condition/ConditionBase.java @@ -61,6 +61,7 @@ import java.util.Vector; import org.apache.tools.ant.ProjectComponent; import org.apache.tools.ant.taskdefs.Available; +import org.apache.tools.ant.taskdefs.Checksum; import org.apache.tools.ant.taskdefs.UpToDate; /** @@ -97,6 +98,13 @@ public abstract class ConditionBase extends ProjectComponent { */ public void addAvailable(Available a) {conditions.addElement(a);} + /** + * Add an <checksum> condition. + * + * @since 1.4 + */ + public void addAvailable(Checksum c) {conditions.addElement(c);} + /** * Add an <uptodate> condition. * diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index 1649b45a7..1617d9ea6 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -55,6 +55,7 @@ condition=org.apache.tools.ant.taskdefs.ConditionTask dependset=org.apache.tools.ant.taskdefs.DependSet bzip2=org.apache.tools.ant.taskdefs.BZip2 bunzip2=org.apache.tools.ant.taskdefs.BUnzip2 +checksum=org.apache.tools.ant.taskdefs.Checksum # optional tasks script=org.apache.tools.ant.taskdefs.optional.Script