1ccf23a609c01ce08a79f56353a1750f1067749c6cushon/* 2ccf23a609c01ce08a79f56353a1750f1067749c6cushon * Copyright 2017 Google Inc. All Rights Reserved. 3ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 4ccf23a609c01ce08a79f56353a1750f1067749c6cushon * Licensed under the Apache License, Version 2.0 (the "License"); 5ccf23a609c01ce08a79f56353a1750f1067749c6cushon * you may not use this file except in compliance with the License. 6ccf23a609c01ce08a79f56353a1750f1067749c6cushon * You may obtain a copy of the License at 7ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 8ccf23a609c01ce08a79f56353a1750f1067749c6cushon * http://www.apache.org/licenses/LICENSE-2.0 9ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 10ccf23a609c01ce08a79f56353a1750f1067749c6cushon * Unless required by applicable law or agreed to in writing, software 11ccf23a609c01ce08a79f56353a1750f1067749c6cushon * distributed under the License is distributed on an "AS IS" BASIS, 12ccf23a609c01ce08a79f56353a1750f1067749c6cushon * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13ccf23a609c01ce08a79f56353a1750f1067749c6cushon * See the License for the specific language governing permissions and 14ccf23a609c01ce08a79f56353a1750f1067749c6cushon * limitations under the License. 15ccf23a609c01ce08a79f56353a1750f1067749c6cushon */ 16ccf23a609c01ce08a79f56353a1750f1067749c6cushon 17ccf23a609c01ce08a79f56353a1750f1067749c6cushonpackage com.google.turbine.zip; 18ccf23a609c01ce08a79f56353a1750f1067749c6cushon 19ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport static java.nio.charset.StandardCharsets.UTF_8; 20ccf23a609c01ce08a79f56353a1750f1067749c6cushon 21ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport com.google.common.io.ByteStreams; 22ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport com.google.common.primitives.UnsignedInts; 23ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.io.ByteArrayInputStream; 24ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.io.Closeable; 25ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.io.IOError; 26ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.io.IOException; 27ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.ByteBuffer; 28ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.ByteOrder; 29ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.MappedByteBuffer; 30ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.channels.FileChannel; 31ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.channels.FileChannel.MapMode; 32ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.charset.CharacterCodingException; 33ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.charset.CharsetDecoder; 34ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.file.Path; 35ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.file.StandardOpenOption; 36ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.Iterator; 37ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.zip.Inflater; 38ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.zip.InflaterInputStream; 39ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.zip.ZipException; 40ccf23a609c01ce08a79f56353a1750f1067749c6cushon 41ccf23a609c01ce08a79f56353a1750f1067749c6cushon/** 42ccf23a609c01ce08a79f56353a1750f1067749c6cushon * A fast, minimal, and somewhat garbage zip implementation. This exists because graal <a 43ccf23a609c01ce08a79f56353a1750f1067749c6cushon * href="http://mail.openjdk.java.net/pipermail/graal-dev/2017-August/005039.html">doesn't yet 44ccf23a609c01ce08a79f56353a1750f1067749c6cushon * support</a> {@link java.util.zip.ZipFile}, and {@link java.util.zip.ZipInputStream} doesn't have 45ccf23a609c01ce08a79f56353a1750f1067749c6cushon * the performance we'd like (*). If you're reading this, you almost certainly want {@code ZipFile} 46ccf23a609c01ce08a79f56353a1750f1067749c6cushon * instead. 47ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 48ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <p>If you're reading this because you're fixing a bug, sorry. 49ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 50ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <p>(*) A benchmark that iterates over all of the entries in rt.jar takes 6.97ms to run with this 51ccf23a609c01ce08a79f56353a1750f1067749c6cushon * implementation and 202.99ms with ZipInputStream. (Those are averages across 100 reps, and I 52ccf23a609c01ce08a79f56353a1750f1067749c6cushon * verified they're doing the same work.) This is likely largely due to ZipInputStream reading the 53ccf23a609c01ce08a79f56353a1750f1067749c6cushon * entire file from the beginning to scan the local headers, whereas this implementation (and 54ccf23a609c01ce08a79f56353a1750f1067749c6cushon * ZipFile) only read the central directory. Iterating over the entries (but not reading the data) 55ccf23a609c01ce08a79f56353a1750f1067749c6cushon * is an interesting benchmark because we typically only read ~10% of the compile-time classpath, so 56ccf23a609c01ce08a79f56353a1750f1067749c6cushon * most time is spent just scanning entry names. And rt.jar is an interesting test case because 57ccf23a609c01ce08a79f56353a1750f1067749c6cushon * every compilation has to read it, and it dominates the size of the classpath for small 58ccf23a609c01ce08a79f56353a1750f1067749c6cushon * compilations. 59ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 60ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <p>Implementation notes: 61ccf23a609c01ce08a79f56353a1750f1067749c6cushon * 62ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <ul> 63ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <li>Leading garbage may be supported, since the archive is read backwards using the central 64ccf23a609c01ce08a79f56353a1750f1067749c6cushon * directory. Archives modified with zip -A may not be supported. Trailing garbage is not 65ccf23a609c01ce08a79f56353a1750f1067749c6cushon * supported. 66ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <li>UTF-8 is the only supported encoding. 67ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <li>STORED and DEFLATE are the only supported compression methods. 6886665ea565aca9132d09988cd8de50920c814f47cushon * <li>zip64 extensible data sectors are not supported. 69ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <li>Zip files larger than Integer.MAX_VALUE bytes are not supported. 70ccf23a609c01ce08a79f56353a1750f1067749c6cushon * <li>The only supported ZIP64 field is ENDTOT. This implementation assumes that the ZIP64 end 71ccf23a609c01ce08a79f56353a1750f1067749c6cushon * header is present only if ENDTOT in EOCD header is 0xFFFF. 72ccf23a609c01ce08a79f56353a1750f1067749c6cushon * </ul> 73ccf23a609c01ce08a79f56353a1750f1067749c6cushon */ 74ccf23a609c01ce08a79f56353a1750f1067749c6cushonpublic class Zip { 75ccf23a609c01ce08a79f56353a1750f1067749c6cushon 76ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ZIP64_ENDSIG = 0x06064b50; 77ccf23a609c01ce08a79f56353a1750f1067749c6cushon 78ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int LOCHDR = 30; // LOC header size 79ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENHDR = 46; // CEN header size 80ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ENDHDR = 22; // END header size 81ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ZIP64_LOCHDR = 20; // ZIP64 end locator header size 82ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ZIP64_ENDHDR = 56; // ZIP64 end header size 83ccf23a609c01ce08a79f56353a1750f1067749c6cushon 84ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ENDTOT = 10; // total number of entries 85ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ENDSIZ = 12; // central directory size in bytes 8686665ea565aca9132d09988cd8de50920c814f47cushon static final int ENDCOM = 20; // zip file comment length 87ccf23a609c01ce08a79f56353a1750f1067749c6cushon 88ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENHOW = 10; // compression method 89ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENLEN = 24; // uncompressed size 90ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENSIZ = 20; // compressed size 91ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENNAM = 28; // filename length 92ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENEXT = 30; // extra field length 93ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENCOM = 32; // comment length 94ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int CENOFF = 42; // LOC header offset 95ccf23a609c01ce08a79f56353a1750f1067749c6cushon 962c831a8dd79cdc77293246b007dbddd55091b571cushon static final int LOCEXT = 28; // extra field length 972c831a8dd79cdc77293246b007dbddd55091b571cushon 98ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ZIP64_ENDSIZ = 40; // central directory size in bytes 99ccf23a609c01ce08a79f56353a1750f1067749c6cushon 100ccf23a609c01ce08a79f56353a1750f1067749c6cushon static final int ZIP64_MAGICCOUNT = 0xFFFF; 101ccf23a609c01ce08a79f56353a1750f1067749c6cushon 102ccf23a609c01ce08a79f56353a1750f1067749c6cushon /** Iterates over a zip archive. */ 103ccf23a609c01ce08a79f56353a1750f1067749c6cushon static class ZipIterator implements Iterator<Entry> { 104ccf23a609c01ce08a79f56353a1750f1067749c6cushon 105ccf23a609c01ce08a79f56353a1750f1067749c6cushon /** A reader for the backing storage. */ 106ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final FileChannel chan; 107ccf23a609c01ce08a79f56353a1750f1067749c6cushon 1082c831a8dd79cdc77293246b007dbddd55091b571cushon private final Path path; 109ccf23a609c01ce08a79f56353a1750f1067749c6cushon private int cdindex = 0; 110ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final MappedByteBuffer cd; 111ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final CharsetDecoder decoder = UTF_8.newDecoder(); 112ccf23a609c01ce08a79f56353a1750f1067749c6cushon 1132c831a8dd79cdc77293246b007dbddd55091b571cushon ZipIterator(Path path, FileChannel chan, MappedByteBuffer cd) { 1142c831a8dd79cdc77293246b007dbddd55091b571cushon this.path = path; 115ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.chan = chan; 116ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.cd = cd; 117ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 118ccf23a609c01ce08a79f56353a1750f1067749c6cushon 119ccf23a609c01ce08a79f56353a1750f1067749c6cushon @Override 120ccf23a609c01ce08a79f56353a1750f1067749c6cushon public boolean hasNext() { 121ccf23a609c01ce08a79f56353a1750f1067749c6cushon return cdindex < cd.limit(); 122ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 123ccf23a609c01ce08a79f56353a1750f1067749c6cushon 124ccf23a609c01ce08a79f56353a1750f1067749c6cushon /* Returns a {@link Entry} for the current CEN entry. */ 125ccf23a609c01ce08a79f56353a1750f1067749c6cushon @Override 126ccf23a609c01ce08a79f56353a1750f1067749c6cushon public Entry next() { 127ccf23a609c01ce08a79f56353a1750f1067749c6cushon // TODO(cushon): technically we're supposed to throw NSEE 12886665ea565aca9132d09988cd8de50920c814f47cushon checkSignature(path, cd, cdindex, 1, 2, "CENSIG"); 129ccf23a609c01ce08a79f56353a1750f1067749c6cushon int nameLength = cd.getChar(cdindex + CENNAM); 130ccf23a609c01ce08a79f56353a1750f1067749c6cushon int extLength = cd.getChar(cdindex + CENEXT); 131ccf23a609c01ce08a79f56353a1750f1067749c6cushon int commentLength = cd.getChar(cdindex + CENCOM); 1322c831a8dd79cdc77293246b007dbddd55091b571cushon Entry entry = new Entry(path, chan, string(cd, cdindex + CENHDR, nameLength), cd, cdindex); 133ccf23a609c01ce08a79f56353a1750f1067749c6cushon cdindex += CENHDR + nameLength + extLength + commentLength; 134ccf23a609c01ce08a79f56353a1750f1067749c6cushon return entry; 135ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 136ccf23a609c01ce08a79f56353a1750f1067749c6cushon 137ccf23a609c01ce08a79f56353a1750f1067749c6cushon public String string(ByteBuffer buf, int offset, int length) { 138ccf23a609c01ce08a79f56353a1750f1067749c6cushon buf = buf.duplicate(); 139ccf23a609c01ce08a79f56353a1750f1067749c6cushon buf.position(offset); 140ccf23a609c01ce08a79f56353a1750f1067749c6cushon buf.limit(offset + length); 141ccf23a609c01ce08a79f56353a1750f1067749c6cushon decoder.reset(); 142ccf23a609c01ce08a79f56353a1750f1067749c6cushon try { 143ccf23a609c01ce08a79f56353a1750f1067749c6cushon return decoder.decode(buf).toString(); 144ccf23a609c01ce08a79f56353a1750f1067749c6cushon } catch (CharacterCodingException e) { 145ccf23a609c01ce08a79f56353a1750f1067749c6cushon throw new IOError(e); 146ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 147ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 148ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 149ccf23a609c01ce08a79f56353a1750f1067749c6cushon 150ccf23a609c01ce08a79f56353a1750f1067749c6cushon /** Provides an {@link Iterable} of {@link Entry} over a zip archive. */ 151ccf23a609c01ce08a79f56353a1750f1067749c6cushon public static class ZipIterable implements Iterable<Entry>, Closeable { 152ccf23a609c01ce08a79f56353a1750f1067749c6cushon 1532c831a8dd79cdc77293246b007dbddd55091b571cushon private final Path path; 154ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final FileChannel chan; 155ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final MappedByteBuffer cd; 156ccf23a609c01ce08a79f56353a1750f1067749c6cushon 157ccf23a609c01ce08a79f56353a1750f1067749c6cushon public ZipIterable(Path path) throws IOException { 1582c831a8dd79cdc77293246b007dbddd55091b571cushon this.path = path; 159ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.chan = FileChannel.open(path, StandardOpenOption.READ); 16086665ea565aca9132d09988cd8de50920c814f47cushon // Locate the EOCD 161ccf23a609c01ce08a79f56353a1750f1067749c6cushon long size = chan.size(); 162ccf23a609c01ce08a79f56353a1750f1067749c6cushon if (size < ENDHDR) { 163ccf23a609c01ce08a79f56353a1750f1067749c6cushon throw new ZipException("invalid zip archive"); 164ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 165ccf23a609c01ce08a79f56353a1750f1067749c6cushon long eocdOffset = size - ENDHDR; 166ccf23a609c01ce08a79f56353a1750f1067749c6cushon MappedByteBuffer eocd = chan.map(MapMode.READ_ONLY, eocdOffset, ENDHDR); 167ccf23a609c01ce08a79f56353a1750f1067749c6cushon eocd.order(ByteOrder.LITTLE_ENDIAN); 16886665ea565aca9132d09988cd8de50920c814f47cushon int index = 0; 16986665ea565aca9132d09988cd8de50920c814f47cushon int commentSize = 0; 17086665ea565aca9132d09988cd8de50920c814f47cushon if (!isSignature(eocd, 0, 5, 6)) { 17186665ea565aca9132d09988cd8de50920c814f47cushon // The archive may contain a zip file comment; keep looking for the EOCD. 17286665ea565aca9132d09988cd8de50920c814f47cushon long start = Math.max(0, size - ENDHDR - 0xFFFF); 17386665ea565aca9132d09988cd8de50920c814f47cushon eocd = chan.map(MapMode.READ_ONLY, start, (size - start)); 17486665ea565aca9132d09988cd8de50920c814f47cushon eocd.order(ByteOrder.LITTLE_ENDIAN); 17586665ea565aca9132d09988cd8de50920c814f47cushon index = (int) ((size - start) - ENDHDR); 17686665ea565aca9132d09988cd8de50920c814f47cushon while (index > 0) { 17786665ea565aca9132d09988cd8de50920c814f47cushon index--; 17886665ea565aca9132d09988cd8de50920c814f47cushon eocd.position(index); 17986665ea565aca9132d09988cd8de50920c814f47cushon if (isSignature(eocd, index, 5, 6)) { 18086665ea565aca9132d09988cd8de50920c814f47cushon commentSize = (int) ((size - start) - ENDHDR) - index; 18186665ea565aca9132d09988cd8de50920c814f47cushon eocdOffset = start + index; 18286665ea565aca9132d09988cd8de50920c814f47cushon break; 18386665ea565aca9132d09988cd8de50920c814f47cushon } 18486665ea565aca9132d09988cd8de50920c814f47cushon } 18586665ea565aca9132d09988cd8de50920c814f47cushon } 18686665ea565aca9132d09988cd8de50920c814f47cushon checkSignature(path, eocd, index, 5, 6, "ENDSIG"); 18786665ea565aca9132d09988cd8de50920c814f47cushon int totalEntries = eocd.getChar(index + ENDTOT); 18886665ea565aca9132d09988cd8de50920c814f47cushon long cdsize = UnsignedInts.toLong(eocd.getInt(index + ENDSIZ)); 18986665ea565aca9132d09988cd8de50920c814f47cushon int actualCommentSize = eocd.getChar(index + ENDCOM); 19086665ea565aca9132d09988cd8de50920c814f47cushon if (commentSize != actualCommentSize) { 19186665ea565aca9132d09988cd8de50920c814f47cushon throw new ZipException( 19286665ea565aca9132d09988cd8de50920c814f47cushon String.format( 19386665ea565aca9132d09988cd8de50920c814f47cushon "zip file comment length was %d, expected %d", commentSize, actualCommentSize)); 19486665ea565aca9132d09988cd8de50920c814f47cushon } 195ccf23a609c01ce08a79f56353a1750f1067749c6cushon // If the number of entries is 0xffff, check if the archive has a zip64 EOCD locator. 196ccf23a609c01ce08a79f56353a1750f1067749c6cushon if (totalEntries == ZIP64_MAGICCOUNT) { 197ccf23a609c01ce08a79f56353a1750f1067749c6cushon // Assume the zip64 EOCD has the usual size; we don't support zip64 extensible data sectors. 198ccf23a609c01ce08a79f56353a1750f1067749c6cushon long zip64eocdOffset = size - ENDHDR - ZIP64_LOCHDR - ZIP64_ENDHDR; 199ccf23a609c01ce08a79f56353a1750f1067749c6cushon MappedByteBuffer zip64eocd = chan.map(MapMode.READ_ONLY, zip64eocdOffset, ZIP64_ENDHDR); 200ccf23a609c01ce08a79f56353a1750f1067749c6cushon zip64eocd.order(ByteOrder.LITTLE_ENDIAN); 201ccf23a609c01ce08a79f56353a1750f1067749c6cushon // Note that zip reading is necessarily best-effort, since an archive could contain 0xFFFF 202ccf23a609c01ce08a79f56353a1750f1067749c6cushon // entries and the last entry's data could contain a ZIP64_ENDSIG. Some implementations 203ccf23a609c01ce08a79f56353a1750f1067749c6cushon // read the full EOCD records and compare them. 204ccf23a609c01ce08a79f56353a1750f1067749c6cushon if (zip64eocd.getInt(0) == ZIP64_ENDSIG) { 2052c831a8dd79cdc77293246b007dbddd55091b571cushon cdsize = zip64eocd.getLong(ZIP64_ENDSIZ); 206ccf23a609c01ce08a79f56353a1750f1067749c6cushon eocdOffset = zip64eocdOffset; 207ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 208ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 209ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.cd = chan.map(MapMode.READ_ONLY, eocdOffset - cdsize, cdsize); 210ccf23a609c01ce08a79f56353a1750f1067749c6cushon cd.order(ByteOrder.LITTLE_ENDIAN); 211ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 212ccf23a609c01ce08a79f56353a1750f1067749c6cushon 213ccf23a609c01ce08a79f56353a1750f1067749c6cushon @Override 214ccf23a609c01ce08a79f56353a1750f1067749c6cushon public Iterator<Entry> iterator() { 2152c831a8dd79cdc77293246b007dbddd55091b571cushon return new ZipIterator(path, chan, cd); 216ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 217ccf23a609c01ce08a79f56353a1750f1067749c6cushon 218ccf23a609c01ce08a79f56353a1750f1067749c6cushon @Override 219ccf23a609c01ce08a79f56353a1750f1067749c6cushon public void close() throws IOException { 220ccf23a609c01ce08a79f56353a1750f1067749c6cushon chan.close(); 221ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 222ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 223ccf23a609c01ce08a79f56353a1750f1067749c6cushon 224ccf23a609c01ce08a79f56353a1750f1067749c6cushon /** An entry in a zip archive. */ 225ccf23a609c01ce08a79f56353a1750f1067749c6cushon public static class Entry { 226ccf23a609c01ce08a79f56353a1750f1067749c6cushon 2272c831a8dd79cdc77293246b007dbddd55091b571cushon private final Path path; 228ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final FileChannel chan; 229ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final String name; 230ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final ByteBuffer cd; 231ccf23a609c01ce08a79f56353a1750f1067749c6cushon private final int cdindex; 232ccf23a609c01ce08a79f56353a1750f1067749c6cushon 2332c831a8dd79cdc77293246b007dbddd55091b571cushon public Entry(Path path, FileChannel chan, String name, ByteBuffer cd, int cdindex) { 2342c831a8dd79cdc77293246b007dbddd55091b571cushon this.path = path; 235ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.chan = chan; 236ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.name = name; 237ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.cd = cd; 238ccf23a609c01ce08a79f56353a1750f1067749c6cushon this.cdindex = cdindex; 239ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 240ccf23a609c01ce08a79f56353a1750f1067749c6cushon 241ccf23a609c01ce08a79f56353a1750f1067749c6cushon /** The entry name. */ 242ccf23a609c01ce08a79f56353a1750f1067749c6cushon public String name() { 243ccf23a609c01ce08a79f56353a1750f1067749c6cushon return name; 244ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 245ccf23a609c01ce08a79f56353a1750f1067749c6cushon 246ccf23a609c01ce08a79f56353a1750f1067749c6cushon /** The entry data. */ 247ccf23a609c01ce08a79f56353a1750f1067749c6cushon public byte[] data() { 2482c831a8dd79cdc77293246b007dbddd55091b571cushon // Read the offset and variable lengths from the central directory and then try to map in the 2492c831a8dd79cdc77293246b007dbddd55091b571cushon // data section in one shot. 250ccf23a609c01ce08a79f56353a1750f1067749c6cushon long offset = UnsignedInts.toLong(cd.getInt(cdindex + CENOFF)); 251ccf23a609c01ce08a79f56353a1750f1067749c6cushon int nameLength = cd.getChar(cdindex + CENNAM); 252ccf23a609c01ce08a79f56353a1750f1067749c6cushon int extLength = cd.getChar(cdindex + CENEXT); 253ccf23a609c01ce08a79f56353a1750f1067749c6cushon int compression = cd.getChar(cdindex + CENHOW); 254ccf23a609c01ce08a79f56353a1750f1067749c6cushon switch (compression) { 255ccf23a609c01ce08a79f56353a1750f1067749c6cushon case 0x8: 256ccf23a609c01ce08a79f56353a1750f1067749c6cushon return getBytes( 2572c831a8dd79cdc77293246b007dbddd55091b571cushon offset, 2582c831a8dd79cdc77293246b007dbddd55091b571cushon nameLength, 2592c831a8dd79cdc77293246b007dbddd55091b571cushon extLength, 2602c831a8dd79cdc77293246b007dbddd55091b571cushon UnsignedInts.toLong(cd.getInt(cdindex + CENSIZ)), 2612c831a8dd79cdc77293246b007dbddd55091b571cushon /*deflate=*/ true); 262ccf23a609c01ce08a79f56353a1750f1067749c6cushon case 0x0: 263ccf23a609c01ce08a79f56353a1750f1067749c6cushon return getBytes( 2642c831a8dd79cdc77293246b007dbddd55091b571cushon offset, 2652c831a8dd79cdc77293246b007dbddd55091b571cushon nameLength, 2662c831a8dd79cdc77293246b007dbddd55091b571cushon extLength, 2672c831a8dd79cdc77293246b007dbddd55091b571cushon UnsignedInts.toLong(cd.getInt(cdindex + CENLEN)), 2682c831a8dd79cdc77293246b007dbddd55091b571cushon /*deflate=*/ false); 269ccf23a609c01ce08a79f56353a1750f1067749c6cushon default: 270ccf23a609c01ce08a79f56353a1750f1067749c6cushon throw new AssertionError( 271ccf23a609c01ce08a79f56353a1750f1067749c6cushon String.format("unsupported compression mode: 0x%x", compression)); 272ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 273ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 274ccf23a609c01ce08a79f56353a1750f1067749c6cushon 2752c831a8dd79cdc77293246b007dbddd55091b571cushon /** 2762c831a8dd79cdc77293246b007dbddd55091b571cushon * Number of extra bytes to read for each file, to avoid re-mapping the data if the local header 2772c831a8dd79cdc77293246b007dbddd55091b571cushon * reports more extra field data than the central directory. 2782c831a8dd79cdc77293246b007dbddd55091b571cushon */ 2792c831a8dd79cdc77293246b007dbddd55091b571cushon static final int EXTRA_FIELD_SLACK = 128; 2802c831a8dd79cdc77293246b007dbddd55091b571cushon 2812c831a8dd79cdc77293246b007dbddd55091b571cushon private byte[] getBytes( 2822c831a8dd79cdc77293246b007dbddd55091b571cushon long offset, int nameLength, int cenExtLength, long size, boolean deflate) { 283ccf23a609c01ce08a79f56353a1750f1067749c6cushon if (size > Integer.MAX_VALUE) { 284ccf23a609c01ce08a79f56353a1750f1067749c6cushon throw new IllegalArgumentException("unsupported zip entry size: " + size); 285ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 286ccf23a609c01ce08a79f56353a1750f1067749c6cushon try { 2872c831a8dd79cdc77293246b007dbddd55091b571cushon MappedByteBuffer fc = 2882c831a8dd79cdc77293246b007dbddd55091b571cushon chan.map( 2892c831a8dd79cdc77293246b007dbddd55091b571cushon MapMode.READ_ONLY, 2902c831a8dd79cdc77293246b007dbddd55091b571cushon offset, 2912c831a8dd79cdc77293246b007dbddd55091b571cushon Math.min( 2922c831a8dd79cdc77293246b007dbddd55091b571cushon LOCHDR + nameLength + cenExtLength + size + EXTRA_FIELD_SLACK, 2932c831a8dd79cdc77293246b007dbddd55091b571cushon chan.size() - offset)); 2942c831a8dd79cdc77293246b007dbddd55091b571cushon fc.order(ByteOrder.LITTLE_ENDIAN); 29586665ea565aca9132d09988cd8de50920c814f47cushon checkSignature(path, fc, /* index= */ 0, 3, 4, "LOCSIG"); 2962c831a8dd79cdc77293246b007dbddd55091b571cushon int locExtLength = fc.getChar(LOCEXT); 2972c831a8dd79cdc77293246b007dbddd55091b571cushon if (locExtLength > cenExtLength + EXTRA_FIELD_SLACK) { 2982c831a8dd79cdc77293246b007dbddd55091b571cushon // If the local header's extra fields don't match the central directory and we didn't 2992c831a8dd79cdc77293246b007dbddd55091b571cushon // leave enough slac, re-map the data section with the correct extra field length. 3002c831a8dd79cdc77293246b007dbddd55091b571cushon fc = chan.map(MapMode.READ_ONLY, offset + LOCHDR + nameLength + locExtLength, size); 3012c831a8dd79cdc77293246b007dbddd55091b571cushon fc.order(ByteOrder.LITTLE_ENDIAN); 3022c831a8dd79cdc77293246b007dbddd55091b571cushon } else { 3032c831a8dd79cdc77293246b007dbddd55091b571cushon // Otherwise seek past the local header, name, and extra fields to the data. 3042c831a8dd79cdc77293246b007dbddd55091b571cushon fc.position(LOCHDR + nameLength + locExtLength); 3052c831a8dd79cdc77293246b007dbddd55091b571cushon fc.limit((int) (LOCHDR + nameLength + locExtLength + size)); 3062c831a8dd79cdc77293246b007dbddd55091b571cushon } 307ccf23a609c01ce08a79f56353a1750f1067749c6cushon byte[] bytes = new byte[(int) size]; 308ccf23a609c01ce08a79f56353a1750f1067749c6cushon fc.get(bytes); 309ccf23a609c01ce08a79f56353a1750f1067749c6cushon if (deflate) { 310ccf23a609c01ce08a79f56353a1750f1067749c6cushon bytes = 311ccf23a609c01ce08a79f56353a1750f1067749c6cushon ByteStreams.toByteArray( 312ccf23a609c01ce08a79f56353a1750f1067749c6cushon new InflaterInputStream( 313ccf23a609c01ce08a79f56353a1750f1067749c6cushon new ByteArrayInputStream(bytes), new Inflater(/*nowrap=*/ true))); 314ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 315ccf23a609c01ce08a79f56353a1750f1067749c6cushon return bytes; 316ccf23a609c01ce08a79f56353a1750f1067749c6cushon } catch (IOException e) { 317ccf23a609c01ce08a79f56353a1750f1067749c6cushon throw new IOError(e); 318ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 319ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 320ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 321ccf23a609c01ce08a79f56353a1750f1067749c6cushon 3222c831a8dd79cdc77293246b007dbddd55091b571cushon static void checkSignature( 3232c831a8dd79cdc77293246b007dbddd55091b571cushon Path path, MappedByteBuffer buf, int index, int i, int j, String name) { 32486665ea565aca9132d09988cd8de50920c814f47cushon if (!isSignature(buf, index, i, j)) { 325ccf23a609c01ce08a79f56353a1750f1067749c6cushon throw new AssertionError( 326ccf23a609c01ce08a79f56353a1750f1067749c6cushon String.format( 3272c831a8dd79cdc77293246b007dbddd55091b571cushon "%s: bad %s (expected: 0x%02x%02x%02x%02x, actual: 0x%08x)", 3282c831a8dd79cdc77293246b007dbddd55091b571cushon path, name, i, j, (int) 'K', (int) 'P', buf.getInt(index))); 329ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 330ccf23a609c01ce08a79f56353a1750f1067749c6cushon } 33186665ea565aca9132d09988cd8de50920c814f47cushon 33286665ea565aca9132d09988cd8de50920c814f47cushon static boolean isSignature(MappedByteBuffer buf, int index, int i, int j) { 33386665ea565aca9132d09988cd8de50920c814f47cushon return (buf.get(index) == 'P') 33486665ea565aca9132d09988cd8de50920c814f47cushon && (buf.get(index + 1) == 'K') 33586665ea565aca9132d09988cd8de50920c814f47cushon && (buf.get(index + 2) == i) 33686665ea565aca9132d09988cd8de50920c814f47cushon && (buf.get(index + 3) == j); 33786665ea565aca9132d09988cd8de50920c814f47cushon } 338ccf23a609c01ce08a79f56353a1750f1067749c6cushon} 339