Add a JNI wrapper over libziparchive.

All functions delegate directly to the native code. We
do not perform any sort of central directory or local file
related lookups in the java layer.

For each entry, libziparchive gives us an offset to the start
of data for the entry and the compressed size. We use a
RandomAccessFile to seek to that offset and then construct an
InputStream that's bounded between [offset, offset + compressed_size).

We reuse existing code (JarVerifier / Manifest) for certificate
validation & collection.

bug: 10193060

Change-Id: I21eb4e6dc6aa8749e3f63830f9799e1f621d26e6
diff --git a/NativeCode.mk b/NativeCode.mk
index f8312ae..ec9aca5 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -83,7 +83,7 @@
 LOCAL_SRC_FILES += $(core_src_files)
 LOCAL_C_INCLUDES += $(core_c_includes)
 LOCAL_SHARED_LIBRARIES += $(core_shared_libraries) libcrypto libexpat libicuuc libicui18n libnativehelper libz
-LOCAL_STATIC_LIBRARIES += $(core_static_libraries)
+LOCAL_STATIC_LIBRARIES += $(core_static_libraries) libziparchive
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := libjavacore
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
@@ -123,7 +123,7 @@
     LOCAL_MODULE := libjavacore
     LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/NativeCode.mk
     LOCAL_SHARED_LIBRARIES += $(core_shared_libraries) libexpat-host libicuuc-host libicui18n-host libcrypto-host libz-host
-    LOCAL_STATIC_LIBRARIES += $(core_static_libraries)
+    LOCAL_STATIC_LIBRARIES += $(core_static_libraries) libziparchive-host
     include $(BUILD_HOST_SHARED_LIBRARY)
 
     ifeq ($(LIBCORE_SKIP_TESTS),)
diff --git a/luni/src/main/java/java/util/jar/JarFile.java b/luni/src/main/java/java/util/jar/JarFile.java
index b219637..6b147f6 100644
--- a/luni/src/main/java/java/util/jar/JarFile.java
+++ b/luni/src/main/java/java/util/jar/JarFile.java
@@ -215,8 +215,7 @@
             // 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,
-                    manifest.getMainAttributesEnd());
+            verifier = new JarVerifier(getName(), manifest, metaEntries);
         } else {
             verifier = null;
             manifestBytes = metaEntries.get(MANIFEST_NAME);
diff --git a/luni/src/main/java/java/util/jar/JarInputStream.java b/luni/src/main/java/java/util/jar/JarInputStream.java
index 31bd05f..585c135 100644
--- a/luni/src/main/java/java/util/jar/JarInputStream.java
+++ b/luni/src/main/java/java/util/jar/JarInputStream.java
@@ -90,8 +90,7 @@
             if (verify) {
                 HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
                 metaEntries.put(JarFile.MANIFEST_NAME, manifestBytes);
-                verifier = new JarVerifier("JarInputStream", manifest,
-                        metaEntries, manifest.getMainAttributesEnd());
+                verifier = new JarVerifier("JarInputStream", manifest, metaEntries);
             }
         }
 
diff --git a/luni/src/main/java/java/util/jar/JarVerifier.java b/luni/src/main/java/java/util/jar/JarVerifier.java
index eb88990..c545a02 100644
--- a/luni/src/main/java/java/util/jar/JarVerifier.java
+++ b/luni/src/main/java/java/util/jar/JarVerifier.java
@@ -152,12 +152,11 @@
      * @param name
      *            the name of the JAR file being verified.
      */
