import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.jar.StrictJarFile;
import java.util.zip.ZipEntry;
+import android.util.jar.StrictJarFile;
+
/**
* Parser for package files (APKs) on disk. This supports apps packaged either
* as a single "monolithic" APK, or apps packaged as a "cluster" of multiple
--- /dev/null
+/*
+ * 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 android.util.jar;
+
+import dalvik.system.CloseGuard;
+import java.io.ByteArrayInputStream;
+import java.io.FilterInputStream;
+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.Set;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+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 StrictJarManifest manifest;
+ private final StrictJarVerifier verifier;
+
+ private final boolean isSigned;
+
+ private final CloseGuard guard = CloseGuard.get();
+ private boolean closed;
+
+ public StrictJarFile(String fileName) throws IOException, SecurityException {
+ 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 StrictJarManifest(metaEntries.get(JarFile.MANIFEST_NAME), true);
+ this.verifier = new StrictJarVerifier(fileName, manifest, metaEntries);
+ Set<String> files = manifest.getEntries().keySet();
+ for (String file : files) {
+ if (findEntry(file) == null) {
+ throw new SecurityException(fileName + ": File " + file + " in manifest does not exist");
+ }
+ }
+
+ isSigned = verifier.readCertificates() && verifier.isSignedJar();
+ } catch (IOException | SecurityException e) {
+ nativeClose(this.nativeHandle);
+ IoUtils.closeQuietly(this.raf);
+ throw e;
+ }
+
+ guard.open("close");
+ }
+
+ public StrictJarManifest getManifest() {
+ return manifest;
+ }
+
+ public Iterator<ZipEntry> iterator() throws IOException {
+ return new EntryIterator(nativeHandle, "");
+ }
+
+ public ZipEntry findEntry(String name) {
+ return nativeFindEntry(nativeHandle, name);
+ }
+
+ /**
+ * Return all certificate chains 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[][] getCertificateChains(ZipEntry ze) {
+ if (isSigned) {
+ return verifier.getCertificateChains(ze.getName());
+ }
+
+ return null;
+ }
+
+ /**
+ * 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.
+ *
+ * @deprecated Switch callers to use getCertificateChains instead
+ */
+ @Deprecated
+ public Certificate[] getCertificates(ZipEntry ze) {
+ if (isSigned) {
+ Certificate[][] certChains = verifier.getCertificateChains(ze.getName());
+
+ // Measure number of certs.
+ int count = 0;
+ for (Certificate[] chain : certChains) {
+ count += chain.length;
+ }
+
+ // Create new array and copy all the certs into it.
+ Certificate[] certs = new Certificate[count];
+ int i = 0;
+ for (Certificate[] chain : certChains) {
+ System.arraycopy(chain, 0, certs, i, chain.length);
+ i += chain.length;
+ }
+
+ return certs;
+ }
+
+ return null;
+ }
+
+ public InputStream getInputStream(ZipEntry ze) {
+ final InputStream is = getZipInputStream(ze);
+
+ if (isSigned) {
+ StrictJarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());
+ if (entry == null) {
+ return is;
+ }
+
+ return new 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 RAFStream(raf, ze.getDataOffset(),
+ ze.getDataOffset() + ze.getSize());
+ } else {
+ final RAFStream wrapped = new RAFStream(
+ raf, ze.getDataOffset(), ze.getDataOffset() + ze.getCompressedSize());
+
+ int bufSize = Math.max(1024, (int) Math.min(ze.getSize(), 65535L));
+ return new 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;
+ }
+
+ static final class JarFileInputStream extends FilterInputStream {
+ private final StrictJarVerifier.VerifierEntry entry;
+
+ private long count;
+ private boolean done = false;
+
+ JarFileInputStream(InputStream is, long size, StrictJarVerifier.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);
+ }
+ }
+
+ /** @hide */
+ public static class ZipInflaterInputStream extends InflaterInputStream {
+ private final ZipEntry entry;
+ private long bytesRead = 0;
+
+ public ZipInflaterInputStream(InputStream is, Inflater inf, int bsize, ZipEntry entry) {
+ super(is, inf, bsize);
+ this.entry = entry;
+ }
+
+ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ final int i;
+ try {
+ i = super.read(buffer, byteOffset, byteCount);
+ } catch (IOException e) {
+ throw new IOException("Error reading data for " + entry.getName() + " near offset "
+ + bytesRead, e);
+ }
+ if (i == -1) {
+ if (entry.getSize() != bytesRead) {
+ throw new IOException("Size mismatch on inflated file: " + bytesRead + " vs "
+ + entry.getSize());
+ }
+ } else {
+ bytesRead += i;
+ }
+ return i;
+ }
+
+ @Override public int available() throws IOException {
+ if (closed) {
+ // Our superclass will throw an exception, but there's a jtreg test that
+ // explicitly checks that the InputStream returned from ZipFile.getInputStream
+ // returns 0 even when closed.
+ return 0;
+ }
+ return super.available() == 0 ? 0 : (int) (entry.getSize() - bytesRead);
+ }
+ }
+
+ /**
+ * Wrap a stream around a RandomAccessFile. The RandomAccessFile is shared
+ * among all streams returned by getInputStream(), so we have to synchronize
+ * access to it. (We can optimize this by adding buffering here to reduce
+ * collisions.)
+ *
+ * <p>We could support mark/reset, but we don't currently need them.
+ *
+ * @hide
+ */
+ public static class RAFStream extends InputStream {
+ private final RandomAccessFile sharedRaf;
+ private long endOffset;
+ private long offset;
+
+
+ public RAFStream(RandomAccessFile raf, long initialOffset, long endOffset) {
+ sharedRaf = raf;
+ offset = initialOffset;
+ this.endOffset = endOffset;
+ }
+
+ public RAFStream(RandomAccessFile raf, long initialOffset) throws IOException {
+ this(raf, initialOffset, raf.length());
+ }
+
+ @Override public int available() throws IOException {
+ return (offset < endOffset ? 1 : 0);
+ }
+
+ @Override public int read() throws IOException {
+ return Streams.readSingleByte(this);
+ }
+
+ @Override public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+ synchronized (sharedRaf) {
+ final long length = endOffset - offset;
+ if (byteCount > length) {
+ byteCount = (int) length;
+ }
+ sharedRaf.seek(offset);
+ int count = sharedRaf.read(buffer, byteOffset, byteCount);
+ if (count > 0) {
+ offset += count;
+ return count;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ @Override public long skip(long byteCount) throws IOException {
+ if (byteCount > endOffset - offset) {
+ byteCount = endOffset - offset;
+ }
+ offset += byteCount;
+ return byteCount;
+ }
+ }
+
+
+ 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);
+}
--- /dev/null
+/*
+ * 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.util.jar;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import libcore.io.Streams;
+
+/**
+ * The {@code StrictJarManifest} class is used to obtain attribute information for a
+ * {@code StrictJarFile} and its entries.
+ *
+ * @hide
+ */
+public class StrictJarManifest implements Cloneable {
+ static final int LINE_LENGTH_LIMIT = 72;
+
+ private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
+
+ private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
+
+ private final Attributes mainAttributes;
+ private final HashMap<String, Attributes> entries;
+
+ static final class Chunk {
+ final int start;
+ final int end;
+
+ Chunk(int start, int end) {
+ this.start = start;
+ this.end = end;
+ }
+ }
+
+ private HashMap<String, Chunk> chunks;
+
+ /**
+ * The end of the main attributes section in the manifest is needed in
+ * verification.
+ */
+ private int mainEnd;
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance.
+ */
+ public StrictJarManifest() {
+ entries = new HashMap<String, Attributes>();
+ mainAttributes = new Attributes();
+ }
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance using the attributes obtained
+ * from the input stream.
+ *
+ * @param is
+ * {@code InputStream} to parse for attributes.
+ * @throws IOException
+ * if an IO error occurs while creating this {@code StrictJarManifest}
+ */
+ public StrictJarManifest(InputStream is) throws IOException {
+ this();
+ read(Streams.readFully(is));
+ }
+
+ /**
+ * Creates a new {@code StrictJarManifest} instance. The new instance will have the
+ * same attributes as those found in the parameter {@code StrictJarManifest}.
+ *
+ * @param man
+ * {@code StrictJarManifest} instance to obtain attributes from.
+ */
+ @SuppressWarnings("unchecked")
+ public StrictJarManifest(StrictJarManifest man) {
+ mainAttributes = (Attributes) man.mainAttributes.clone();
+ entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
+ .getEntries()).clone();
+ }
+
+ StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
+ this();
+ if (readChunks) {
+ chunks = new HashMap<String, Chunk>();
+ }
+ read(manifestBytes);
+ }
+
+ /**
+ * Resets the both the main attributes as well as the entry attributes
+ * associated with this {@code StrictJarManifest}.
+ */
+ public void clear() {
+ entries.clear();
+ mainAttributes.clear();
+ }
+
+ /**
+ * Returns the {@code Attributes} associated with the parameter entry
+ * {@code name}.
+ *
+ * @param name
+ * the name of the entry to obtain {@code Attributes} from.
+ * @return the Attributes for the entry or {@code null} if the entry does
+ * not exist.
+ */
+ public Attributes getAttributes(String name) {
+ return getEntries().get(name);
+ }
+
+ /**
+ * Returns a map containing the {@code Attributes} for each entry in the
+ * {@code StrictJarManifest}.
+ *
+ * @return the map of entry attributes.
+ */
+ public Map<String, Attributes> getEntries() {
+ return entries;
+ }
+
+ /**
+ * Returns the main {@code Attributes} of the {@code JarFile}.
+ *
+ * @return main {@code Attributes} associated with the source {@code
+ * JarFile}.
+ */
+ public Attributes getMainAttributes() {
+ return mainAttributes;
+ }
+
+ /**
+ * Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
+ * will equal the {@code StrictJarManifest} from which it was cloned.
+ *
+ * @return a copy of this instance.
+ */
+ @Override
+ public Object clone() {
+ return new StrictJarManifest(this);
+ }
+
+ /**
+ * Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
+ * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
+ * calling this method, or no attributes will be written.
+ *
+ * @throws IOException
+ * If an error occurs writing the {@code StrictJarManifest}.
+ */
+ public void write(OutputStream os) throws IOException {
+ write(this, os);
+ }
+
+ /**
+ * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
+ *
+ * @param is
+ * The {@code InputStream} to read from.
+ * @throws IOException
+ * If an error occurs reading the manifest.
+ */
+ public void read(InputStream is) throws IOException {
+ read(Streams.readFullyNoClose(is));
+ }
+
+ private void read(byte[] buf) throws IOException {
+ if (buf.length == 0) {
+ return;
+ }
+
+ StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
+ mainEnd = im.getEndOfMainSection();
+ im.readEntries(entries, chunks);
+ }
+
+ /**
+ * Returns the hash code for this instance.
+ *
+ * @return this {@code StrictJarManifest}'s hashCode.
+ */
+ @Override
+ public int hashCode() {
+ return mainAttributes.hashCode() ^ getEntries().hashCode();
+ }
+
+ /**
+ * Determines if the receiver is equal to the parameter object. Two {@code
+ * StrictJarManifest}s are equal if they have identical main attributes as well as
+ * identical entry attributes.
+ *
+ * @param o
+ * the object to compare against.
+ * @return {@code true} if the manifests are equal, {@code false} otherwise
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (o == null) {
+ return false;
+ }
+ if (o.getClass() != this.getClass()) {
+ return false;
+ }
+ if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
+ return false;
+ }
+ return getEntries().equals(((StrictJarManifest) o).getEntries());
+ }
+
+ Chunk getChunk(String name) {
+ return chunks.get(name);
+ }
+
+ void removeChunks() {
+ chunks = null;
+ }
+
+ int getMainAttributesEnd() {
+ return mainEnd;
+ }
+
+ /**
+ * Writes out the attribute information of the specified manifest to the
+ * specified {@code OutputStream}
+ *
+ * @param manifest
+ * the manifest to write out.
+ * @param out
+ * The {@code OutputStream} to write to.
+ * @throws IOException
+ * If an error occurs writing the {@code StrictJarManifest}.
+ */
+ static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
+ CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
+
+ Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
+ String version = manifest.mainAttributes.getValue(versionName);
+ if (version == null) {
+ versionName = Attributes.Name.SIGNATURE_VERSION;
+ version = manifest.mainAttributes.getValue(versionName);
+ }
+ if (version != null) {
+ writeEntry(out, versionName, version, encoder, buffer);
+ Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
+ while (entries.hasNext()) {
+ Attributes.Name name = (Attributes.Name) entries.next();
+ if (!name.equals(versionName)) {
+ writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
+ }
+ }
+ }
+ out.write(LINE_SEPARATOR);
+ Iterator<String> i = manifest.getEntries().keySet().iterator();
+ while (i.hasNext()) {
+ String key = i.next();
+ writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
+ Attributes attributes = manifest.entries.get(key);
+ Iterator<?> entries = attributes.keySet().iterator();
+ while (entries.hasNext()) {
+ Attributes.Name name = (Attributes.Name) entries.next();
+ writeEntry(out, name, attributes.getValue(name), encoder, buffer);
+ }
+ out.write(LINE_SEPARATOR);
+ }
+ }
+
+ private static void writeEntry(OutputStream os, Attributes.Name name,
+ String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
+ String nameString = name.toString();
+ os.write(nameString.getBytes(StandardCharsets.US_ASCII));
+ os.write(VALUE_SEPARATOR);
+
+ encoder.reset();
+ bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
+
+ CharBuffer cBuf = CharBuffer.wrap(value);
+
+ while (true) {
+ CoderResult r = encoder.encode(cBuf, bBuf, true);
+ if (CoderResult.UNDERFLOW == r) {
+ r = encoder.flush(bBuf);
+ }
+ os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
+ os.write(LINE_SEPARATOR);
+ if (CoderResult.UNDERFLOW == r) {
+ break;
+ }
+ os.write(' ');
+ bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.util.jar;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.jar.Attributes;
+
+/**
+ * Reads a JAR file manifest. The specification is here:
+ * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
+ */
+class StrictJarManifestReader {
+ // There are relatively few unique attribute names,
+ // but a manifest might have thousands of entries.
+ private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
+
+ private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
+
+ private final byte[] buf;
+
+ private final int endOfMainSection;
+
+ private int pos;
+
+ private Attributes.Name name;
+
+ private String value;
+
+ private int consecutiveLineBreaks = 0;
+
+ public StrictJarManifestReader(byte[] buf, Attributes main) throws IOException {
+ this.buf = buf;
+ while (readHeader()) {
+ main.put(name, value);
+ }
+ this.endOfMainSection = pos;
+ }
+
+ public void readEntries(Map<String, Attributes> entries, Map<String, StrictJarManifest.Chunk> chunks) throws IOException {
+ int mark = pos;
+ while (readHeader()) {
+ if (!Attributes.Name.NAME.equals(name)) {
+ throw new IOException("Entry is not named");
+ }
+ String entryNameValue = value;
+
+ Attributes entry = entries.get(entryNameValue);
+ if (entry == null) {
+ entry = new Attributes(12);
+ }
+
+ while (readHeader()) {
+ entry.put(name, value);
+ }
+
+ if (chunks != null) {
+ if (chunks.get(entryNameValue) != null) {
+ // TODO A bug: there might be several verification chunks for
+ // the same name. I believe they should be used to update
+ // signature in order of appearance; there are two ways to fix
+ // this: either use a list of chunks, or decide on used
+ // signature algorithm in advance and reread the chunks while
+ // updating the signature; for now a defensive error is thrown
+ throw new IOException("A jar verifier does not support more than one entry with the same name");
+ }
+ chunks.put(entryNameValue, new StrictJarManifest.Chunk(mark, pos));
+ mark = pos;
+ }
+
+ entries.put(entryNameValue, entry);
+ }
+ }
+
+ public int getEndOfMainSection() {
+ return endOfMainSection;
+ }
+
+ /**
+ * Read a single line from the manifest buffer.
+ */
+ private boolean readHeader() throws IOException {
+ if (consecutiveLineBreaks > 1) {
+ // break a section on an empty line
+ consecutiveLineBreaks = 0;
+ return false;
+ }
+ readName();
+ consecutiveLineBreaks = 0;
+ readValue();
+ // if the last line break is missed, the line
+ // is ignored by the reference implementation
+ return consecutiveLineBreaks > 0;
+ }
+
+ private void readName() throws IOException {
+ int mark = pos;
+
+ while (pos < buf.length) {
+ if (buf[pos++] != ':') {
+ continue;
+ }
+
+ String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
+
+ if (buf[pos++] != ' ') {
+ throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
+ }
+
+ try {
+ name = attributeNameCache.get(nameString);
+ if (name == null) {
+ name = new Attributes.Name(nameString);
+ attributeNameCache.put(nameString, name);
+ }
+ } catch (IllegalArgumentException e) {
+ // new Attributes.Name() throws IllegalArgumentException but we declare IOException
+ throw new IOException(e.getMessage());
+ }
+ return;
+ }
+ }
+
+ private void readValue() throws IOException {
+ boolean lastCr = false;
+ int mark = pos;
+ int last = pos;
+ valueBuffer.reset();
+ while (pos < buf.length) {
+ byte next = buf[pos++];
+ switch (next) {
+ case 0:
+ throw new IOException("NUL character in a manifest");
+ case '\n':
+ if (lastCr) {
+ lastCr = false;
+ } else {
+ consecutiveLineBreaks++;
+ }
+ continue;
+ case '\r':
+ lastCr = true;
+ consecutiveLineBreaks++;
+ continue;
+ case ' ':
+ if (consecutiveLineBreaks == 1) {
+ valueBuffer.write(buf, mark, last - mark);
+ mark = pos;
+ consecutiveLineBreaks = 0;
+ continue;
+ }
+ }
+
+ if (consecutiveLineBreaks >= 1) {
+ pos--;
+ break;
+ }
+ last = pos;
+ }
+
+ valueBuffer.write(buf, mark, last - mark);
+ // A bit frustrating that that Charset.forName will be called
+ // again.
+ value = valueBuffer.toString(StandardCharsets.UTF_8.name());
+ }
+}
--- /dev/null
+/*
+ * 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.util.jar;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import libcore.io.Base64;
+import sun.security.jca.Providers;
+import sun.security.pkcs.PKCS7;
+
+/**
+ * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage
+ * the verification of signed JARs. {@code JarFile} and {@code JarInputStream}
+ * objects are expected to have a {@code JarVerifier} instance member which
+ * can be used to carry out the tasks associated with verifying a signed JAR.
+ * These tasks would typically include:
+ * <ul>
+ * <li>verification of all signed signature files
+ * <li>confirmation that all signed data was signed only by the party or parties
+ * specified in the signature block data
+ * <li>verification that the contents of all signature files (i.e. {@code .SF}
+ * files) agree with the JAR entries information found in the JAR manifest.
+ * </ul>
+ */
+class StrictJarVerifier {
+ /**
+ * List of accepted digest algorithms. This list is in order from most
+ * preferred to least preferred.
+ */
+ private static final String[] DIGEST_ALGORITHMS = new String[] {
+ "SHA-512",
+ "SHA-384",
+ "SHA-256",
+ "SHA1",
+ };
+
+ private final String jarName;
+ private final StrictJarManifest manifest;
+ private final HashMap<String, byte[]> metaEntries;
+ private final int mainAttributesEnd;
+
+ private final Hashtable<String, HashMap<String, Attributes>> signatures =
+ new Hashtable<String, HashMap<String, Attributes>>(5);
+
+ private final Hashtable<String, Certificate[]> certificates =
+ new Hashtable<String, Certificate[]>(5);
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries =
+ new Hashtable<String, Certificate[][]>();
+
+ /**
+ * Stores and a hash and a message digest and verifies that massage digest
+ * matches the hash.
+ */
+ static class VerifierEntry extends OutputStream {
+
+ private final String name;
+
+ private final MessageDigest digest;
+
+ private final byte[] hash;
+
+ private final Certificate[][] certChains;
+
+ private final Hashtable<String, Certificate[][]> verifiedEntries;
+
+ VerifierEntry(String name, MessageDigest digest, byte[] hash,
+ Certificate[][] certChains, Hashtable<String, Certificate[][]> verifedEntries) {
+ this.name = name;
+ this.digest = digest;
+ this.hash = hash;
+ this.certChains = certChains;
+ this.verifiedEntries = verifedEntries;
+ }
+
+ /**
+ * Updates a digest with one byte.
+ */
+ @Override
+ public void write(int value) {
+ digest.update((byte) value);
+ }
+
+ /**
+ * Updates a digest with byte array.
+ */
+ @Override
+ public void write(byte[] buf, int off, int nbytes) {
+ digest.update(buf, off, nbytes);
+ }
+
+ /**
+ * Verifies that the digests stored in the manifest match the decrypted
+ * digests from the .SF file. This indicates the validity of the
+ * signing, not the integrity of the file, as its digest must be
+ * calculated and verified when its contents are read.
+ *
+ * @throws SecurityException
+ * if the digest value stored in the manifest does <i>not</i>
+ * agree with the decrypted digest as recovered from the
+ * <code>.SF</code> file.
+ */
+ void verify() {
+ byte[] d = digest.digest();
+ if (!MessageDigest.isEqual(d, Base64.decode(hash))) {
+ throw invalidDigest(JarFile.MANIFEST_NAME, name, name);
+ }
+ verifiedEntries.put(name, certChains);
+ }
+ }
+
+ private static SecurityException invalidDigest(String signatureFile, String name,
+ String jarName) {
+ throw new SecurityException(signatureFile + " has invalid digest for " + name +
+ " in " + jarName);
+ }
+
+ private static SecurityException failedVerification(String jarName, String signatureFile) {
+ throw new SecurityException(jarName + " failed verification of " + signatureFile);
+ }
+
+ private static SecurityException failedVerification(String jarName, String signatureFile,
+ Throwable e) {
+ throw new SecurityException(jarName + " failed verification of " + signatureFile, e);
+ }
+
+
+ /**
+ * Constructs and returns a new instance of {@code JarVerifier}.
+ *
+ * @param name
+ * the name of the JAR file being verified.
+ */
+ StrictJarVerifier(String name, StrictJarManifest manifest,
+ HashMap<String, byte[]> metaEntries) {
+ jarName = name;
+ this.manifest = manifest;
+ this.metaEntries = metaEntries;
+ this.mainAttributesEnd = manifest.getMainAttributesEnd();
+ }
+
+ /**
+ * Invoked for each new JAR entry read operation from the input
+ * stream. This method constructs and returns a new {@link VerifierEntry}
+ * which contains the certificates used to sign the entry and its hash value
+ * as specified in the JAR MANIFEST format.
+ *
+ * @param name
+ * the name of an entry in a JAR file which is <b>not</b> in the
+ * {@code META-INF} directory.
+ * @return a new instance of {@link VerifierEntry} which can be used by
+ * callers as an {@link OutputStream}.
+ */
+ VerifierEntry initEntry(String name) {
+ // If no manifest is present by the time an entry is found,
+ // verification cannot occur. If no signature files have
+ // been found, do not verify.
+ if (manifest == null || signatures.isEmpty()) {
+ return null;
+ }
+
+ Attributes attributes = manifest.getAttributes(name);
+ // entry has no digest
+ if (attributes == null) {
+ return null;
+ }
+
+ ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();
+ Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, HashMap<String, Attributes>> entry = it.next();
+ HashMap<String, Attributes> hm = entry.getValue();
+ if (hm.get(name) != null) {
+ // Found an entry for entry name in .SF file
+ String signatureFile = entry.getKey();
+ Certificate[] certChain = certificates.get(signatureFile);
+ if (certChain != null) {
+ certChains.add(certChain);
+ }
+ }
+ }
+
+ // entry is not signed
+ if (certChains.isEmpty()) {
+ return null;
+ }
+ Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);
+
+ for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+ final String algorithm = DIGEST_ALGORITHMS[i];
+ final String hash = attributes.getValue(algorithm + "-Digest");
+ if (hash == null) {
+ continue;
+ }
+ byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+
+ try {
+ return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,
+ certChainsArray, verifiedEntries);
+ } catch (NoSuchAlgorithmException ignored) {
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Add a new meta entry to the internal collection of data held on each JAR
+ * entry in the {@code META-INF} directory including the manifest
+ * file itself. Files associated with the signing of a JAR would also be
+ * added to this collection.
+ *
+ * @param name
+ * the name of the file located in the {@code META-INF}
+ * directory.
+ * @param buf
+ * the file bytes for the file called {@code name}.
+ * @see #removeMetaEntries()
+ */
+ void addMetaEntry(String name, byte[] buf) {
+ metaEntries.put(name.toUpperCase(Locale.US), buf);
+ }
+
+ /**
+ * If the associated JAR file is signed, check on the validity of all of the
+ * known signatures.
+ *
+ * @return {@code true} if the associated JAR is signed and an internal
+ * check verifies the validity of the signature(s). {@code false} if
+ * the associated JAR file has no entries at all in its {@code
+ * META-INF} directory. This situation is indicative of an invalid
+ * JAR file.
+ * <p>
+ * Will also return {@code true} if the JAR file is <i>not</i>
+ * signed.
+ * @throws SecurityException
+ * if the JAR file is signed and it is determined that a
+ * signature block file contains an invalid signature for the
+ * corresponding signature file.
+ */
+ synchronized boolean readCertificates() {
+ if (metaEntries.isEmpty()) {
+ return false;
+ }
+
+ Iterator<String> it = metaEntries.keySet().iterator();
+ while (it.hasNext()) {
+ String key = it.next();
+ if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
+ verifyCertificate(key);
+ it.remove();
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Verifies that the signature computed from {@code sfBytes} matches
+ * that specified in {@code blockBytes} (which is a PKCS7 block). Returns
+ * certificates listed in the PKCS7 block. Throws a {@code GeneralSecurityException}
+ * if something goes wrong during verification.
+ */
+ static Certificate[] verifyBytes(byte[] blockBytes, byte[] sfBytes)
+ throws GeneralSecurityException {
+
+ Object obj = null;
+ try {
+
+ obj = Providers.startJarVerification();
+ PKCS7 block = new PKCS7(blockBytes);
+ if (block.verify(sfBytes) == null) {
+ throw new GeneralSecurityException("Failed to verify signature");
+ }
+ X509Certificate[] blockCerts = block.getCertificates();
+ Certificate[] signerCertChain = null;
+ if (blockCerts != null) {
+ signerCertChain = new Certificate[blockCerts.length];
+ for (int i = 0; i < blockCerts.length; ++i) {
+ signerCertChain[i] = blockCerts[i];
+ }
+ }
+ return signerCertChain;
+ } catch (IOException e) {
+ throw new GeneralSecurityException("IO exception verifying jar cert", e);
+ } finally {
+ Providers.stopJarVerification(obj);
+ }
+ }
+
+ /**
+ * @param certFile
+ */
+ private void verifyCertificate(String certFile) {
+ // Found Digital Sig, .SF should already have been read
+ String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
+ byte[] sfBytes = metaEntries.get(signatureFile);
+ if (sfBytes == null) {
+ return;
+ }
+
+ byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);
+ // Manifest entry is required for any verifications.
+ if (manifestBytes == null) {
+ return;
+ }
+
+ byte[] sBlockBytes = metaEntries.get(certFile);
+ try {
+ Certificate[] signerCertChain = verifyBytes(sBlockBytes, sfBytes);
+ if (signerCertChain != null) {
+ certificates.put(signatureFile, signerCertChain);
+ }
+ } catch (GeneralSecurityException e) {
+ throw failedVerification(jarName, signatureFile, e);
+ }
+
+ // Verify manifest hash in .sf file
+ Attributes attributes = new Attributes();
+ HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
+ try {
+ StrictJarManifestReader im = new StrictJarManifestReader(sfBytes, attributes);
+ im.readEntries(entries, null);
+ } catch (IOException e) {
+ return;
+ }
+
+ // Do we actually have any signatures to look at?
+ if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {
+ return;
+ }
+
+ boolean createdBySigntool = false;
+ String createdBy = attributes.getValue("Created-By");
+ if (createdBy != null) {
+ createdBySigntool = createdBy.indexOf("signtool") != -1;
+ }
+
+ // Use .SF to verify the mainAttributes of the manifest
+ // If there is no -Digest-Manifest-Main-Attributes entry in .SF
+ // file, such as those created before java 1.5, then we ignore
+ // such verification.
+ if (mainAttributesEnd > 0 && !createdBySigntool) {
+ String digestAttribute = "-Digest-Manifest-Main-Attributes";
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {
+ throw failedVerification(jarName, signatureFile);
+ }
+ }
+
+ // Use .SF to verify the whole manifest.
+ String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
+ if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {
+ Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, Attributes> entry = it.next();
+ StrictJarManifest.Chunk chunk = manifest.getChunk(entry.getKey());
+ if (chunk == null) {
+ return;
+ }
+ if (!verify(entry.getValue(), "-Digest", manifestBytes,
+ chunk.start, chunk.end, createdBySigntool, false)) {
+ throw invalidDigest(signatureFile, entry.getKey(), jarName);
+ }
+ }
+ }
+ metaEntries.put(signatureFile, null);
+ signatures.put(signatureFile, entries);
+ }
+
+ /**
+ * Returns a <code>boolean</code> indication of whether or not the
+ * associated jar file is signed.
+ *
+ * @return {@code true} if the JAR is signed, {@code false}
+ * otherwise.
+ */
+ boolean isSignedJar() {
+ return certificates.size() > 0;
+ }
+
+ private boolean verify(Attributes attributes, String entry, byte[] data,
+ int start, int end, boolean ignoreSecondEndline, boolean ignorable) {
+ for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {
+ String algorithm = DIGEST_ALGORITHMS[i];
+ String hash = attributes.getValue(algorithm + entry);
+ if (hash == null) {
+ continue;
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance(algorithm);
+ } catch (NoSuchAlgorithmException e) {
+ continue;
+ }
+ if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {
+ md.update(data, start, end - 1 - start);
+ } else {
+ md.update(data, start, end - start);
+ }
+ byte[] b = md.digest();
+ byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);
+ return MessageDigest.isEqual(b, Base64.decode(hashBytes));
+ }
+ return ignorable;
+ }
+
+ /**
+ * Returns all of the {@link java.security.cert.Certificate} chains that
+ * were used to verify the signature on the JAR entry called
+ * {@code name}. Callers must not modify the returned arrays.
+ *
+ * @param name
+ * the name of a JAR entry.
+ * @return an array of {@link java.security.cert.Certificate} chains.
+ */
+ Certificate[][] getCertificateChains(String name) {
+ return verifiedEntries.get(name);
+ }
+
+ /**
+ * Remove all entries from the internal collection of data held about each
+ * JAR entry in the {@code META-INF} directory.
+ */
+ void removeMetaEntries() {
+ metaEntries.clear();
+ }
+}
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
+ android_util_jar_StrictJarFile.cpp \
android_graphics_Canvas.cpp \
android_graphics_Picture.cpp \
android/graphics/AutoDecodeCancel.cpp \
extern int register_android_app_ActivityThread(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
extern int register_android_media_RemoteDisplay(JNIEnv *env);
+extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_InputEventReceiver(JNIEnv* env);
REG_JNI(register_android_app_backup_FullBackup),
REG_JNI(register_android_app_ActivityThread),
REG_JNI(register_android_app_NativeActivity),
+ REG_JNI(register_android_util_jar_StrictJarFile),
REG_JNI(register_android_view_InputChannel),
REG_JNI(register_android_view_InputEventReceiver),
REG_JNI(register_android_view_InputEventSender),
REG_JNI(register_android_animation_PropertyValuesHolder),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
REG_JNI(register_com_android_internal_net_NetworkStatsFactory),
+
+
};
/*
--- /dev/null
+/*
+ * 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 <memory>
+#include <string>
+
+#include "JNIHelp.h"
+#include "JniConstants.h"
+#include "ScopedLocalRef.h"
+#include "ScopedUtfChars.h"
+#include "jni.h"
+#include "ziparchive/zip_archive.h"
+#include "cutils/log.h"
+
+namespace android {
+
+// The method ID for ZipEntry.<init>(String,String,JJJIII[BJJ)
+static jmethodID zipEntryCtor;
+
+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) {
+ return env->NewObject(JniConstants::zipEntryClass,
+ 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
+ NULL, // byte[] extra
+ 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) {
+ CloseArchive(handle);
+ throwIoException(env, error);
+ return static_cast<jlong>(-1);
+ }
+
+ return reinterpret_cast<jlong>(handle);
+}
+
+class IterationHandle {
+ public:
+ IterationHandle() :
+ cookie_(NULL) {
+ }
+
+ void** CookieAddress() {
+ return &cookie_;
+ }
+
+ ~IterationHandle() {
+ EndIteration(cookie_);
+ }
+
+ private:
+ void* cookie_;
+};
+
+
+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();
+ int32_t error = 0;
+ if (prefixChars.size() == 0) {
+ error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ handle->CookieAddress(), NULL, NULL);
+ } else {
+ ZipString entry_name(prefixChars.c_str());
+ error = StartIteration(reinterpret_cast<ZipArchiveHandle>(nativeHandle),
+ handle->CookieAddress(), &entry_name, NULL);
+ }
+
+ 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;
+ ZipString entryName;
+
+ IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
+ const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
+ if (error) {
+ delete handle;
+ return NULL;
+ }
+
+ std::unique_ptr<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());
+}
+
+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),
+ ZipString(entryNameChars.c_str()), &data);
+ if (error) {
+ return NULL;
+ }
+
+ return newZipEntry(env, data, entryName);
+}
+
+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_android_util_jar_StrictJarFile(JNIEnv* env) {
+ jniRegisterNativeMethods(env, "android/util/jar/StrictJarFile", gMethods, NELEM(gMethods));
+
+ zipEntryCtor = env->GetMethodID(JniConstants::zipEntryClass, "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;JJJII[BJ)V");
+ LOG_ALWAYS_FATAL_IF(zipEntryCtor == NULL, "Unable to find ZipEntry.<init>");
+}
+
+}; // namespace android
android.util.SuperNotCalledException
android.util.TypedValue
android.util.Xml
+android.util.jar.StrictJarFile
android.view.AbsSavedState
android.view.AbsSavedState$1
android.view.AbsSavedState$2
java.util.jar.JarEntry
java.util.jar.JarFile
java.util.jar.JarFile$JarFileEnumerator
-java.util.jar.Manifest
-java.util.jar.ManifestReader
-java.util.jar.StrictJarFile
java.util.logging.ConsoleHandler
java.util.logging.ErrorManager
java.util.logging.Filter
org.xmlpull.v1.XmlPullParser
org.xmlpull.v1.XmlPullParserException
org.xmlpull.v1.XmlSerializer
+<<<<<<< HEAD
sun.misc.Unsafe
+=======
+<<<<<<< HEAD
+=======
+sun.misc.Unsafe
+>>>>>>> 631d21f... Move StrictJarFile from libcore to framework
+>>>>>>> 43ea2cc... DO NOT MERGE Move StrictJarFile from libcore to framework