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