Merge "Add support for opening JAR/ZIP files via FD." am: 7f64c195f7 am: 4c65d49a92
am: de915e3ade
Change-Id: I625a398a8def68b8b8ef77f1408f5c6f8a85c1fa
This commit is contained in:
@@ -17,19 +17,24 @@
|
|||||||
|
|
||||||
package android.util.jar;
|
package android.util.jar;
|
||||||
|
|
||||||
|
import android.system.ErrnoException;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.system.OsConstants;
|
||||||
|
|
||||||
import dalvik.system.CloseGuard;
|
import dalvik.system.CloseGuard;
|
||||||
|
import java.io.FileDescriptor;
|
||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.RandomAccessFile;
|
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
import java.util.zip.Inflater;
|
import java.util.zip.Inflater;
|
||||||
import java.util.zip.InflaterInputStream;
|
import java.util.zip.InflaterInputStream;
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.jar.JarFile;
|
import libcore.io.IoBridge;
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
import libcore.io.Streams;
|
import libcore.io.Streams;
|
||||||
|
|
||||||
@@ -46,7 +51,7 @@ public final class StrictJarFile {
|
|||||||
|
|
||||||
// NOTE: It's possible to share a file descriptor with the native
|
// NOTE: It's possible to share a file descriptor with the native
|
||||||
// code, at the cost of some additional complexity.
|
// code, at the cost of some additional complexity.
|
||||||
private final RandomAccessFile raf;
|
private final FileDescriptor fd;
|
||||||
|
|
||||||
private final StrictJarManifest manifest;
|
private final StrictJarManifest manifest;
|
||||||
private final StrictJarVerifier verifier;
|
private final StrictJarVerifier verifier;
|
||||||
@@ -61,8 +66,30 @@ public final class StrictJarFile {
|
|||||||
this(fileName, true, true);
|
this(fileName, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StrictJarFile(FileDescriptor fd)
|
||||||
|
throws IOException, SecurityException {
|
||||||
|
this(fd, true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrictJarFile(FileDescriptor fd,
|
||||||
|
boolean verify,
|
||||||
|
boolean signatureSchemeRollbackProtectionsEnforced)
|
||||||
|
throws IOException, SecurityException {
|
||||||
|
this("[fd:" + fd.getInt$() + "]", fd, verify,
|
||||||
|
signatureSchemeRollbackProtectionsEnforced);
|
||||||
|
}
|
||||||
|
|
||||||
|
public StrictJarFile(String fileName,
|
||||||
|
boolean verify,
|
||||||
|
boolean signatureSchemeRollbackProtectionsEnforced)
|
||||||
|
throws IOException, SecurityException {
|
||||||
|
this(fileName, IoBridge.open(fileName, OsConstants.O_RDONLY),
|
||||||
|
verify, signatureSchemeRollbackProtectionsEnforced);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @param name of the archive (not necessarily a path).
|
||||||
|
* @param fd seekable file descriptor for the JAR file.
|
||||||
* @param verify whether to verify the file's JAR signatures and collect the corresponding
|
* @param verify whether to verify the file's JAR signatures and collect the corresponding
|
||||||
* signer certificates.
|
* signer certificates.
|
||||||
* @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against
|
* @param signatureSchemeRollbackProtectionsEnforced {@code true} to enforce protections against
|
||||||
@@ -70,12 +97,13 @@ public final class StrictJarFile {
|
|||||||
* {@code false} to ignore any such protections. This parameter is ignored when
|
* {@code false} to ignore any such protections. This parameter is ignored when
|
||||||
* {@code verify} is {@code false}.
|
* {@code verify} is {@code false}.
|
||||||
*/
|
*/
|
||||||
public StrictJarFile(String fileName,
|
private StrictJarFile(String name,
|
||||||
|
FileDescriptor fd,
|
||||||
boolean verify,
|
boolean verify,
|
||||||
boolean signatureSchemeRollbackProtectionsEnforced)
|
boolean signatureSchemeRollbackProtectionsEnforced)
|
||||||
throws IOException, SecurityException {
|
throws IOException, SecurityException {
|
||||||
this.nativeHandle = nativeOpenJarFile(fileName);
|
this.nativeHandle = nativeOpenJarFile(name, fd.getInt$());
|
||||||
this.raf = new RandomAccessFile(fileName, "r");
|
this.fd = fd;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Read the MANIFEST and signature files up front and try to
|
// Read the MANIFEST and signature files up front and try to
|
||||||
@@ -86,14 +114,14 @@ public final class StrictJarFile {
|
|||||||
this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
|
this.manifest = new StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
|
||||||
this.verifier =
|
this.verifier =
|
||||||
new StrictJarVerifier(
|
new StrictJarVerifier(
|
||||||
fileName,
|
name,
|
||||||
manifest,
|
manifest,
|
||||||
metaEntries,
|
metaEntries,
|
||||||
signatureSchemeRollbackProtectionsEnforced);
|
signatureSchemeRollbackProtectionsEnforced);
|
||||||
Set<String> files = manifest.getEntries().keySet();
|
Set<String> files = manifest.getEntries().keySet();
|
||||||
for (String file : files) {
|
for (String file : files) {
|
||||||
if (findEntry(file) == null) {
|
if (findEntry(file) == null) {
|
||||||
throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
|
throw new SecurityException("File " + file + " in manifest does not exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +133,7 @@ public final class StrictJarFile {
|
|||||||
}
|
}
|
||||||
} catch (IOException | SecurityException e) {
|
} catch (IOException | SecurityException e) {
|
||||||
nativeClose(this.nativeHandle);
|
nativeClose(this.nativeHandle);
|
||||||
IoUtils.closeQuietly(this.raf);
|
IoUtils.closeQuietly(fd);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,10 +220,12 @@ public final class StrictJarFile {
|
|||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
if (!closed) {
|
if (!closed) {
|
||||||
guard.close();
|
if (guard != null) {
|
||||||
|
guard.close();
|
||||||
|
}
|
||||||
|
|
||||||
nativeClose(nativeHandle);
|
nativeClose(nativeHandle);
|
||||||
IoUtils.closeQuietly(raf);
|
IoUtils.closeQuietly(fd);
|
||||||
closed = true;
|
closed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,11 +244,11 @@ public final class StrictJarFile {
|
|||||||
|
|
||||||
private InputStream getZipInputStream(ZipEntry ze) {
|
private InputStream getZipInputStream(ZipEntry ze) {
|
||||||
if (ze.getMethod() == ZipEntry.STORED) {
|
if (ze.getMethod() == ZipEntry.STORED) {
|
||||||
return new RAFStream(raf, ze.getDataOffset(),
|
return new FDStream(fd, ze.getDataOffset(),
|
||||||
ze.getDataOffset() + ze.getSize());
|
ze.getDataOffset() + ze.getSize());
|
||||||
} else {
|
} else {
|
||||||
final RAFStream wrapped = new RAFStream(
|
final FDStream wrapped = new FDStream(
|
||||||
raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
|
fd, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
|
||||||
|
|
||||||
int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
|
int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
|
||||||
return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
|
return new ZipInflaterInputStream(wrapped, new Inflater(true), bufSize, ze);
|
||||||
@@ -396,7 +426,7 @@ public final class StrictJarFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared
|
* Wrap a stream around a FileDescriptor. The file descriptor is shared
|
||||||
* among all streams returned by getInputStream(), so we have to synchronize
|
* among all streams returned by getInputStream(), so we have to synchronize
|
||||||
* access to it. (We can optimize this by adding buffering here to reduce
|
* access to it. (We can optimize this by adding buffering here to reduce
|
||||||
* collisions.)
|
* collisions.)
|
||||||
@@ -405,22 +435,17 @@ public final class StrictJarFile {
|
|||||||
*
|
*
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
public static class RAFStream extends InputStream {
|
public static class FDStream extends InputStream {
|
||||||
private final RandomAccessFile sharedRaf;
|
private final FileDescriptor fd;
|
||||||
private long endOffset;
|
private long endOffset;
|
||||||
private long offset;
|
private long offset;
|
||||||
|
|
||||||
|
public FDStream(FileDescriptor fd, long initialOffset, long endOffset) {
|
||||||
public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
|
this.fd = fd;
|
||||||
sharedRaf = raf;
|
|
||||||
offset = initialOffset;
|
offset = initialOffset;
|
||||||
this.endOffset = endOffset;
|
this.endOffset = endOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
|
|
||||||
this(raf, initialOffset, raf.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public int available() throws IOException {
|
@Override public int available() throws IOException {
|
||||||
return (offset < endOffset ? 1 : 0);
|
return (offset < endOffset ? 1 : 0);
|
||||||
}
|
}
|
||||||
@@ -430,13 +455,17 @@ public final class StrictJarFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
@Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
|
||||||
synchronized (sharedRaf) {
|
synchronized (this.fd) {
|
||||||
final long length = endOffset - offset;
|
final long length = endOffset - offset;
|
||||||
if (byteCount > length) {
|
if (byteCount > length) {
|
||||||
byteCount = (int) length;
|
byteCount = (int) length;
|
||||||
}
|
}
|
||||||
sharedRaf.seek(offset);
|
try {
|
||||||
int count = sharedRaf.read(buffer, byteOffset, byteCount);
|
Os.lseek(fd, offset, OsConstants.SEEK_SET);
|
||||||
|
} catch (ErrnoException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
int count = IoBridge.read(fd, buffer, byteOffset, byteCount);
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
offset += count;
|
offset += count;
|
||||||
return count;
|
return count;
|
||||||
@@ -455,8 +484,8 @@ public final class StrictJarFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static native long nativeOpenJarFile(String name, int fd)
|
||||||
private static native long nativeOpenJarFile(String fileName) throws IOException;
|
throws IOException;
|
||||||
private static native long nativeStartIteration(long nativeHandle, String prefix);
|
private static native long nativeStartIteration(long nativeHandle, String prefix);
|
||||||
private static native ZipEntry nativeNextEntry(long iterationHandle);
|
private static native ZipEntry nativeNextEntry(long iterationHandle);
|
||||||
private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
|
private static native ZipEntry nativeFindEntry(long nativeHandle, String entryName);
|
||||||
|
|||||||
@@ -51,14 +51,16 @@ static jobject newZipEntry(JNIEnv* env, const ZipEntry& entry, jstring entryName
|
|||||||
static_cast<jlong>(entry.offset));
|
static_cast<jlong>(entry.offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring fileName) {
|
static jlong StrictJarFile_nativeOpenJarFile(JNIEnv* env, jobject, jstring name, jint fd) {
|
||||||
ScopedUtfChars fileChars(env, fileName);
|
// Name argument is used for logging, and can be any string.
|
||||||
if (fileChars.c_str() == NULL) {
|
ScopedUtfChars nameChars(env, name);
|
||||||
|
if (nameChars.c_str() == NULL) {
|
||||||
return static_cast<jlong>(-1);
|
return static_cast<jlong>(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
ZipArchiveHandle handle;
|
ZipArchiveHandle handle;
|
||||||
int32_t error = OpenArchive(fileChars.c_str(), &handle);
|
int32_t error = OpenArchiveFd(fd, nameChars.c_str(), &handle,
|
||||||
|
false /* owned by Java side */);
|
||||||
if (error) {
|
if (error) {
|
||||||
CloseArchive(handle);
|
CloseArchive(handle);
|
||||||
throwIoException(env, error);
|
throwIoException(env, error);
|
||||||
@@ -154,7 +156,7 @@ static void StrictJarFile_nativeClose(JNIEnv*, jobject, jlong nativeHandle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
static JNINativeMethod gMethods[] = {
|
||||||
NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;)J"),
|
NATIVE_METHOD(StrictJarFile, nativeOpenJarFile, "(Ljava/lang/String;I)J"),
|
||||||
NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
|
NATIVE_METHOD(StrictJarFile, nativeStartIteration, "(JLjava/lang/String;)J"),
|
||||||
NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
|
NATIVE_METHOD(StrictJarFile, nativeNextEntry, "(J)Ljava/util/zip/ZipEntry;"),
|
||||||
NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
|
NATIVE_METHOD(StrictJarFile, nativeFindEntry, "(JLjava/lang/String;)Ljava/util/zip/ZipEntry;"),
|
||||||
|
|||||||
Reference in New Issue
Block a user