001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.enforcer.rules.checksum;
020
021import javax.inject.Named;
022
023import java.io.File;
024import java.io.IOException;
025import java.io.InputStream;
026import java.nio.file.Files;
027
028import org.apache.commons.codec.digest.DigestUtils;
029import org.apache.maven.enforcer.rule.api.EnforcerRuleError;
030import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
031import org.apache.maven.enforcer.rules.AbstractStandardEnforcerRule;
032
033/**
034 * Rule to validate a binary file to match the specified checksum.
035 *
036 * @author Edward Samson
037 * @author Lyubomyr Shaydariv
038 * @see RequireTextFileChecksum
039 */
040@Named("requireFileChecksum")
041public class RequireFileChecksum extends AbstractStandardEnforcerRule {
042
043    private File file;
044
045    private String checksum;
046
047    private String type;
048
049    private String nonexistentFileMessage;
050
051    @Override
052    public void execute() throws EnforcerRuleException {
053        if (this.file == null) {
054            throw new EnforcerRuleError("Input file unspecified");
055        }
056
057        if (this.type == null) {
058            throw new EnforcerRuleError("Hash type unspecified");
059        }
060
061        if (this.checksum == null) {
062            throw new EnforcerRuleError("Checksum unspecified");
063        }
064
065        if (!this.file.exists()) {
066            String message = nonexistentFileMessage;
067            if (message == null) {
068                message = "File does not exist: " + this.file.getAbsolutePath();
069            }
070            throw new EnforcerRuleException(message);
071        }
072
073        if (this.file.isDirectory()) {
074            throw new EnforcerRuleError("Cannot calculate the checksum of directory: " + this.file.getAbsolutePath());
075        }
076
077        if (!this.file.canRead()) {
078            throw new EnforcerRuleError("Cannot read file: " + this.file.getAbsolutePath());
079        }
080
081        String checksum = calculateChecksum();
082
083        if (!checksum.equalsIgnoreCase(this.checksum)) {
084            String exceptionMessage = getMessage();
085            if (exceptionMessage == null) {
086                exceptionMessage =
087                        this.type + " hash of " + this.file + " was " + checksum + " but expected " + this.checksum;
088            }
089            throw new EnforcerRuleException(exceptionMessage);
090        }
091    }
092
093    /**
094     * The file to check.
095     *
096     * @param file file
097     */
098    public void setFile(File file) {
099        this.file = file;
100    }
101
102    public File getFile() {
103        return file;
104    }
105
106    /**
107     * The expected checksum value.
108     *
109     * @param checksum checksum
110     */
111    public void setChecksum(String checksum) {
112        this.checksum = checksum;
113    }
114
115    public String getChecksum() {
116        return checksum;
117    }
118
119    /**
120     * The checksum algorithm to use. Possible values: "md5", "sha1", "sha256", "sha384", "sha512".
121     *
122     * @param type algorithm
123     */
124    public void setType(String type) {
125        this.type = type;
126    }
127
128    public String getType() {
129        return type;
130    }
131
132    /**
133     * The friendly message to use when the file does not exist.
134     *
135     * @param nonexistentFileMessage message
136     */
137    public void setNonexistentFileMessage(String nonexistentFileMessage) {
138        this.nonexistentFileMessage = nonexistentFileMessage;
139    }
140
141    public String getNonexistentFileMessage() {
142        return nonexistentFileMessage;
143    }
144
145    protected String calculateChecksum() throws EnforcerRuleException {
146        try (InputStream inputStream = Files.newInputStream(this.file.toPath())) {
147            return calculateChecksum(inputStream);
148        } catch (IOException e) {
149            throw new EnforcerRuleError("Unable to calculate checksum", e);
150        }
151    }
152
153    protected String calculateChecksum(InputStream inputStream) throws IOException, EnforcerRuleException {
154        String result;
155        if ("md5".equals(this.type)) {
156            result = DigestUtils.md5Hex(inputStream);
157        } else if ("sha1".equals(this.type)) {
158            result = DigestUtils.sha1Hex(inputStream);
159        } else if ("sha256".equals(this.type)) {
160            result = DigestUtils.sha256Hex(inputStream);
161        } else if ("sha384".equals(this.type)) {
162            result = DigestUtils.sha384Hex(inputStream);
163        } else if ("sha512".equals(this.type)) {
164            result = DigestUtils.sha512Hex(inputStream);
165        } else {
166            throw new EnforcerRuleError("Unsupported hash type: " + this.type);
167        }
168        return result;
169    }
170
171    @Override
172    public String toString() {
173        return String.format(
174                "RequireFileChecksum[message=%s, file=%s, checksum=%s, type=%s, nonexistentFileMessage=%s]",
175                getMessage(), file, checksum, type, nonexistentFileMessage);
176    }
177}