# HG changeset patch # User redestad # Date 1489516400 -3600 # Tue Mar 14 19:33:20 2017 +0100 # Node ID 025bbf5097e8ba36b379bc2d43a84bd5efd9061c # Parent 341a471ff66263913b245cc8cfe0894bb7011872 8176709: JarFileSystem::isMultiReleaseJar is incorrect Reviewed-by: alanb, sherman, psandoz, mchung diff --git a/src/java.base/share/classes/java/util/jar/JarFile.java b/src/java.base/share/classes/java/util/jar/JarFile.java --- a/src/java.base/share/classes/java/util/jar/JarFile.java +++ b/src/java.base/share/classes/java/util/jar/JarFile.java @@ -925,7 +925,7 @@ * Since there are no repeated substring in our search strings, * the good suffix shifts can be replaced with a comparison. */ - private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) { + private static int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) { int len = src.length; int last = b.length - len; int i = 0; @@ -972,6 +972,8 @@ CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT) != -1; // is this a multi-release jar file if (MULTI_RELEASE_ENABLED) { + // Keep this implementation up to date with + // JarFileSystem::isMultiReleaseJar int i = match(MULTIRELEASE_CHARS, b, MULTIRELEASE_LASTOCC, MULTIRELEASE_OPTOSFT); if (i != -1) { diff --git a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java --- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java +++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/JarFileSystem.java @@ -88,14 +88,108 @@ private boolean isMultiReleaseJar() { try (InputStream is = newInputStream(getBytes("/META-INF/MANIFEST.MF"))) { - return (new Manifest(is)).getMainAttributes() - .containsKey(new Attributes.Name("Multi-Release")); - // fixme change line above after JarFile integration to contain Attributes.Name.MULTI_RELEASE + byte[] b = is.readAllBytes(); + // Keep this implementation up to date with + // JarFile::checkForSpecialAttributes + int i = match(MULTIRELEASE_CHARS, b, MULTIRELEASE_LASTOCC, + MULTIRELEASE_OPTOSFT); + if (i != -1) { + i += MULTIRELEASE_CHARS.length; + if (i < b.length) { + byte c = b[i++]; + // Check that the value is followed by a newline + // and does not have a continuation + if (c == '\n' && + (i == b.length || b[i] != ' ')) { + return true; + } else if (c == '\r') { + if (i == b.length) { + return true; + } else { + c = b[i++]; + if (c == '\n') { + if (i == b.length || b[i] != ' ') { + return true; + } + } else if (c != ' ') { + return true; + } + } + } + } + } + return false; } catch (IOException x) { return false; } } + private static final byte[] MULTIRELEASE_CHARS = + {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':', + ' ', 'T', 'R', 'U', 'E'}; + + // The bad character shift for "multi-release: true" + private static final byte[] MULTIRELEASE_LASTOCC; + + // The good suffix shift for "multi-release: true" + private static final byte[] MULTIRELEASE_OPTOSFT; + + static { + MULTIRELEASE_LASTOCC = new byte[64]; + MULTIRELEASE_OPTOSFT = new byte[19]; + MULTIRELEASE_LASTOCC[(int)'M' - 32] = 1; + MULTIRELEASE_LASTOCC[(int)'I' - 32] = 5; + MULTIRELEASE_LASTOCC[(int)'-' - 32] = 6; + MULTIRELEASE_LASTOCC[(int)'L' - 32] = 9; + MULTIRELEASE_LASTOCC[(int)'A' - 32] = 11; + MULTIRELEASE_LASTOCC[(int)'S' - 32] = 12; + MULTIRELEASE_LASTOCC[(int)':' - 32] = 14; + MULTIRELEASE_LASTOCC[(int)' ' - 32] = 15; + MULTIRELEASE_LASTOCC[(int)'T' - 32] = 16; + MULTIRELEASE_LASTOCC[(int)'R' - 32] = 17; + MULTIRELEASE_LASTOCC[(int)'U' - 32] = 18; + MULTIRELEASE_LASTOCC[(int)'E' - 32] = 19; + for (int i = 0; i < 17; i++) { + MULTIRELEASE_OPTOSFT[i] = 19; + } + MULTIRELEASE_OPTOSFT[17] = 6; + MULTIRELEASE_OPTOSFT[18] = 1; + } + + /** + * Returns true if the pattern {@code src} is found in {@code b}. + * The {@code lastOcc} array is the precomputed bad character shifts. + * Since there are no repeated substring in our search strings, + * the good suffix shifts can be replaced with a comparison. + */ + private static int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) { + int len = src.length; + int last = b.length - len; + int i = 0; + next: + while (i <= last) { + for (int j = (len - 1); j >= 0; j--) { + byte c = b[i + j]; + if (c >= ' ' && c <= 'z') { + if (c >= 'a') c -= 32; // Canonicalize + + if (c != src[j]) { + // no match + int badShift = lastOcc[c - 32]; + i += Math.max(j + 1 - badShift, optoSft[j]); + continue next; + } + } else { + // no match, character not valid for name + i += len; + continue next; + } + } + return i; + } + return -1; + } + /** * create a map of aliases for versioned entries, for example: * version/PackagePrivate.class -> META-INF/versions/9/version/PackagePrivate.class diff --git a/test/jdk/nio/zipfs/MultiReleaseJarTest.java b/test/jdk/nio/zipfs/MultiReleaseJarTest.java --- a/test/jdk/nio/zipfs/MultiReleaseJarTest.java +++ b/test/jdk/nio/zipfs/MultiReleaseJarTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8144355 8144062 + * @bug 8144355 8144062 8176709 * @summary Test aliasing additions to ZipFileSystem for multi-release jar files * @library /lib/testlibrary/java/util/jar * @build Compiler JarBuilder CreateMultiReleaseTestJars @@ -42,6 +42,7 @@ import java.nio.file.*; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.testng.Assert; import org.testng.annotations.*; @@ -50,6 +51,7 @@ final private int MAJOR_VERSION = Runtime.version().major(); final private String userdir = System.getProperty("user.dir","."); + final private CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars(); final private Map stringEnv = new HashMap<>(); final private Map integerEnv = new HashMap<>(); final private Map versionEnv = new HashMap<>(); @@ -63,7 +65,6 @@ @BeforeClass public void initialize() throws Exception { - CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars(); creator.compileEntries(); creator.buildUnversionedJar(); creator.buildMultiReleaseJar(); @@ -187,6 +188,44 @@ } } + @Test + public void testIsMultiReleaseJar() throws Exception { + testCustomMultiReleaseValue("true", true); + testCustomMultiReleaseValue("true\r\nOther: value", true); + testCustomMultiReleaseValue("true\nOther: value", true); + testCustomMultiReleaseValue("true\rOther: value", true); + + testCustomMultiReleaseValue("false", false); + testCustomMultiReleaseValue(" true", false); + testCustomMultiReleaseValue("true ", false); + testCustomMultiReleaseValue("true\n ", false); + testCustomMultiReleaseValue("true\r ", false); + testCustomMultiReleaseValue("true\n true", false); + testCustomMultiReleaseValue("true\r\n true", false); + } + + private static final AtomicInteger JAR_COUNT = new AtomicInteger(0); + + private void testCustomMultiReleaseValue(String value, boolean expected) + throws Exception { + String fileName = "custom-mr" + JAR_COUNT.incrementAndGet() + ".jar"; + creator.buildCustomMultiReleaseJar(fileName, value, Map.of(), + /*addEntries*/true); + + Map env = Map.of("multi-release", "runtime"); + Path filePath = Paths.get(userdir, fileName); + String ssp = filePath.toUri().toString(); + URI customJar = new URI("jar", ssp , null); + try (FileSystem fs = FileSystems.newFileSystem(customJar, env)) { + if (expected) { + Assert.assertTrue(readAndCompare(fs, MAJOR_VERSION)); + } else { + Assert.assertTrue(readAndCompare(fs, 8)); + } + } + Files.delete(filePath); + } + private static class ByteArrayClassLoader extends ClassLoader { final private FileSystem fs; diff --git a/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java b/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java --- a/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java +++ b/test/lib/testlibrary/java/util/jar/CreateMultiReleaseTestJars.java @@ -109,9 +109,17 @@ public void buildCustomMultiReleaseJar(String filename, String multiReleaseValue, Map extraAttributes) throws IOException { + buildCustomMultiReleaseJar(filename, multiReleaseValue, extraAttributes, false); + } + + public void buildCustomMultiReleaseJar(String filename, String multiReleaseValue, + Map extraAttributes, boolean addEntries) throws IOException { JarBuilder jb = new JarBuilder(filename); extraAttributes.entrySet() .forEach(entry -> jb.addAttribute(entry.getKey(), entry.getValue())); + if (addEntries) { + addEntries(jb); + } jb.addAttribute("Multi-Release", multiReleaseValue); jb.build(); }