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.fail; 22 23 import com.google.common.collect.ImmutableMap; 24 import com.google.common.hash.Hashing; 25 import com.google.common.io.ByteStreams; 26 import java.io.IOException; 27 import java.net.URI; 28 import java.nio.file.FileSystem; 29 import java.nio.file.FileSystems; 30 import java.nio.file.Files; 31 import java.nio.file.Path; 32 import java.nio.file.StandardOpenOption; 33 import java.nio.file.attribute.FileTime; 34 import java.util.Enumeration; 35 import java.util.LinkedHashMap; 36 import java.util.Map; 37 import java.util.jar.JarEntry; 38 import java.util.jar.JarFile; 39 import java.util.jar.JarOutputStream; 40 import java.util.zip.ZipException; 41 import java.util.zip.ZipOutputStream; 42 import org.junit.Rule; 43 import org.junit.Test; 44 import org.junit.rules.TemporaryFolder; 45 import org.junit.runner.RunWith; 46 import org.junit.runners.JUnit4; 47 48 /** {@link Zip}Test */ 49 @RunWith(JUnit4.class) 50 public class ZipTest { 51 52 @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); 53 54 @Test 55 public void testEntries() throws IOException { 56 testEntries(1000); 57 } 58 59 @Test 60 public void zip64_testEntries() throws IOException { 61 testEntries(70000); 62 } 63 64 @Test 65 public void compression() throws IOException { 66 Path path = temporaryFolder.newFile("test.jar").toPath(); 67 try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) { 68 for (int i = 0; i < 2; i++) { 69 String name = "entry" + i; 70 byte[] bytes = name.getBytes(UTF_8); 71 jos.putNextEntry(new JarEntry(name)); 72 jos.write(bytes); 73 } 74 } 75 assertThat(actual(path)).isEqualTo(expected(path)); 76 } 77 78 private void testEntries(int entries) throws IOException { 79 Path path = temporaryFolder.newFile("test.jar").toPath(); 80 try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(path))) { 81 for (int i = 0; i < entries; i++) { 82 String name = "entry" + i; 83 byte[] bytes = name.getBytes(UTF_8); 84 createEntry(jos, name, bytes); 85 } 86 } 87 assertThat(actual(path)).isEqualTo(expected(path)); 88 } 89 90 private static void createEntry(ZipOutputStream jos, String name, byte[] bytes) 91 throws IOException { 92 JarEntry je = new JarEntry(name); 93 je.setMethod(JarEntry.STORED); 94 je.setSize(bytes.length); 95 je.setCrc(Hashing.crc32().hashBytes(bytes).padToLong()); 96 jos.putNextEntry(je); 97 jos.write(bytes); 98 } 99 100 private static Map<String, Long> actual(Path path) throws IOException { 101 Map<String, Long> result = new LinkedHashMap<>(); 102 for (Zip.Entry e : new Zip.ZipIterable(path)) { 103 result.put(e.name(), Hashing.goodFastHash(128).hashBytes(e.data()).padToLong()); 104 } 105 return result; 106 } 107 108 private static Map<String, Long> expected(Path path) throws IOException { 109 Map<String, Long> result = new LinkedHashMap<>(); 110 try (JarFile jf = new JarFile(path.toFile())) { 111 Enumeration<JarEntry> entries = jf.entries(); 112 while (entries.hasMoreElements()) { 113 JarEntry je = entries.nextElement(); 114 result.put( 115 je.getName(), 116 Hashing.goodFastHash(128) 117 .hashBytes(ByteStreams.toByteArray(jf.getInputStream(je))) 118 .padToLong()); 119 } 120 } 121 return result; 122 } 123 124 @Test 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:file:" + path.toAbsolutePath()), 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 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 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.write(path, "trailing garbage".getBytes(UTF_8), StandardOpenOption.APPEND); 163 164 try { 165 actual(path); 166 fail(); 167 } catch (ZipException e) { 168 assertThat(e).hasMessage("zip file comment length was 33, expected 17"); 169 } 170 } 171 } 172