/*
 *  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.
 *
 */

package org.apache.tools.ant.taskdefs;

import org.apache.tools.ant.Project;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.filters.ChainableReader;
import org.apache.tools.ant.types.RedirectorElement;
import org.apache.tools.ant.types.FilterChain;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.resources.FileResource;

import java.util.Iterator;
import java.io.File;
import java.io.Reader;
import java.io.IOException;

/**
 * JAR verification task.
 * For every JAR passed in, we fork jarsigner to verify
 * that it is correctly signed. This is more rigorous than just checking for
 * the existence of a signature; the entire certification chain is tested
 * @since Ant 1.7
 */

public class VerifyJar extends AbstractJarSignerTask {
    /**
     * no file message {@value}
     */
    public static final String ERROR_NO_FILE = "Not found :";

    /**
     * The string we look for in the text to indicate direct verification
     */
    private static final String VERIFIED_TEXT = "jar verified.";

    /**
     * certification flag
     */
    private boolean certificates=false;
    private BufferingOutputFilter outputCache = new BufferingOutputFilter();
    public static final String ERROR_NO_VERIFY = "Failed to verify ";

    /**
     * Ask for certificate information to be printed
     * @param certificates
     */
    public void setCertificates(boolean certificates) {
        this.certificates = certificates;
    }

    /**
     * verify our jar files
     * @throws BuildException
     */
    public void execute() throws BuildException {
        //validation logic
        final boolean hasJar = jar != null;

        if (!hasJar && !hasResources()) {
            throw new BuildException(ERROR_NO_SOURCE);
        }

        beginExecution();

        //patch the redirector to save output to a file
        RedirectorElement redirector = getRedirector();
        redirector.setAlwaysLog(true);
        FilterChain outputFilterChain = redirector.createOutputFilterChain();
        outputFilterChain.add(outputCache);

        try {
            Path sources = createUnifiedSourcePath();
            Iterator iter = sources.iterator();
            while (iter.hasNext()) {
                FileResource fr = (FileResource) iter.next();
                verifyOneJar(fr.getFile());
            }

        } finally {
            endExecution();
        }

    }

    /**
     * verify a JAR.
     * @param jar
     * @throws BuildException if the file could not be verified
     */
    private void verifyOneJar(File jar) {
        if(!jar.exists()) {
            throw new BuildException(ERROR_NO_FILE+jar);
        }
        final ExecTask cmd = createJarSigner();

        setCommonOptions(cmd);
        bindToKeystore(cmd);

        //verify special operations
        addValue(cmd, "-verify");

        if(certificates) {
            addValue(cmd, "-certs");
        }

        //JAR  is required
        addValue(cmd, jar.getPath());

        log("Verifying JAR: " +
                jar.getAbsolutePath());
        outputCache.clear();
        BuildException ex = null;
        try {
            cmd.execute();
        } catch (BuildException e) {
            ex = e;
        }
        String results = outputCache.toString();
        //deal with jdk1.4.2 bug:
        if (ex != null) {
            if (results.indexOf("zip file closed") >= 0) {
                log("You are running " + JARSIGNER_COMMAND + " against a JVM with"
                    + " a known bug that manifests as an IllegalStateException.",
                    Project.MSG_WARN);
            } else {
                throw ex;
            }
        }
        if (results.indexOf(VERIFIED_TEXT) < 0) {
            throw new BuildException(ERROR_NO_VERIFY + jar);
        }
    }

    /**
     * we are not thread safe here. Do not use on multiple threads at the same time.
     */
    private static class BufferingOutputFilter implements ChainableReader {

        private BufferingOutputFilterReader buffer;

        public Reader chain(Reader rdr) {
            buffer = new BufferingOutputFilterReader(rdr);
            return buffer;
        }

        public String toString() {
            return buffer.toString();
        }

        public void clear() {
            if(buffer!=null) {
                buffer.clear();
            }
        }
    }

    /**
     * catch the output of the buffer
     */
    private static class BufferingOutputFilterReader extends Reader {

        private Reader next;

        private StringBuffer buffer=new StringBuffer();

        public BufferingOutputFilterReader(Reader next) {
            this.next = next;
        }

        public int read(char cbuf[], int off, int len) throws IOException {
            //hand down
            int result=next.read(cbuf,off,len);
            //cache
            buffer.append(cbuf,off,len);
            //return
            return result;
        }

        public void close() throws IOException {
            next.close();
        }

        public String toString() {
            return buffer.toString();
        }

        public void clear() {
            buffer=new StringBuffer();
        }
    }
}
