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 com.google.common.truth.Truth.assertThat;
20ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport static java.nio.charset.StandardCharsets.UTF_8;
2186665ea565aca9132d09988cd8de50920c814f47cushonimport static org.junit.Assert.fail;
22ccf23a609c01ce08a79f56353a1750f1067749c6cushon
232c831a8dd79cdc77293246b007dbddd55091b571cushonimport com.google.common.collect.ImmutableMap;
24ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport com.google.common.hash.Hashing;
25ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport com.google.common.io.ByteStreams;
26ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.io.IOException;
272c831a8dd79cdc77293246b007dbddd55091b571cushonimport java.net.URI;
282c831a8dd79cdc77293246b007dbddd55091b571cushonimport java.nio.file.FileSystem;
292c831a8dd79cdc77293246b007dbddd55091b571cushonimport java.nio.file.FileSystems;
30ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.file.Files;
31ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.nio.file.Path;
3286665ea565aca9132d09988cd8de50920c814f47cushonimport java.nio.file.StandardOpenOption;
332c831a8dd79cdc77293246b007dbddd55091b571cushonimport java.nio.file.attribute.FileTime;
34ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.Enumeration;
35ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.LinkedHashMap;
36ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.Map;
37ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.jar.JarEntry;
38ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.jar.JarFile;
39ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport java.util.jar.JarOutputStream;
4086665ea565aca9132d09988cd8de50920c814f47cushonimport java.util.zip.ZipException;
4186665ea565aca9132d09988cd8de50920c814f47cushonimport java.util.zip.ZipOutputStream;
42ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport org.junit.Rule;
43ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport org.junit.Test;
44ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport org.junit.rules.TemporaryFolder;
45ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport org.junit.runner.RunWith;
46ccf23a609c01ce08a79f56353a1750f1067749c6cushonimport org.junit.runners.JUnit4;
47ccf23a609c01ce08a79f56353a1750f1067749c6cushon
48ccf23a609c01ce08a79f56353a1750f1067749c6cushon/** {@link Zip}Test */
49ccf23a609c01ce08a79f56353a1750f1067749c6cushon@RunWith(JUnit4.class)
50ccf23a609c01ce08a79f56353a1750f1067749c6cushonpublic class ZipTest {
51ccf23a609c01ce08a79f56353a1750f1067749c6cushon
52ccf23a609c01ce08a79f56353a1750f1067749c6cushon  @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
53ccf23a609c01ce08a79f56353a1750f1067749c6cushon
54ccf23a609c01ce08a79f56353a1750f1067749c6cushon  @Test
55ccf23a609c01ce08a79f56353a1750f1067749c6cushon  public void testEntries() throws IOException {
56ccf23a609c01ce08a79f56353a1750f1067749c6cushon    testEntries(1000);
57ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
58ccf23a609c01ce08a79f56353a1750f1067749c6cushon
59ccf23a609c01ce08a79f56353a1750f1067749c6cushon  @Test
60ccf23a609c01ce08a79f56353a1750f1067749c6cushon  public void zip64_testEntries() throws IOException {
61ccf23a609c01ce08a79f56353a1750f1067749c6cushon    testEntries(70000);
62ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
63ccf23a609c01ce08a79f56353a1750f1067749c6cushon
64ccf23a609c01ce08a79f56353a1750f1067749c6cushon  @Test
65ccf23a609c01ce08a79f56353a1750f1067749c6cushon  public void compression() throws IOException {
66ccf23a609c01ce08a79f56353a1750f1067749c6cushon    Path path = temporaryFolder.newFile("test.jar").toPath();
67ccf23a609c01ce08a79f56353a1750f1067749c6cushon    try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
68ccf23a609c01ce08a79f56353a1750f1067749c6cushon      for (int i = 0; i < 2; i++) {
69ccf23a609c01ce08a79f56353a1750f1067749c6cushon        String name = "entry" + i;
70ccf23a609c01ce08a79f56353a1750f1067749c6cushon        byte[] bytes = name.getBytes(UTF_8);
71ccf23a609c01ce08a79f56353a1750f1067749c6cushon        jos.putNextEntry(new JarEntry(name));
72ccf23a609c01ce08a79f56353a1750f1067749c6cushon        jos.write(bytes);
73ccf23a609c01ce08a79f56353a1750f1067749c6cushon      }
74ccf23a609c01ce08a79f56353a1750f1067749c6cushon    }
75ccf23a609c01ce08a79f56353a1750f1067749c6cushon    assertThat(actual(path)).isEqualTo(expected(path));
76ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
77ccf23a609c01ce08a79f56353a1750f1067749c6cushon
78ccf23a609c01ce08a79f56353a1750f1067749c6cushon  private void testEntries(int entries) throws IOException {
79ccf23a609c01ce08a79f56353a1750f1067749c6cushon    Path path = temporaryFolder.newFile("test.jar").toPath();
80ccf23a609c01ce08a79f56353a1750f1067749c6cushon    try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) {
81ccf23a609c01ce08a79f56353a1750f1067749c6cushon      for (int i = 0; i < entries; i++) {
82ccf23a609c01ce08a79f56353a1750f1067749c6cushon        String name = "entry" + i;
83ccf23a609c01ce08a79f56353a1750f1067749c6cushon        byte[] bytes = name.getBytes(UTF_8);
84ccf23a609c01ce08a79f56353a1750f1067749c6cushon        createEntry(jos, name, bytes);
85ccf23a609c01ce08a79f56353a1750f1067749c6cushon      }
86ccf23a609c01ce08a79f56353a1750f1067749c6cushon    }
87ccf23a609c01ce08a79f56353a1750f1067749c6cushon    assertThat(actual(path)).isEqualTo(expected(path));
88ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
89ccf23a609c01ce08a79f56353a1750f1067749c6cushon
9086665ea565aca9132d09988cd8de50920c814f47cushon  private static void createEntry(ZipOutputStream jos, String name, byte[] bytes)
91ccf23a609c01ce08a79f56353a1750f1067749c6cushon      throws IOException {
92ccf23a609c01ce08a79f56353a1750f1067749c6cushon    JarEntry je = new JarEntry(name);
93ccf23a609c01ce08a79f56353a1750f1067749c6cushon    je.setMethod(JarEntry.STORED);
94ccf23a609c01ce08a79f56353a1750f1067749c6cushon    je.setSize(bytes.length);
95ccf23a609c01ce08a79f56353a1750f1067749c6cushon    je.setCrc(Hashing.crc32().hashBytes(bytes).padToLong());
96ccf23a609c01ce08a79f56353a1750f1067749c6cushon    jos.putNextEntry(je);
97ccf23a609c01ce08a79f56353a1750f1067749c6cushon    jos.write(bytes);
98ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
99ccf23a609c01ce08a79f56353a1750f1067749c6cushon
100ccf23a609c01ce08a79f56353a1750f1067749c6cushon  private static Map<String, Long> actual(Path path) throws IOException {
101ccf23a609c01ce08a79f56353a1750f1067749c6cushon    Map<String, Long> result = new LinkedHashMap<>();
102ccf23a609c01ce08a79f56353a1750f1067749c6cushon    for (Zip.Entry e : new Zip.ZipIterable(path)) {
103ccf23a609c01ce08a79f56353a1750f1067749c6cushon      result.put(e.name(), Hashing.goodFastHash(128).hashBytes(e.data()).padToLong());
104ccf23a609c01ce08a79f56353a1750f1067749c6cushon    }
105ccf23a609c01ce08a79f56353a1750f1067749c6cushon    return result;
106ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
107ccf23a609c01ce08a79f56353a1750f1067749c6cushon
108ccf23a609c01ce08a79f56353a1750f1067749c6cushon  private static Map<String, Long> expected(Path path) throws IOException {
109ccf23a609c01ce08a79f56353a1750f1067749c6cushon    Map<String, Long> result = new LinkedHashMap<>();
110ccf23a609c01ce08a79f56353a1750f1067749c6cushon    try (JarFile jf = new JarFile(path.toFile())) {
111ccf23a609c01ce08a79f56353a1750f1067749c6cushon      Enumeration<JarEntry> entries = jf.entries();
112ccf23a609c01ce08a79f56353a1750f1067749c6cushon      while (entries.hasMoreElements()) {
113ccf23a609c01ce08a79f56353a1750f1067749c6cushon        JarEntry je = entries.nextElement();
114ccf23a609c01ce08a79f56353a1750f1067749c6cushon        result.put(
115ccf23a609c01ce08a79f56353a1750f1067749c6cushon            je.getName(),
116ccf23a609c01ce08a79f56353a1750f1067749c6cushon            Hashing.goodFastHash(128)
117ccf23a609c01ce08a79f56353a1750f1067749c6cushon                .hashBytes(ByteStreams.toByteArray(jf.getInputStream(je)))
118ccf23a609c01ce08a79f56353a1750f1067749c6cushon                .padToLong());
119ccf23a609c01ce08a79f56353a1750f1067749c6cushon      }
120ccf23a609c01ce08a79f56353a1750f1067749c6cushon    }
121ccf23a609c01ce08a79f56353a1750f1067749c6cushon    return result;
122ccf23a609c01ce08a79f56353a1750f1067749c6cushon  }
1232c831a8dd79cdc77293246b007dbddd55091b571cushon
1242c831a8dd79cdc77293246b007dbddd55091b571cushon  @Test
1252c831a8dd79cdc77293246b007dbddd55091b571cushon  public void attributes() throws Exception {
1262c831a8dd79cdc77293246b007dbddd55091b571cushon    Path path = temporaryFolder.newFile("test.jar").toPath();
1272c831a8dd79cdc77293246b007dbddd55091b571cushon    Files.delete(path);
1282c831a8dd79cdc77293246b007dbddd55091b571cushon    try (FileSystem fs =
1292c831a8dd79cdc77293246b007dbddd55091b571cushon        FileSystems.newFileSystem(
1302c831a8dd79cdc77293246b007dbddd55091b571cushon            URI.create("jar:file:" + path.toAbsolutePath()), ImmutableMap.of("create", "true"))) {
1312c831a8dd79cdc77293246b007dbddd55091b571cushon      for (int i = 0; i < 3; i++) {
1322c831a8dd79cdc77293246b007dbddd55091b571cushon        String name = "entry" + i;
1332c831a8dd79cdc77293246b007dbddd55091b571cushon        byte[] bytes = name.getBytes(UTF_8);
1342c831a8dd79cdc77293246b007dbddd55091b571cushon        Path entry = fs.getPath(name);
1352c831a8dd79cdc77293246b007dbddd55091b571cushon        Files.write(entry, bytes);
1362c831a8dd79cdc77293246b007dbddd55091b571cushon        Files.setLastModifiedTime(entry, FileTime.fromMillis(0));
1372c831a8dd79cdc77293246b007dbddd55091b571cushon      }
1382c831a8dd79cdc77293246b007dbddd55091b571cushon    }
1392c831a8dd79cdc77293246b007dbddd55091b571cushon    assertThat(actual(path)).isEqualTo(expected(path));
1402c831a8dd79cdc77293246b007dbddd55091b571cushon  }
14186665ea565aca9132d09988cd8de50920c814f47cushon
14286665ea565aca9132d09988cd8de50920c814f47cushon  @Test
14386665ea565aca9132d09988cd8de50920c814f47cushon  public void zipFileCommentsAreSupported() throws Exception {
14486665ea565aca9132d09988cd8de50920c814f47cushon    Path path = temporaryFolder.newFile("test.jar").toPath();
14586665ea565aca9132d09988cd8de50920c814f47cushon    Files.delete(path);
14686665ea565aca9132d09988cd8de50920c814f47cushon    try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(path))) {
14786665ea565aca9132d09988cd8de50920c814f47cushon      createEntry(zos, "hello", "world".getBytes(UTF_8));
14886665ea565aca9132d09988cd8de50920c814f47cushon      zos.setComment("this is a comment");
14986665ea565aca9132d09988cd8de50920c814f47cushon    }
15086665ea565aca9132d09988cd8de50920c814f47cushon    assertThat(actual(path)).isEqualTo(expected(path));
15186665ea565aca9132d09988cd8de50920c814f47cushon  }
15286665ea565aca9132d09988cd8de50920c814f47cushon
15386665ea565aca9132d09988cd8de50920c814f47cushon  @Test
15486665ea565aca9132d09988cd8de50920c814f47cushon  public void malformedComment() throws Exception {
15586665ea565aca9132d09988cd8de50920c814f47cushon    Path path = temporaryFolder.newFile("test.jar").toPath();
15686665ea565aca9132d09988cd8de50920c814f47cushon    Files.delete(path);
15786665ea565aca9132d09988cd8de50920c814f47cushon
15886665ea565aca9132d09988cd8de50920c814f47cushon    try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(path))) {
15986665ea565aca9132d09988cd8de50920c814f47cushon      createEntry(zos, "hello", "world".getBytes(UTF_8));
16086665ea565aca9132d09988cd8de50920c814f47cushon      zos.setComment("this is a comment");
16186665ea565aca9132d09988cd8de50920c814f47cushon    }
16286665ea565aca9132d09988cd8de50920c814f47cushon    Files.write(path, "trailing garbage".getBytes(UTF_8), StandardOpenOption.APPEND);
16386665ea565aca9132d09988cd8de50920c814f47cushon
16486665ea565aca9132d09988cd8de50920c814f47cushon    try {
16586665ea565aca9132d09988cd8de50920c814f47cushon      actual(path);
16686665ea565aca9132d09988cd8de50920c814f47cushon      fail();
16786665ea565aca9132d09988cd8de50920c814f47cushon    } catch (ZipException e) {
16886665ea565aca9132d09988cd8de50920c814f47cushon      assertThat(e).hasMessage("zip file comment length was 33, expected 17");
16986665ea565aca9132d09988cd8de50920c814f47cushon    }
17086665ea565aca9132d09988cd8de50920c814f47cushon  }
171ccf23a609c01ce08a79f56353a1750f1067749c6cushon}
172