-    JarVerifier(String name, Manifest manifest, HashMap<String, byte[]> metaEntries,
-            int mainAttributesEnd) {
+    JarVerifier(String name, Manifest manifest, HashMap<String, byte[]> metaEntries) {
         jarName = name;
         this.manifest = manifest;
         this.metaEntries = metaEntries;
-        this.mainAttributesEnd = mainAttributesEnd;
+        this.mainAttributesEnd = manifest.getMainAttributesEnd();
     }
 
     /**
diff --git a/luni/src/main/java/java/util/jar/StrictJarFile.java b/luni/src/main/java/java/util/jar/StrictJarFile.java
new file mode 100644
index 0000000..fa175d8
--- /dev/null
+++ b/luni/src/main/java/java/util/jar/StrictJarFile.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed 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 dalvik.system.CloseGuard;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.RandomAccessFile;
+import java.security.cert.Certificate;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.zip.Inflater;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+/**
+ * A subset of the JarFile API implemented as a thin wrapper over
+ * system/core/libziparchive.
+ *
+ * @hide for internal use only. Not API compatible (or as forgiving) as
+ *        {@link java.util.jar.JarFile}
+ */
+public final class StrictJarFile {
+
+    private final long nativeHandle;
+
+    // NOTE: It's possible to share a file descriptor with the native
+    // code, at the cost of some additional complexity.
+    private final RandomAccessFile raf;
+
+    private final Manifest manifest;
+    private final JarVerifier verifier;
+
+    private final boolean isSigned;
+
+    private final CloseGuard guard = CloseGuard.get();
+    private boolean closed;
+
+    public StrictJarFile(String fileName) throws IOException {
+        this.nativeHandle = nativeOpenJarFile(fileName);
+        this.raf = new RandomAccessFile(fileName, "r");
+
+        try {
+            // Read the MANIFEST and signature files up front and try to
+            // parse them. We never want to accept a JAR File with broken signatures
+            // or manifests, so it's best to throw as early as possible.
+            HashMap<String, byte[]> metaEntries = getMetaEntries();
+            this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+            this.verifier = new JarVerifier(fileName, manifest, metaEntries);
+
+            isSigned = verifier.readCertificates() && verifier.isSignedJar();
+        } catch (IOException ioe) {
+            nativeClose(this.nativeHandle);
+            throw ioe;
+        }
+
+        guard.open("close");
+    }
+
+    public Manifest getManifest() {
+        return manifest;
+    }
+
+    public Iterator<ZipEntry> iterator() throws IOException {
+        return new EntryIterator(nativeHandle, "");
+    }
+
+    public ZipEntry findEntry(String name) {
+        return nativeFindEntry(nativeHandle, name);
+    }
+
+    /**
+     * Return all certificates for a given {@link ZipEntry} belonging to this jar.
+     * This method MUST be called only after fully exhausting the InputStream belonging
+     * to this entry.
+     *
+     * Returns {@code null} if this jar file isn't signed or if this method is
+     * called before the stream is processed.
+     */
+    public Certificate[] getCertificates(ZipEntry ze) {
+        if (isSigned) {
+            return verifier.getCertificates(ze.getName());
+        }
+
+        return null;
+    }
+
+    public InputStream getInputStream(ZipEntry ze) {
+        final InputStream is = getZipInputStream(ze);
+
+        if (isSigned) {
+            JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+            if (entry == null) {
+                return is;
+            }
+
+            return new JarFile.JarFileInputStream(is, ze.getSize(), entry);
+        }
+
+        return is;
+    }
+
+    public void close() throws IOException {
+        if (!closed) {
+            guard.close();
+
+            nativeClose(nativeHandle);
+            IoUtils.closeQuietly(raf);
+            closed = true;
+        }
+    }
+
+    private InputStream getZipInputStream(ZipEntry ze) {
+        if (ze.getMethod() == ZipEntry.STORED) {
+            return new ZipFile.RAFStream(raf, ze.getDataOffset(),
+                    ze.getDataOffset() + ze.getSize());
+        } else {
+            final ZipFile.RAFStream wrapped = new ZipFile.RAFStream(
+                    raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+            int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+            return new ZipFile.ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
+        }
+    }
+
+    static final class EntryIterator implements Iterator<ZipEntry> {
+        private final long iterationHandle;
+        private ZipEntry nextEntry;
+
+        EntryIterator(long nativeHandle, String prefix) throws IOException {
+            iterationHandle = nativeStartIteration(nativeHandle, prefix);
+        }
+
+        public ZipEntry next() {
+            if (nextEntry != null) {
+                final ZipEntry ze = nextEntry;
+                nextEntry = null;
+                return ze;
+            }
+
+            return nativeNextEntry(iterationHandle);
+        }
+
+        public boolean hasNext() {
+            if (nextEntry != null) {
+                return true;
+            }
+
+            final ZipEntry ze = nativeNextEntry(iterationHandle);
+            if (ze == null) {
+                return false;
+            }
+
+            nextEntry = ze;
+            return true;
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    private HashMap<String, byte[]> getMetaEntries() throws IOException {
+        HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>();
+
+        Iterator<ZipEntry> entryIterator = new EntryIterator(nativeHandle, "META-INF/");
+        while (entryIterator.hasNext()) {
+            final ZipEntry entry = entryIterator.next();
+            metaEntries.put(entry.getName(), Streams.readFully(getInputStream(entry)));
+        }
+
+        return metaEntries;
+    }
+
+    private static native long nativeOpenJarFile(String fileName) throws IOException;
+    private static native long nativeStartIteration(long nativeHandle, String prefix);
+    private static native ZipEntry nativeNextEntry(long iterationHandle);
+    private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
+    private static native void nativeClose(long nativeHandle);
+}
diff --git a/luni/src/main/java/java/util/zip/ZipEntry.java b/luni/src/main/java/java/util/zip/ZipEntry.java
index f64c717..69f027a 100644
--- a/luni/src/main/java/java/util/zip/ZipEntry.java
+++ b/luni/src/main/java/java/util/zip/ZipEntry.java
@@ -25,9 +25,9 @@
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
-import libcore.io.Streams;
 import libcore.io.BufferIterator;
 import libcore.io.HeapBufferIterator;
+import libcore.io.Streams;
 
 /**
  * An entry within a zip file.
@@ -54,6 +54,8 @@
     int nameLength = -1;
     long localHeaderRelOffset = -1;
 
+    long dataOffset = -1;
+
     /**
      * Zip entry state: Deflated.
      */
@@ -64,6 +66,23 @@
      */
     public static final int STORED = 0;
 
+    ZipEntry(String name, String comment, long crc, long compressedSize,
+            long size, int compressionMethod, int time, int modDate, byte[] extra,
+            int nameLength, long localHeaderRelOffset, long dataOffset) {
+        this.name = name;
+        this.comment = comment;
+        this.crc = crc;
+        this.compressedSize = compressedSize;
+        this.size = size;
+        this.compressionMethod = compressionMethod;
+        this.time = time;
+        this.modDate = modDate;
+        this.extra = extra;
+        this.nameLength = nameLength;
+        this.localHeaderRelOffset = localHeaderRelOffset;
+        this.dataOffset = dataOffset;
+    }
+
     /**
      * Constructs a new {@code ZipEntry} with the specified name. The name is actually a path,
      * and may contain {@code /} characters.
@@ -287,6 +306,17 @@
         }
     }
 
+
+    /** @hide */
+    public void setDataOffset(long value) {
+        dataOffset = value;
+    }
+
+    /** @hide */
+    public long getDataOffset() {
+        return dataOffset;
+    }
+
     /**
      * Returns the string representation of this {@code ZipEntry}.
      *
@@ -316,6 +346,7 @@
         extra = ze.extra;
         nameLength = ze.nameLength;
         localHeaderRelOffset = ze.localHeaderRelOffset;
+        dataOffset = ze.dataOffset;
     }
 
     /**
diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java
index c25bbc1..40036cf 100644
--- a/luni/src/main/java/java/util/zip/ZipFile.java
+++ b/luni/src/main/java/java/util/zip/ZipFile.java
@@ -434,16 +434,23 @@
      * collisions.)
      *
      * <p>We could support mark/reset, but we don't currently need them.
+     *
+     * @hide
      */
-    static class RAFStream extends InputStream {
+    public static class RAFStream extends InputStream {
         private final RandomAccessFile sharedRaf;
         private long endOffset;
         private long offset;
 
-        public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+
+        public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
             sharedRaf = raf;
             offset = initialOffset;
-            endOffset = raf.length();
+            this.endOffset = endOffset;
+        }
+
+        public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+            this(raf, initialOffset, raf.length());
         }
 
         @Override public int available() throws IOException {
@@ -491,7 +498,8 @@
         }
     }
 
-    static class ZipInflaterInputStream extends InflaterInputStream {
+    /** @hide */
+    public static class ZipInflaterInputStream extends InflaterInputStream {
         private final ZipEntry entry;
         private long bytesRead = 0;
 
diff --git a/luni/src/main/native/Register.cpp b/luni/src/main/native/Register.cpp
index 15f6953..68c59c3 100644
--- a/luni/src/main/native/Register.cpp
+++ b/luni/src/main/native/Register.cpp
@@ -49,6 +49,7 @@
     REGISTER(register_java_nio_ByteOrder);
     REGISTER(register_java_nio_charset_Charsets);
     REGISTER(register_java_text_Bidi);
+    REGISTER(register_java_util_jar_StrictJarFile);
     REGISTER(register_java_util_regex_Matcher);
     REGISTER(register_java_util_regex_Pattern);
     REGISTER(register_java_util_zip_Adler32);
diff --git a/luni/src/main/native/java_util_jar_StrictJarFile.cpp b/luni/src/main/native/java_util_jar_StrictJarFile.cpp
new file mode 100644
index 0000000..7611749
--- /dev/null
+++ b/luni/src/main/native/java_util_jar_StrictJarFile.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "StrictJarFile"
+
+#include <string>
+
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "UniquePtr.h"
+#include "jni.h"
+#include "ziparchive/zip_archive.h"
+#include "cutils/log.h"
+
+static void throwIoException(JNIEnv* env, const int32_t errorCode) {
+  jniThrowException(env, "java/io/IOException", ErrorCodeString(errorCode));
+}
+
+static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName,
+                           const uint16_t nameLength) {
+  ScopedLocalRef<jclass> zipEntryClass(env, env->FindClass("java/util/zip/ZipEntry"));
+  const jmethodID zipEntryCtor = env->GetMethodID(zipEntryClass.get(), "<init>",
+                                   "(Ljava/lang/String;Ljava/lang/String;JJJIII[BIJJ)V");
+
+  return env->NewObject(zipEntryClass.get(),
+                        zipEntryCtor,
+                        entryName,
+                        NULL,  // comment
+                        static_cast<jlong>(entry.crc32),
+                        static_cast<jlong>(entry.compressed_length),
+                        static_cast<jlong>(entry.uncompressed_length),
+                        static_cast<jint>(entry.method),
+                        static_cast<jint>(0),  // time
+                        static_cast<jint>(0),  // modData
+                        NULL,  // byte[] extra
+                        static_cast<jint>(nameLength),
+                        static_cast<jlong>(-1),  // local header offset
+                        static_cast<jlong>(entry.offset));
+}
+
+static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) {
+  ScopedUtfChars fileChars(env, fileName);
+  if (fileChars.c_str() == NULL) {
+    return static_cast<jlong>(-1);
+  }
+
+  ZipArchiveHandle handle;
+  int32_t error = OpenArchive(fileChars.c_str(), &handle);
+  if (error) {
+    throwIoException(env, error);
+    return static_cast<jlong>(-1);
+  }
+
+  return reinterpret_cast<jlong>(handle);
+}
+
+class IterationHandle {
+ public:
+  IterationHandle(const char* prefix) :
+    cookie_(NULL), prefix_(strdup(prefix)) {
+  }
+
+  void** CookieAddress() {
+    return &cookie_;
+  }
+
+  const char* Prefix() const {
+    return prefix_;
+  }
+
+  ~IterationHandle() {
+    free(prefix_);
+  }
+
+ private:
+  void* cookie_;
+  char* prefix_;
+};
+
+
+static jlong StrictJarFile_nativeStartIteration(JNIEnv* env, jobject, jlong nativeHandle,
+                                                jstring prefix) {
+  ScopedUtfChars prefixChars(env, prefix);
+  if (prefixChars.c_str() == NULL) {
+    return static_cast<jlong>(-1);
+  }
+
+  IterationHandle* handle = new IterationHandle(prefixChars.c_str());
+  int32_t error = 0;
+  if (prefixChars.size() == 0) {
+    error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+                           handle->CookieAddress(), NULL);
+  } else {
+    error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+                           handle->CookieAddress(), handle->Prefix());
+  }
+
+  if (error) {
+    throwIoException(env, error);
+    return static_cast<jlong>(-1);
+  }
+
+  return reinterpret_cast<jlong>(handle);
+}
+
+static jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
+  ZipEntry data;
+  ZipEntryName entryName;
+
+  IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
+  const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
+  if (error) {
+    delete handle;
+    return NULL;
+  }
+
+  UniquePtr<char[]> entryNameCString(new char[entryName.name_length + 1]);
+  memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
+  entryNameCString[entryName.name_length] = '\0';
+  ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
+
+  return newZipEntry(env, data, entryNameString.get(), entryName.name_length);
+}
+
+static jobject StrictJarFile_nativeFindEntry(JNIEnv* env, jobject, jlong nativeHandle,
+                                             jstring entryName) {
+  ScopedUtfChars entryNameChars(env, entryName);
+  if (entryNameChars.c_str() == NULL) {
+    return NULL;
+  }
+
+  ZipEntry data;
+  const int32_t error = FindEntry(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+                                  entryNameChars.c_str(), &data);
+  if (error) {
+    return NULL;
+  }
+
+  return newZipEntry(env, data, entryName, entryNameChars.size());
+}
+
+static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) {
+  CloseArchive(reinterpret_cast<ZipArchiveHandle>(nativeHandle));
+}
+
+static JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;)J"),
+  NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
+  NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
+  NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
+  NATIVE_METHOD(StrictJarFile, nativeClose, "(J)V"),
+};
+
+void register_java_util_jar_StrictJarFile(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "java/util/jar/StrictJarFile", gMethods, NELEM(gMethods));
+
+}
diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk
index 9e1a05b..4e10cc7 100644
--- a/luni/src/main/native/sub.mk
+++ b/luni/src/main/native/sub.mk
@@ -28,6 +28,7 @@
 	java_nio_ByteOrder.cpp \
 	java_nio_charset_Charsets.cpp \
 	java_text_Bidi.cpp \
