summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NativeCode.mk4
-rw-r--r--luni/src/main/java/java/util/jar/JarFile.java3
-rw-r--r--luni/src/main/java/java/util/jar/JarInputStream.java3
-rw-r--r--luni/src/main/java/java/util/jar/JarVerifier.java5
-rw-r--r--luni/src/main/java/java/util/jar/StrictJarFile.java197
-rw-r--r--luni/src/main/java/java/util/zip/ZipEntry.java33
-rw-r--r--luni/src/main/java/java/util/zip/ZipFile.java16
-rw-r--r--luni/src/main/native/Register.cpp1
-rw-r--r--luni/src/main/native/java_util_jar_StrictJarFile.cpp173
-rw-r--r--luni/src/main/native/sub.mk4
-rw-r--r--luni/src/test/java/libcore/java/util/jar/StrictJarFileTest.java180
11 files changed, 604 insertions, 15 deletions
diff --git a/NativeCode.mk b/NativeCode.mk
index f8312ae..ec9aca5 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -83,7 +83,7 @@ LOCAL_CPPFLAGS += $(core_cppflags)
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 @@ ifeq ($(WITH_HOST_DALVIK),true)
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 @@ public class JarFile extends ZipFile {
// 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 @@ public class JarInputStream extends ZipInputStream {
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 @@ class JarVerifier {
* @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.Arrays;
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 @@ public class ZipEntry implements ZipConstants, Cloneable {
int nameLength = -1;
long localHeaderRelOffset = -1;
+ long dataOffset = -1;
+
/**
* Zip entry state: Deflated.
*/
@@ -64,6 +66,23 @@ public class ZipEntry implements ZipConstants, Cloneable {
*/
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 @@ public class ZipEntry implements ZipConstants, Cloneable {
}
}
+
+ /** @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 @@ public class ZipEntry implements ZipConstants, Cloneable {
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 @@ public class ZipFile implements Closeable, ZipConstants {
* 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 @@ public class ZipFile implements Closeable, ZipConstants {
}
}
- 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 @@ jint JNI_OnLoad(JavaVM* vm, void*) {
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 @@ LOCAL_SRC_FILES := \
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 @@ LOCAL_C_INCLUDES += \
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) {
+ }
+ }
+}