1 /* 2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 25 import org.testng.annotations.Test; 26 27 import java.io.IOException; 28 import java.io.OutputStream; 29 import java.net.URI; 30 import java.nio.file.FileSystem; 31 import java.nio.file.FileSystems; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.HashSet; 38 import java.util.Map; 39 import java.util.Set; 40 import java.util.jar.Attributes; 41 import java.util.jar.JarInputStream; 42 import java.util.jar.Manifest; 43 import java.util.zip.ZipEntry; 44 45 import static org.testng.Assert.assertEquals; 46 import static org.testng.Assert.assertNotNull; 47 import static org.testng.Assert.assertNull; 48 49 /** 50 * @test 51 * @bug 8211917 52 * @summary Test that irrespective of which order the META-INF/MANIFEST.MF gets added using the FileSystem APIs, 53 * the underlying zipfs implementation always places it at the start of the zip to allow for java.util.jar.JarInputStream 54 * to be able to locate it. 55 * @run testng ZipFSManifestOrderTest 56 */ 57 public class ZipFSManifestOrderTest { 58 59 private static final String MANIFEST_MAIN_CLASS = "foo.bar.Baz"; 60 private static final String[] EXPECTED_PATHS = {"hello.txt", "META-INF/bar/world.txt", "META-INF/greeting.txt"}; 61 62 /** 63 * Use the FileSystem APIs to first add the manifest and then the rest of the files, to a jar. Then verify that the 64 * JarInputStream finds the manifest and the other expected entries. 65 * 66 * @throws Exception 67 */ 68 @Test 69 public void testJarWithManifestAddedFirst() throws Exception { 70 final Path jarPath = Paths.get("manifest-first.jar"); 71 Files.deleteIfExists(jarPath); 72 final Map<String, String> options = Collections.singletonMap("create", "true"); 73 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), options)) { 74 // first write the manifest 75 final Path manifestPath = zipFs.getPath("META-INF", "MANIFEST.MF"); 76 writeManifest(manifestPath); 77 78 // now write some other files 79 for (final String path : EXPECTED_PATHS) { 80 writeTxtFile(zipFs.getPath(path)); 81 } 82 } 83 verifyWithJarInputStream(jarPath, true); 84 85 // now repeat the whole test again, on the same jar, but this time update the jar instead of creating it new 86 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), Collections.emptyMap())) { 87 // first write the manifest 88 final Path manifestPath = zipFs.getPath("META-INF", "MANIFEST.MF"); 89 writeManifest(manifestPath); 90 91 // now write some other files 92 for (final String path : EXPECTED_PATHS) { 93 writeTxtFile(zipFs.getPath(path)); 94 } 95 } 96 verifyWithJarInputStream(jarPath, true); 97 } 98 99 /** 100 * Use the FileSystem APIs to add the manifest last, to a jar. Then verify that the JarInputStream finds the 101 * manifest and the other expected entries. 102 * 103 * @throws Exception 104 */ 105 @Test 106 public void testJarWithManifestAddedLast() throws Exception { 107 final Path jarPath = Paths.get("manifest-last.jar"); 108 Files.deleteIfExists(jarPath); 109 final Map<String, String> options = Collections.singletonMap("create", "true"); 110 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), options)) { 111 // first write other files 112 for (final String path : EXPECTED_PATHS) { 113 writeTxtFile(zipFs.getPath(path)); 114 } 115 // now write the manifest 116 final Path manifestPath = zipFs.getPath("META-INF", "MANIFEST.MF"); 117 writeManifest(manifestPath); 118 119 } 120 verifyWithJarInputStream(jarPath, true); 121 122 // now repeat the whole test again, on the same jar, but this time update the jar instead of creating it new 123 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), Collections.emptyMap())) { 124 // first write other files 125 for (final String path : EXPECTED_PATHS) { 126 writeTxtFile(zipFs.getPath(path)); 127 } 128 // now write the manifest 129 final Path manifestPath = zipFs.getPath("META-INF", "MANIFEST.MF"); 130 writeManifest(manifestPath); 131 132 } 133 verifyWithJarInputStream(jarPath, true); 134 } 135 136 /** 137 * Use the FileSystem APIs to add the manifest ordered somewhere in between among other files in a jar. Then verify that 138 * the JarInputStream finds the manifest and the other expected entries. 139 * 140 * @throws Exception 141 */ 142 @Test 143 public void testJarWithManifestAddedInBetween() throws Exception { 144 final Path jarPath = Paths.get("manifest-in-between.jar"); 145 Files.deleteIfExists(jarPath); 146 final Map<String, String> options = Collections.singletonMap("create", "true"); 147 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), options)) { 148 // first write other files 149 for (int i = 0; i < EXPECTED_PATHS.length; i++) { 150 writeTxtFile(zipFs.getPath(EXPECTED_PATHS[i])); 151 if (i == EXPECTED_PATHS.length - 2) { 152 // write out the manifest 153 final Path manifestPath = zipFs.getPath("META-INF", "MANIFEST.MF"); 154 writeManifest(manifestPath); 155 } 156 } 157 } 158 verifyWithJarInputStream(jarPath, true); 159 160 // now repeat the whole test again, on the same jar, but this time update the jar instead of creating it new 161 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), Collections.emptyMap())) { 162 // first write other files 163 for (int i = 0; i < EXPECTED_PATHS.length; i++) { 164 writeTxtFile(zipFs.getPath(EXPECTED_PATHS[i])); 165 if (i == EXPECTED_PATHS.length - 2) { 166 // write out the manifest 167 final Path manifestPath = zipFs.getPath("META-INF", "MANIFEST.MF"); 168 writeManifest(manifestPath); 169 } 170 } 171 } 172 verifyWithJarInputStream(jarPath, true); 173 } 174 175 /** 176 * Use the FileSystem APIs to create a jar without any manifest. Then verify that the JarInputStream can find the 177 * expected entries in the jar and also doesn't find any manifest in it. 178 * 179 * @throws Exception 180 */ 181 @Test 182 public void testJarWithNoManifest() throws Exception { 183 final Path jarPath = Paths.get("no-manifest.jar"); 184 Files.deleteIfExists(jarPath); 185 final Map<String, String> options = Collections.singletonMap("create", "true"); 186 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), options)) { 187 for (int i = 0; i < EXPECTED_PATHS.length; i++) { 188 writeTxtFile(zipFs.getPath(EXPECTED_PATHS[i])); 189 } 190 } 191 verifyWithJarInputStream(jarPath, false); 192 193 // now repeat the whole test again, on the same jar, but this time update the jar instead of creating it new 194 try (final FileSystem zipFs = FileSystems.newFileSystem(URI.create("jar:" + jarPath.toUri()), Collections.emptyMap())) { 195 for (int i = 0; i < EXPECTED_PATHS.length; i++) { 196 writeTxtFile(zipFs.getPath(EXPECTED_PATHS[i])); 197 } 198 } 199 verifyWithJarInputStream(jarPath, false); 200 } 201 202 private static void writeTxtFile(final Path filePath) throws IOException { 203 if (filePath.getParent() != null) { 204 Files.createDirectories(filePath.getParent()); 205 } 206 try (final OutputStream os = Files.newOutputStream(filePath)) { 207 os.write(new byte[]{'a', 'b', 'c'}); 208 } 209 } 210 211 private static void writeManifest(final Path manifestPath) throws IOException { 212 if (manifestPath.getParent() != null) { 213 Files.createDirectories(manifestPath.getParent()); 214 } 215 try (final OutputStream os = Files.newOutputStream(manifestPath)) { 216 final Manifest manifest = new Manifest(); 217 final Attributes attributes = manifest.getMainAttributes(); 218 attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0"); 219 attributes.put(Attributes.Name.MAIN_CLASS, MANIFEST_MAIN_CLASS); 220 manifest.write(os); 221 } 222 } 223 224 /** 225 * Verify that the given jar can be opened using JarInputStream and the JarInputStream is 226 * able to {@link JarInputStream#getManifest() locate the manifest}. Additionally, also 227 * verify that certain other expected entries in the jar, are found by the JarInputStream 228 * 229 * @param jar Path to the jar 230 * @throws IOException 231 */ 232 private static void verifyWithJarInputStream(final Path jar, final boolean expectManifest) throws IOException { 233 try (final JarInputStream jaris = new JarInputStream(Files.newInputStream(jar))) { 234 // verify the manifest is present 235 final Manifest manifest = jaris.getManifest(); 236 if (expectManifest) { 237 assertNotNull(manifest, "JarInputStream couldn't locate a manifest in " + jar); 238 final String mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS); 239 assertEquals(mainClass, MANIFEST_MAIN_CLASS, "Unexpected Main-Class in manifest of " + jar); 240 } else { 241 assertNull(manifest, "JarInputStream unexpectedly found a manifest in " + jar); 242 } 243 // verify the rest of the expected entries are present 244 final Set<String> missing = new HashSet<>(Arrays.asList(EXPECTED_PATHS)); 245 ZipEntry entry = jaris.getNextEntry(); 246 while (entry != null) { 247 missing.remove(entry.getName()); 248 entry = jaris.getNextEntry(); 249 } 250 assertEquals(missing.size(), 0, "Missing entries " + missing + " in jar " + jar); 251 } 252 } 253 }