+	java_util_jar_StrictJarFile.cpp \
 	java_util_regex_Matcher.cpp \
 	java_util_regex_Pattern.cpp \
 	java_util_zip_Adler32.cpp \
@@ -60,7 +61,8 @@
 	external/icu4c/common \
 	external/icu4c/i18n \
 	external/openssl/include \
-	external/zlib
+	external/zlib \
+	system/core/include
 
 LOCAL_STATIC_LIBRARIES += \
 	libfdlibm
diff --git a/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java b/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java
new file mode 100644
index 0000000..0b194f5
--- /dev/null
+++ b/luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2013 Google Inc.
+ *
+ * Licensed 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 libcore.java.util.jar;
+
+import junit.framework.TestCase;
+import tests.support.resource.Support_Resources;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.jar.StrictJarFile;
+import java.util.zip.ZipEntry;
+import libcore.io.Streams;
+
+public class StrictJarFileTest extends TestCase {
+
+    // A well formed jar file with 6 entries.
+    private static final String JAR_1 = "hyts_patch.jar";
+
+    private File resources;
+
+    @Override
+    protected void setUp() {
+        resources = Support_Resources.createTempFolder();
+    }
+
+    public void testConstructor() throws Exception {
+        try {
+            StrictJarFile jarFile = new StrictJarFile("Wrong.file");
+            fail("Should throw IOException");
+        } catch (IOException e) {
+            // expected
+        }
+
+        Support_Resources.copyFile(resources, null, JAR_1);
+        String fileName = (new File(resources, JAR_1)).getCanonicalPath();
+        StrictJarFile jarFile = new StrictJarFile(fileName);
+        jarFile.close();
+    }
+
+    public void testIteration() throws Exception {
+        Support_Resources.copyFile(resources, null, JAR_1);
+        StrictJarFile jarFile = new StrictJarFile(new File(resources, JAR_1).getAbsolutePath());
+
+        Iterator<ZipEntry> it = jarFile.iterator();
+        HashMap<String, ZipEntry> entries = new HashMap<String, ZipEntry>();
+        while (it.hasNext()) {
+            final ZipEntry ze = it.next();
+            entries.put(ze.getName(), ze);
+        }
+
+        assertEquals(6, entries.size());
+        assertTrue(entries.containsKey("META-INF/"));
+
+        assertTrue(entries.containsKey("META-INF/MANIFEST.MF"));
+        ZipEntry ze = entries.get("META-INF/MANIFEST.MF");
+        assertEquals(62, ze.getSize());
+        assertEquals(ZipEntry.DEFLATED, ze.getMethod());
+        assertEquals(61, ze.getCompressedSize());
+
+        assertTrue(entries.containsKey("Blah.txt"));
+        ze = entries.get("Blah.txt");
+        assertEquals(4, ze.getSize());
+        assertEquals(ZipEntry.DEFLATED, ze.getMethod());
+        assertEquals(6, ze.getCompressedSize());
+        assertEquals("Blah", new String(Streams.readFully(jarFile.getInputStream(ze)),
+                Charset.forName("UTF-8")));
+
+        assertTrue(entries.containsKey("foo/"));
+        assertTrue(entries.containsKey("foo/bar/"));
+        assertTrue(entries.containsKey("foo/bar/A.class"));
+        ze = entries.get("foo/bar/A.class");
+        assertEquals(311, ze.getSize());
+        assertEquals(ZipEntry.DEFLATED, ze.getMethod());
+        assertEquals(225, ze.getCompressedSize());
+    }
+
+    public void testFindEntry() throws Exception {
+        Support_Resources.copyFile(resources, null, JAR_1);
+        StrictJarFile jarFile = new StrictJarFile(new File(resources, JAR_1).getAbsolutePath());
+
+        assertNull(jarFile.findEntry("foobar"));
+        assertNull(jarFile.findEntry("blah.txt"));
+        assertNotNull(jarFile.findEntry("Blah.txt"));
+        final ZipEntry ze = jarFile.findEntry("Blah.txt");
+        assertEquals(4, ze.getSize());
+        assertEquals(ZipEntry.DEFLATED, ze.getMethod());
+        assertEquals(6, ze.getCompressedSize());
+        assertEquals("Blah", new String(Streams.readFully(jarFile.getInputStream(ze)),
+                Charset.forName("UTF-8")));
+    }
+
+    public void testGetManifest() throws Exception {
+        Support_Resources.copyFile(resources, null, JAR_1);
+        StrictJarFile jarFile = new StrictJarFile(new File(resources, JAR_1).getAbsolutePath());
+
+        assertNotNull(jarFile.getManifest());
+        assertEquals("1.4.2 (IBM Corporation)", jarFile.getManifest().getMainAttributes().getValue("Created-By"));
+    }
+
+    public void testJarSigning_wellFormed() throws IOException {
+        Support_Resources.copyFile(resources, null, "Integrate.jar");
+        StrictJarFile jarFile = new StrictJarFile(new File(resources, "Integrate.jar").getAbsolutePath());
+        Iterator<ZipEntry> entries = jarFile.iterator();
+        while (entries.hasNext()) {
+            ZipEntry zipEntry = entries.next();
+            jarFile.getInputStream(zipEntry).skip(Long.MAX_VALUE);
+            if ("Test.class".equals(zipEntry.getName())) {
+                assertNotNull(jarFile.getCertificates(zipEntry));
+            }
+        }
+    }
+
+     public void testJarSigning_fudgedEntry() throws IOException {
+        Support_Resources.copyFile(resources, null, "Integrate.jar");
+        StrictJarFile jarFile = new StrictJarFile(
+                new File(resources, "Integrate.jar").getAbsolutePath());
+
+        ZipEntry ze = jarFile.findEntry("Test.class");
+        jarFile.getInputStream(ze).skip(Long.MAX_VALUE);
+
+        // Fudge the size so that certificates do not match.
+        ze.setSize(ze.getSize() - 1);
+        try {
+            jarFile.getInputStream(ze).skip(Long.MAX_VALUE);
+            fail();
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testJarSigning_modifiedClass() throws IOException {
+        Support_Resources.copyFile(resources, null,  "Modified_Class.jar");
+        StrictJarFile jarFile = new StrictJarFile(
+                new File(resources,  "Modified_Class.jar").getAbsolutePath());
+
+        ZipEntry ze = jarFile.findEntry("Test.class");
+        try {
+            jarFile.getInputStream(ze).skip(Long.MAX_VALUE);
+            fail();
+        } catch (SecurityException expected) {
+        }
+    }
+
+    public void testJarSigning_brokenMainAttributes() throws Exception {
+        assertThrowsOnInit("Modified_Manifest_MainAttributes.jar");
+    }
+
+    public void testJarSigning_brokenEntryAttributes() throws Exception {
+        assertThrowsOnInit("Modified_Manifest_EntryAttributes.jar");
+    }
+
+    public void testJarSigning_brokenSignatureFile() throws Exception {
+        assertThrowsOnInit("Modified_SF_EntryAttributes.jar");
+    }
+
+    private void assertThrowsOnInit(String name) throws Exception {
+        Support_Resources.copyFile(resources, null,  name);
+        try {
+            StrictJarFile jarFile = new StrictJarFile(
+                    new File(resources,  name).getAbsolutePath());
+            fail();
+        } catch (SecurityException expected) {
+        }
+    }
+}