changeset 13239:5bdf60cf3bb7

8195874: Improve jar specification adherence Summary: Also reviewed by Chris Ries <chris.ries@oracle.com> Reviewed-by: alanb, mchung, rriggs
author igerasim
date Thu, 07 Jun 2018 15:00:19 -0700
parents 4c1076450d08
children 355822b67ab3
files src/share/classes/sun/misc/URLClassPath.java test/lib/testlibrary/jdk/testlibrary/JarUtils.java
diffstat 2 files changed, 133 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/misc/URLClassPath.java	Tue Jun 05 20:30:21 2018 -0700
+++ b/src/share/classes/sun/misc/URLClassPath.java	Thu Jun 07 15:00:19 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -37,6 +37,7 @@
 import java.util.jar.Attributes.Name;
 import java.net.JarURLConnection;
 import java.net.MalformedURLException;
+import java.net.URI;
 import java.net.URL;
 import java.net.URLClassLoader;
 import java.net.URLConnection;
@@ -69,6 +70,8 @@
     private static final boolean DEBUG_LOOKUP_CACHE;
     private static final boolean DISABLE_JAR_CHECKING;
     private static final boolean DISABLE_ACC_CHECKING;
+    private static final boolean DISABLE_CP_URL_CHECK;
+    private static final boolean DEBUG_CP_URL_CHECK;
 
     static {
         JAVA_VERSION = java.security.AccessController.doPrivileged(
@@ -84,6 +87,13 @@
         p = AccessController.doPrivileged(
             new GetPropertyAction("jdk.net.URLClassPath.disableRestrictedPermissions"));
         DISABLE_ACC_CHECKING = p != null ? p.equals("true") || p.equals("") : false;
+
+        // This property will be removed in a later release
+        p = AccessController.doPrivileged(
+            new GetPropertyAction("jdk.net.URLClassPath.disableClassPathURLCheck"));
+
+        DISABLE_CP_URL_CHECK = p != null ? p.equals("true") || p.isEmpty() : false;
+        DEBUG_CP_URL_CHECK = "debug".equals(p);
     }
 
     /* The original search path of URLs. */
@@ -1217,11 +1227,51 @@
             int i = 0;
             while (st.hasMoreTokens()) {
                 String path = st.nextToken();
-                urls[i] = new URL(base, path);
-                i++;
+                URL url = DISABLE_CP_URL_CHECK ? new URL(base, path) : safeResolve(base, path);
+                if (url != null) {
+                    urls[i] = url;
+                    i++;
+                }
+            }
+            if (i == 0) {
+                urls = null;
+            } else if (i != urls.length) {
+                // Truncate nulls from end of array
+                urls = Arrays.copyOf(urls, i);
             }
             return urls;
         }
+
+        /*
+         * Return a URL for the given path resolved against the base URL, or
+         * null if the resulting URL is invalid.
+         */
+        static URL safeResolve(URL base, String path) {
+            String child = path.replace(File.separatorChar, '/');
+            try {
+                if (!URI.create(child).isAbsolute()) {
+                    URL url = new URL(base, child);
+                    if (base.getProtocol().equalsIgnoreCase("file")) {
+                        return url;
+                    } else {
+                        String bp = base.getPath();
+                        String urlp = url.getPath();
+                        int pos = bp.lastIndexOf('/');
+                        if (pos == -1) {
+                            pos = bp.length() - 1;
+                        }
+                        if (urlp.regionMatches(0, bp, 0, pos + 1)
+                            && urlp.indexOf("..", pos) == -1) {
+                            return url;
+                        }
+                    }
+                }
+            } catch (MalformedURLException | IllegalArgumentException e) {}
+            if (DEBUG_CP_URL_CHECK) {
+                System.err.println("Class-Path entry: \"" + path + "\" ignored in JAR file " + base);
+            }
+            return null;
+        }
     }
 
     /*
--- a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java	Tue Jun 05 20:30:21 2018 -0700
+++ b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java	Thu Jun 07 15:00:19 2018 -0700
@@ -24,16 +24,20 @@
 package jdk.testlibrary;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
@@ -46,6 +50,72 @@
 public final class JarUtils {
 
     /**
+     * Creates a JAR file.
+     *
+     * Equivalent to {@code jar cfm <jarfile> <manifest> -C <dir> file...}
+     *
+     * The input files are resolved against the given directory. Any input
+     * files that are directories are processed recursively.
+     */
+    public static void createJarFile(Path jarfile, Manifest man, Path dir, Path... file)
+        throws IOException
+    {
+        // create the target directory
+        Path parent = jarfile.getParent();
+        if (parent != null)
+            Files.createDirectories(parent);
+
+        List<Path> entries = new ArrayList<>();
+        for (Path entry : file) {
+            Files.find(dir.resolve(entry), Integer.MAX_VALUE,
+                        (p, attrs) -> attrs.isRegularFile())
+                    .map(e -> dir.relativize(e))
+                    .forEach(entries::add);
+        }
+
+        try (OutputStream out = Files.newOutputStream(jarfile);
+             JarOutputStream jos = new JarOutputStream(out))
+        {
+            if (man != null) {
+                JarEntry je = new JarEntry(JarFile.MANIFEST_NAME);
+                jos.putNextEntry(je);
+                man.write(jos);
+                jos.closeEntry();
+            }
+
+            for (Path entry : entries) {
+                String name = toJarEntryName(entry);
+                jos.putNextEntry(new JarEntry(name));
+                Files.copy(dir.resolve(entry), jos);
+                jos.closeEntry();
+            }
+        }
+    }
+
+   /**
+     * Creates a JAR file.
+     *
+     * Equivalent to {@code jar cf <jarfile>  -C <dir> file...}
+     *
+     * The input files are resolved against the given directory. Any input
+     * files that are directories are processed recursively.
+     */
+    public static void createJarFile(Path jarfile, Path dir, Path... file)
+        throws IOException
+    {
+        createJarFile(jarfile, null, dir, file);
+    }
+
+    /**
+     * Creates a JAR file from the contents of a directory.
+     *
+     * Equivalent to {@code jar cf <jarfile> -C <dir> .}
+     */
+    public static void createJarFile(Path jarfile, Path dir) throws IOException {
+        createJarFile(jarfile, dir, Paths.get("."));
+    }
+
+    /**
      * Create jar file with specified files. If a specified file does not exist,
      * a new jar entry will be created with the file name itself as the content.
      */
@@ -196,4 +266,14 @@
             }
         }
     }
+
+    /**
+     * Map a file path to the equivalent name in a JAR file
+     */
+    private static String toJarEntryName(Path file) {
+        Path normalized = file.normalize();
+        return normalized.subpath(0, normalized.getNameCount())  // drop root
+                .toString()
+                .replace(File.separatorChar, '/');
+    }
 }