All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.util.jar.JarFile Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 14-robolectric-10818077
Show newest version
/*
 * 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 java.util.jar;

import java.io.File;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import libcore.io.Streams;

/**
 * {@code JarFile} is used to read jar entries and their associated data from
 * jar files.
 *
 * @see JarInputStream
 * @see JarEntry
 */
public class JarFile extends ZipFile {

    /**
     * The MANIFEST file name.
     */
    public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF";

    // The directory containing the manifest.
    static final String META_DIR = "META-INF/";

    // The manifest after it has been read from the JAR.
    private Manifest manifest;

    // The entry for the MANIFEST.MF file before the first call to getManifest().
    private byte[] manifestBytes;

    JarVerifier verifier;

    private boolean closed = false;

    static final class JarFileInputStream extends FilterInputStream {
        private final JarVerifier.VerifierEntry entry;

        private long count;
        private boolean done = false;

        JarFileInputStream(InputStream is, long size, JarVerifier.VerifierEntry e) {
            super(is);
            entry = e;

            count = size;
        }

        @Override
        public int read() throws IOException {
            if (done) {
                return -1;
            }
            if (count > 0) {
                int r = super.read();
                if (r != -1) {
                    entry.write(r);
                    count--;
                } else {
                    count = 0;
                }
                if (count == 0) {
                    done = true;
                    entry.verify();
                }
                return r;
            } else {
                done = true;
                entry.verify();
                return -1;
            }
        }

        @Override
        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
            if (done) {
                return -1;
            }
            if (count > 0) {
                int r = super.read(buffer, byteOffset, byteCount);
                if (r != -1) {
                    int size = r;
                    if (count < size) {
                        size = (int) count;
                    }
                    entry.write(buffer, byteOffset, size);
                    count -= size;
                } else {
                    count = 0;
                }
                if (count == 0) {
                    done = true;
                    entry.verify();
                }
                return r;
            } else {
                done = true;
                entry.verify();
                return -1;
            }
        }

        @Override
        public int available() throws IOException {
            if (done) {
                return 0;
            }
            return super.available();
        }

