• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.turbine.zip;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static java.nio.charset.StandardCharsets.UTF_8;
21 import static org.junit.Assert.assertThrows;
22 
23 import com.google.common.collect.ImmutableMap;
24 import com.google.common.hash.Hashing;
25 import java.io.IOException;
26 import java.net.URI;
27 import java.nio.ByteBuffer;
28 import java.nio.ByteOrder;
29 import java.nio.file.FileSystem;
30 import java.nio.file.FileSystems;
31 import java.nio.file.Files;
32 import java.nio.file.Path;
33 import java.nio.file.StandardOpenOption;
34 import java.nio.file.attribute.FileTime;
35 import java.util.Enumeration;
36 import java.util.LinkedHashMap;
37 import java.util.Map;
38 import java.util.jar.JarEntry;
39 import java.util.jar.JarFile;
40 import java.util.jar.JarOutputStream;
41 import java.util.zip.ZipException;
42 import java.util.zip.ZipFile;
43 import java.util.zip.ZipOutputStream;
44 import org.junit.Rule;
45 import org.junit.Test;
46 import org.junit.rules.TemporaryFolder;
47 import org.junit.runner.RunWith;
48 import org.junit.runners.JUnit4;
49 
50 /** {@link Zip}Test */
51 @RunWith(JUnit4.class)
52 public class ZipTest {
53 
54   @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
55 
56   @Test
testEntries()57   public void testEntries() throws IOException {
58     testEntries(1000);
59   }
60 
61   @Test
zip64_testEntries()62   public void zip64_testEntries() throws IOException {
63     testEntries(70000);
64   }
65 
66   @Test
compression()67   public void compression() throws IOException {
68     Path path = temporaryFolder.newFile("test.jar").toPath();
69     try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
70       for (int i = 0; i < 2; i++) {
71         String name = "entry" + i;
72         byte[] bytes = name.getBytes(UTF_8);
73         jos.putNextEntry(new JarEntry(name));
74         jos.write(bytes);
75       }
76     }
77     assertThat(actual(path)).isEqualTo(expected(path));
78   }
79 
testEntries(int entries)80   private void testEntries(int entries) throws IOException {
81     Path path = temporaryFolder.newFile("test.jar").toPath();
82     try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
83       for (int i = 0; i < entries; i++) {
84         String name = "entry" + i;
85         byte[] bytes = name.getBytes(UTF_8);
86         createEntry(jos, name, bytes);
87       }
88     }
89     assertThat(actual(path)).isEqualTo(expected(path));
90   }
91 
createEntry(ZipOutputStream jos, String name, byte[] bytes)92   private static void createEntry(ZipOutputStream jos, String name, byte[] bytes)
93       throws IOException {
94     JarEntry je = new JarEntry(name);
95     je.setMethod(JarEntry.STORED);
96     je.setSize(bytes.length);
97     je.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
98     jos.putNextEntry(je);
99     jos.write(bytes);
100   }
101 
actual(Path path)102   private static Map<String, Long> actual(Path path) throws IOException {
103     Map<String, Long> result = new LinkedHashMap<>();
104     for (Zip.Entry e : new Zip.ZipIterable(path)) {
105       result.put(e.name(), Hashing.goodFastHash(128).hashBytes(e.data()).padToLong());
106     }
107     return result;
108   }
109 
expected(Path path)110   private static Map<String, Long> expected(Path path) throws IOException {
111     Map<String, Long> result = new LinkedHashMap<>();
112     try (JarFile jf = new JarFile(path.toFile())) {
113       Enumeration<JarEntry> entries = jf.entries();
114       while (entries.hasMoreElements()) {
115         JarEntry je = entries.nextElement();
116         result.put(
117             je.getName(),
118             Hashing.goodFastHash(128).hashBytes(jf.getInputStream(je).readAllBytes()).padToLong());
119       }
120     }
121     return result;
122   }
123 
124   @Test
attributes()125   public void attributes() throws Exception {
126     Path path = temporaryFolder.newFile("test.jar").toPath();
127     Files.delete(path);
128     try (FileSystem fs =
129         FileSystems.newFileSystem(
130             URI.create("jar:" + path.toUri()), ImmutableMap.of("create", "true"))) {
131       for (int i = 0; i < 3; i++) {
132         String name = "entry" + i;
133         byte[] bytes = name.getBytes(UTF_8);
134         Path entry = fs.getPath(name);
135         Files.write(entry, bytes);
136         Files.setLastModifiedTime(entry, FileTime.fromMillis(0));
137       }
138     }
139     assertThat(actual(path)).isEqualTo(expected(path));
140   }
141 
142   @Test
zipFileCommentsAreSupported()143   public void zipFileCommentsAreSupported() throws Exception {
144     Path path = temporaryFolder.newFile("test.jar").toPath();
145     Files.delete(path);
146     try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(path))) {
147       createEntry(zos, "hello", "world".getBytes(UTF_8));
148       zos.setComment("this is a comment");
149     }
150     assertThat(actual(path)).isEqualTo(expected(path));
151   }
152 
153   @Test
malformedComment()154   public void malformedComment() throws Exception {
155     Path path = temporaryFolder.newFile("test.jar").toPath();
156     Files.delete(path);
157 
158     try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(path))) {
159       createEntry(zos, "hello", "world".getBytes(UTF_8));
160       zos.setComment("this is a comment");
161     }
162     Files.writeString(path, "trailing garbage", StandardOpenOption.APPEND);
163 
164     ZipException e = assertThrows(ZipException.class, () -> actual(path));
165     assertThat(e).hasMessageThat().isEqualTo("zip file comment length was 33, expected 17");
166   }
167 
168   // Create a zip64 archive with an extensible data sector
169   @Test
zip64extension()170   public void zip64extension() throws IOException {
171 
172     ByteBuffer buf = ByteBuffer.allocate(1000);
173     buf.order(ByteOrder.LITTLE_ENDIAN);
174 
175     // The jar has a single entry named 'hello', with the value 'world'
176     byte[] name = "hello".getBytes(UTF_8);
177     byte[] value = "world".getBytes(UTF_8);
178     int crc = Hashing.crc32().hashBytes(value).asInt();
179 
180     int localHeaderPosition = buf.position();
181 
182     // local file header signature     4 bytes  (0x04034b50)
183     buf.putInt((int) ZipFile.LOCSIG);
184     // version needed to extract       2 bytes
185     buf.putShort((short) 0);
186     // general purpose bit flag        2 bytes
187     buf.putShort((short) 0);
188     // compression method              2 bytes
189     buf.putShort((short) 0);
190     // last mod file time              2 bytes
191     buf.putShort((short) 0);
192     // last mod file date              2 bytes
193     buf.putShort((short) 0);
194     // crc-32                          4 bytes
195     buf.putInt(crc);
196     // compressed size                 4 bytes
197     buf.putInt(value.length);
198     // uncompressed size               4 bytes
199     buf.putInt(value.length);
200     // file name length                2 bytes
201     buf.putShort((short) name.length);
202     // extra field length              2 bytes
203     buf.putShort((short) 0);
204     // file name (variable size)
205     buf.put(name);
206     // extra field (variable size)
207     // file data
208     buf.put(value);
209 
210     int centralDirectoryPosition = buf.position();
211 
212     // central file header signature   4 bytes  (0x02014b50)
213     buf.putInt((int) ZipFile.CENSIG);
214     // version made by                 2 bytes
215     buf.putShort((short) 0);
216     // version needed to extract       2 bytes
217     buf.putShort((short) 0);
218     // general purpose bit flag        2 bytes
219     buf.putShort((short) 0);
220     // compression method              2 bytes
221     buf.putShort((short) 0);
222     // last mod file time              2 bytes
223     buf.putShort((short) 0);
224     // last mod file date              2 bytes
225     buf.putShort((short) 0);
226     // crc-32                          4 bytes
227     buf.putInt(crc);
228     // compressed size                 4 bytes
229     buf.putInt(value.length);
230     // uncompressed size               4 bytes
231     buf.putInt(value.length);
232     // file name length                2 bytes
233     buf.putShort((short) name.length);
234     // extra field length              2 bytes
235     buf.putShort((short) 0);
236     // file comment length             2 bytes
237     buf.putShort((short) 0);
238     // disk number start               2 bytes
239     buf.putShort((short) 0);
240     // internal file attributes        2 bytes
241     buf.putShort((short) 0);
242     // external file attributes        4 bytes
243     buf.putInt(0);
244     // relative offset of local header 4 bytes
245     buf.putInt(localHeaderPosition);
246     // file name (variable size)
247     buf.put(name);
248 
249     int centralDirectorySize = buf.position() - centralDirectoryPosition;
250     int zip64eocdPosition = buf.position();
251 
252     // zip64 end of central dir
253     // signature                       4 bytes  (0x06064b50)
254     buf.putInt(Zip.ZIP64_ENDSIG);
255     // size of zip64 end of central
256     // directory record                8 bytes
257     buf.putLong(Zip.ZIP64_ENDSIZ + 5);
258     // version made by                 2 bytes
259     buf.putShort((short) 0);
260     // version needed to extract       2 bytes
261     buf.putShort((short) 0);
262     // number of this disk             4 bytes
263     buf.putInt(0);
264     // number of the disk with the
265     // start of the central directory  4 bytes
266     buf.putInt(0);
267     // total number of entries in the
268     // central directory on this disk  8 bytes
269     buf.putLong(1);
270     // total number of entries in the
271     // central directory               8 bytes
272     buf.putLong(1);
273     // size of the central directory   8 bytes
274     buf.putLong(centralDirectorySize);
275     // offset of start of central
276     // directory with respect to
277     // offset of start of central
278     // the starting disk number        8 bytes
279     buf.putLong(centralDirectoryPosition);
280     // zip64 extensible data sector    (variable size)
281     buf.put((byte) 3);
282     buf.putInt(42);
283 
284     // zip64 end of central dir locator
285     // signature                       4 bytes  (0x07064b50)
286     buf.putInt(Zip.ZIP64_LOCSIG);
287     // number of the disk with the
288     // start of the zip64 end of
289     // central directory               4 bytes
290     buf.putInt(0);
291     // relative offset of the zip64
292     // end of central directory record 8 bytes
293     buf.putLong(zip64eocdPosition);
294     // total number of disks           4 bytes
295     buf.putInt(0);
296 
297     // end of central dir signature    4 bytes  (0x06054b50)
298     buf.putInt((int) ZipFile.ENDSIG);
299     // number of this disk             2 bytes
300     buf.putShort((short) 0);
301     // number of the disk with the
302     // start of the central directory  2 bytes
303     buf.putShort((short) 0);
304     // total number of entries in the
305     // central directory on this disk  2 bytes
306     buf.putShort((short) 1);
307     // total number of entries in
308     // the central directory           2 bytes
309     buf.putShort((short) Zip.ZIP64_MAGICCOUNT);
310     // size of the central directory   4 bytes
311     buf.putInt(centralDirectorySize);
312     // offset of start of central
313     // directory with respect to
314     // the starting disk number        4 bytes
315     buf.putInt(centralDirectoryPosition);
316     //         .ZIP file comment length        2 bytes
317     buf.putShort((short) 0);
318     //         .ZIP file comment       (variable size)
319 
320     byte[] bytes = new byte[buf.position()];
321     buf.rewind();
322     buf.get(bytes);
323     Path path = temporaryFolder.newFile("test.jar").toPath();
324     Files.write(path, bytes);
325     assertThat(actual(path)).isEqualTo(expected(path));
326   }
327 }
328