content: /*
   * 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 android.framework.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.List;
  import java.util.zip.ZipEntry;
  import java.util.zip.ZipFile;
  
  import android.framework.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 it is read.
      private ZipEntry manifestEntry;
  
      JarVerifier verifier;
  
      private boolean closed = false;
  
      static final class JarFileInputStream extends FilterInputStream {
          private long count;
  
          private ZipEntry zipEntry;
  
          private JarVerifier.VerifierEntry entry;
  
          private boolean done = false;
  
          JarFileInputStream(InputStream is, ZipEntry ze,
                  JarVerifier.VerifierEntry e) {
              super(is);
              zipEntry = ze;
              count = zipEntry.getSize();
              entry = e;
          }
  
          @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[] buf, int off, int nbytes) throws IOException {
              if (done) {
                  return -1;
              }
              if (count > 0) {
                  int r = super.read(buf, off, nbytes);
                  if (r != -1) {
                      int size = r;
                      if (count < size) {
                          size = (int) count;
                      }
                      entry.write(buf, off, 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);
          }
      }
  
      /**
       * 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 {
          super(file);
          if (verify) {
              verifier = new JarVerifier(file.getPath());
          }
          readMetaEntries();
      }
  
      /**
       * 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 {
          this(file, verify, mode, false);
      }
  
      /**
       * See previous constructor for other parameter definitions.
       * @param chainCheck
       *            whether or not to check certificate chain signatures
       * @hide
       */
      public JarFile(File file, boolean verify, int mode, boolean chainCheck) throws IOException {
          super(file, mode);
          if (verify) {
              verifier = new JarVerifier(file.getPath(), chainCheck);
          }
          readMetaEntries();
      }
  
      /**
       * 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(filename, verify, false);
      }
  
      /**
       * See previous constructor for other parameter definitions.
       * @param chainCheck
       *            whether or not to check certificate chain signatures
       * @hide
       */
      public JarFile(String filename, boolean verify, boolean chainCheck) throws IOException {
          super(filename);
          if (verify) {
              verifier = new JarVerifier(filename, chainCheck);
          }
          readMetaEntries();
      }
  
      /**
       * 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<JarEntry> entries() {
          class JarFileEnumerator implements Enumeration<JarEntry> {
              Enumeration<? extends ZipEntry> ze;
  
              JarFile jf;
  
              JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) {
                  ze = zenum;
                  this.jf = jf;
              }
  
              public boolean hasMoreElements() {
                  return ze.hasMoreElements();
              }
  
              public JarEntry nextElement() {
                  JarEntry je = new JarEntry(ze.nextElement());
                  je.parentJar = jf;
                  return je;
              }
          }
          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;
          }
          try {
              InputStream is = super.getInputStream(manifestEntry);
              if (verifier != null) {
                  verifier.addMetaEntry(manifestEntry.getName(), Streams.readFully(is));
                  is = super.getInputStream(manifestEntry);
              }
              try {
                  manifest = new Manifest(is, verifier != null);
              } finally {
                  is.close();
              }
              manifestEntry = null;  // Can discard the entry now.
          } catch (NullPointerException e) {
              manifestEntry = null;
          }
          return manifest;
      }
  
      /**
       * Called by the JarFile constructors, this method reads the contents of the
       * file's META-INF/ directory and picks out the MANIFEST.MF file and
       * verifier signature files if they exist. Any signature files found are
       * registered with the verifier.
       *
       * @throws IOException
       *             if there is a problem reading the jar file entries.
       */
      private void readMetaEntries() throws IOException {
          // Get all meta directory entries
          ZipEntry[] metaEntries = getMetaEntriesImpl();
          if (metaEntries == null) {
              verifier = null;
              return;
          }
  
          boolean signed = false;
  
          for (ZipEntry entry : metaEntries) {
              String entryName = entry.getName();
              // Is this the entry for META-INF/MANIFEST.MF ?
              if (manifestEntry == null && entryName.equalsIgnoreCase(MANIFEST_NAME)) {
                  manifestEntry = entry;
                  // If there is no verifier then we don't need to look any further.
                  if (verifier == null) {
                      break;
                  }
              } else {
                  // Is this an entry that the verifier needs?
                  if (verifier != null
                          && (endsWithIgnoreCase(entryName, ".SF")
                                  || endsWithIgnoreCase(entryName, ".DSA")
                                  || endsWithIgnoreCase(entryName, ".RSA"))) {
                      signed = true;
                      InputStream is = super.getInputStream(entry);
                      verifier.addMetaEntry(entryName, Streams.readFully(is));
                  }
              }
          }
  
          // If there were no signature files, then no verifier work to do.
          if (!signed) {
              verifier = null;
          }
      }
  
      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 (manifestEntry != null) {
              getManifest();
          }
          if (verifier != null) {
              verifier.setManifest(getManifest());
              if (manifest != null) {
                  verifier.mainAttributesEnd = manifest.getMainAttributesEnd();
              }
              if (verifier.readCertificates()) {
                  verifier.removeMetaEntries();
                  if (manifest != null) {
                      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, 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;
          }
          JarEntry je = new JarEntry(ze);
          je.parentJar = this;
          return je;
      }
  
      /**
       * Returns all the ZipEntry's that relate to files in the
       * JAR's META-INF directory.
       *
       * @return the list of ZipEntry's or {@code null} if there are none.
       */
      private ZipEntry[] getMetaEntriesImpl() {
          List<ZipEntry> list = new ArrayList<ZipEntry>(8);
          Enumeration<? extends ZipEntry> allEntries = entries();
          while (allEntries.hasMoreElements()) {
              ZipEntry ze = allEntries.nextElement();
              if (ze.getName().startsWith(META_DIR)
                      && ze.getName().length() > META_DIR.length()) {
                  list.add(ze);
              }
          }
          if (list.size() == 0) {
              return null;
          }
          ZipEntry[] result = new ZipEntry[list.size()];
          list.toArray(result);
          return result;
      }
  
      /**
       * Closes this {@code JarFile}.
       *
       * @throws IOException
       *             if an error occurs.
       */
      @Override
      public void close() throws IOException {
          super.close();
          closed = true;
      }
  }
  