        @Override
        public long skip(long byteCount) throws IOException {
            return Streams.skipByReading(this, byteCount);
        }
    }

    static final class JarFileEnumerator implements Enumeration {
        final Enumeration ze;
        final JarFile jf;

        JarFileEnumerator(Enumeration zenum, JarFile jf) {
            ze = zenum;
            this.jf = jf;
        }

        public boolean hasMoreElements() {
            return ze.hasMoreElements();
        }

        public JarEntry nextElement() {
            return new JarEntry(ze.nextElement(), jf /* parentJar */);
        }
    }

    /**
     * Create a new {@code JarFile} using the contents of the specified file.
     *
     * @param file
     *            the JAR file as {@link File}.
     * @throws IOException
     *             If the file cannot be read.
     */
    public JarFile(File file) throws IOException {
        this(file, true);
    }

    /**
     * Create a new {@code JarFile} using the contents of the specified file.
     *
     * @param file
     *            the JAR file as {@link File}.
     * @param verify
     *            if this JAR file is signed whether it must be verified.
     * @throws IOException
     *             If the file cannot be read.
     */
    public JarFile(File file, boolean verify) throws IOException {
        this(file, verify, ZipFile.OPEN_READ);
    }

    /**
     * Create a new {@code JarFile} using the contents of file.
     *
     * @param file
     *            the JAR file as {@link File}.
     * @param verify
     *            if this JAR filed is signed whether it must be verified.
     * @param mode
     *            the mode to use, either {@link ZipFile#OPEN_READ OPEN_READ} or
     *            {@link ZipFile#OPEN_DELETE OPEN_DELETE}.
     * @throws IOException
     *             If the file cannot be read.
     */
    public JarFile(File file, boolean verify, int mode) throws IOException {
        super(file, mode);

        // Step 1: Scan the central directory for meta entries (MANIFEST.mf
        // & possibly the signature files) and read them fully.
        HashMap metaEntries = readMetaEntries(this, verify);

        // Step 2: Construct a verifier with the information we have.
        // Verification is possible *only* if the JAR file contains a manifest
        // *AND* it contains signing related information (signature block
        // files and the signature files).
        //
        // TODO: Is this really the behaviour we want if verify == true ?
        // We silently skip verification for files that have no manifest or
        // no signatures.
        if (verify && metaEntries.containsKey(MANIFEST_NAME) &&
                metaEntries.size() > 1) {
            // We create the manifest straight away, so that we can create
            // the jar verifier as well.
            manifest = new Manifest(metaEntries.get(MANIFEST_NAME), true);
            verifier = new JarVerifier(getName(), manifest, metaEntries);
        } else {
            verifier = null;
            manifestBytes = metaEntries.get(MANIFEST_NAME);
        }
    }

    /**
     * Create a new {@code JarFile} from the contents of the file specified by
     * filename.
     *
     * @param filename
     *            the file name referring to the JAR file.
     * @throws IOException
     *             if file name cannot be opened for reading.
     */
    public JarFile(String filename) throws IOException {
        this(filename, true);
    }

    /**
     * Create a new {@code JarFile} from the contents of the file specified by
     * {@code filename}.
     *
     * @param filename
     *            the file name referring to the JAR file.
     * @param verify
     *            if this JAR filed is signed whether it must be verified.
     * @throws IOException
     *             If file cannot be opened or read.
     */
    public JarFile(String filename, boolean verify) throws IOException {
        this(new File(filename), verify, ZipFile.OPEN_READ);
    }

    /**
     * Return an enumeration containing the {@code JarEntrys} contained in this
     * {@code JarFile}.
     *
     * @return the {@code Enumeration} containing the JAR entries.
     * @throws IllegalStateException
     *             if this {@code JarFile} is closed.
     */
    @Override
    public Enumeration entries() {
        return new JarFileEnumerator(super.entries(), this);
    }

    /**
     * Return the {@code JarEntry} specified by its name or {@code null} if no
     * such entry exists.
     *
     * @param name
     *            the name of the entry in the JAR file.
     * @return the JAR entry defined by the name.
     */
    public JarEntry getJarEntry(String name) {
        return (JarEntry) getEntry(name);
    }

    /**
     * Returns the {@code Manifest} object associated with this {@code JarFile}
     * or {@code null} if no MANIFEST entry exists.
     *
     * @return the MANIFEST.
     * @throws IOException
     *             if an error occurs reading the MANIFEST file.
     * @throws IllegalStateException
     *             if the jar file is closed.
     * @see Manifest
     */
    public Manifest getManifest() throws IOException {
        if (closed) {
            throw new IllegalStateException("JarFile has been closed");
        }

        if (manifest != null) {
            return manifest;
        }

        // If manifest == null && manifestBytes == null, there's no manifest.
        if (manifestBytes == null) {
            return null;
        }

        // We hit this code path only if the verification isn't necessary. If
        // we did decide to verify this file, we'd have created the Manifest and
        // the associated Verifier in the constructor itself.
        manifest = new Manifest(manifestBytes, false);
        manifestBytes = null;

        return manifest;
    }

    /**
     * Called by the JarFile constructors, Reads the contents of the
     * file's META-INF/ directory and picks out the MANIFEST.MF file and
     * verifier signature files if they exist.
     *
     * @throws IOException
     *             if there is a problem reading the jar file entries.
     * @return a map of entry names to their {@code byte[]} content.
     */
    static HashMap readMetaEntries(ZipFile zipFile,
            boolean verificationRequired) throws IOException {
        // Get all meta directory entries
        List metaEntries = getMetaEntries(zipFile);

        HashMap metaEntriesMap = new HashMap();

        for (ZipEntry entry : metaEntries) {
            String entryName = entry.getName();
            // Is this the entry for META-INF/MANIFEST.MF ?
            //
            // TODO: Why do we need the containsKey check ? Shouldn't we discard
            // files that contain duplicate entries like this as invalid ?.
            if (entryName.equalsIgnoreCase(MANIFEST_NAME) &&
                    !metaEntriesMap.containsKey(MANIFEST_NAME)) {

                metaEntriesMap.put(MANIFEST_NAME, Streams.readFully(
                        zipFile.getInputStream(entry)));

                // If there is no verifier then we don't need to look any further.
                if (!verificationRequired) {
                    break;
                }
            } else if (verificationRequired) {
                // Is this an entry that the verifier needs?
                if (endsWithIgnoreCase(entryName, ".SF")
                        || endsWithIgnoreCase(entryName, ".DSA")
                        || endsWithIgnoreCase(entryName, ".RSA")
                        || endsWithIgnoreCase(entryName, ".EC")) {
                    InputStream is = zipFile.getInputStream(entry);
                    metaEntriesMap.put(entryName.toUpperCase(Locale.US), Streams.readFully(is));
                }
            }
        }

        return metaEntriesMap;
    }

    private static boolean endsWithIgnoreCase(String s, String suffix) {
        return s.regionMatches(true, s.length() - suffix.length(), suffix, 0, suffix.length());
    }

    /**
     * Return an {@code InputStream} for reading the decompressed contents of
     * ZIP entry.
     *
     * @param ze
     *            the ZIP entry to be read.
     * @return the input stream to read from.
     * @throws IOException
     *             if an error occurred while creating the input stream.
     */
    @Override
    public InputStream getInputStream(ZipEntry ze) throws IOException {
        if (manifestBytes != null) {
            getManifest();
        }

        if (verifier != null) {
            if (verifier.readCertificates()) {
                verifier.removeMetaEntries();
                manifest.removeChunks();

                if (!verifier.isSignedJar()) {
                    verifier = null;
                }
            }
        }

        InputStream in = super.getInputStream(ze);
        if (in == null) {
            return null;
        }
        if (verifier == null || ze.getSize() == -1) {
            return in;
        }
        JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
        if (entry == null) {
            return in;
        }
        return new JarFileInputStream(in, ze.getSize(), entry);
    }

    /**
     * Return the {@code JarEntry} specified by name or {@code null} if no such
     * entry exists.
     *
     * @param name
     *            the name of the entry in the JAR file.
     * @return the ZIP entry extracted.
     */
    @Override
    public ZipEntry getEntry(String name) {
        ZipEntry ze = super.getEntry(name);
        if (ze == null) {
            return ze;
        }
        return new JarEntry(ze, this /* parentJar */);
    }

    /**
     * Returns all the ZipEntry's that relate to files in the
     * JAR's META-INF directory.
     */
    private static List getMetaEntries(ZipFile zipFile) {
        List list = new ArrayList(8);

        Enumeration allEntries = zipFile.entries();
        while (allEntries.hasMoreElements()) {
            ZipEntry ze = allEntries.nextElement();
            if (ze.getName().startsWith(META_DIR)
                    && ze.getName().length() > META_DIR.length()) {
                list.add(ze);
            }
        }

        return list;
    }

    /**
     * Closes this {@code JarFile}.
     *
     * @throws IOException
     *             if an error occurs.
     */
    @Override
    public void close() throws IOException {
        super.close();
        closed = true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy