changeset 13159:4e0712371a12

8189969: Manifest better manifest entries Reviewed-by: weijun, igerasim
author coffeys
date Mon, 15 Jan 2018 13:17:33 +0000
parents 5684e884a865
children c8f20e312aa8
files src/share/classes/sun/security/util/ManifestDigester.java test/javax/security/auth/Subject/doAs/NestedActions.java test/lib/testlibrary/jdk/testlibrary/JarUtils.java test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java test/lib/testlibrary/jdk/testlibrary/Utils.java
diffstat 5 files changed, 207 insertions(+), 109 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/sun/security/util/ManifestDigester.java	Fri Jan 12 22:16:44 2018 +0000
+++ b/src/share/classes/sun/security/util/ManifestDigester.java	Mon Jan 15 13:17:33 2018 +0000
@@ -26,8 +26,10 @@
 package sun.security.util;
 
 import java.security.*;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.io.ByteArrayOutputStream;
+import java.util.List;
 
 /**
  * This class is used to compute digests on sections of the Manifest.
@@ -39,7 +41,7 @@
     /** the raw bytes of the manifest */
     private byte rawBytes[];
 
-    /** the offset/length pair for a section */
+    /** the entries grouped by names */
     private HashMap<String, Entry> entries; // key is a UTF-8 string
 
     /** state returned by findSection */
@@ -120,8 +122,8 @@
             return; // XXX: exception?
 
         // create an entry for main attributes
-        entries.put(MF_MAIN_ATTRS,
-                new Entry(0, pos.endOfSection + 1, pos.startOfNext, rawBytes));
+        entries.put(MF_MAIN_ATTRS, new Entry().addSection(
+                new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));
 
         int start = pos.startOfNext;
         while(findSection(start, pos)) {
@@ -167,9 +169,10 @@
                             }
                         }
 
-                        entries.put(nameBuf.toString(),
-                            new Entry(start, sectionLen, sectionLenWithBlank,
-                                rawBytes));
+                        entries.computeIfAbsent(nameBuf.toString(),
+                                                dummy -> new Entry())
+                                .addSection(new Section(start, sectionLen,
+                                        sectionLenWithBlank, rawBytes));
 
                     } catch (java.io.UnsupportedEncodingException uee) {
                         throw new IllegalStateException(
@@ -192,13 +195,52 @@
     }
 
     public static class Entry {
+
+        // One Entry for one name, and one name can have multiple sections.
+        // According to the JAR File Specification: "If there are multiple
+        // individual sections for the same file entry, the attributes in
+        // these sections are merged."
+        private List<Section> sections = new ArrayList<>();
+        boolean oldStyle;
+
+        private Entry addSection(Section sec)
+        {
+            sections.add(sec);
+            return this;
+        }
+
+        public byte[] digest(MessageDigest md)
+        {
+            md.reset();
+            for (Section sec : sections) {
+                if (oldStyle) {
+                    Section.doOldStyle(md, sec.rawBytes, sec.offset, sec.lengthWithBlankLine);
+                } else {
+                    md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);
+                }
+            }
+            return md.digest();
+        }
+
+        /** Netscape doesn't include the new line. Intel and JavaSoft do */
+
+        public byte[] digestWorkaround(MessageDigest md)
+        {
+            md.reset();
+            for (Section sec : sections) {
+                md.update(sec.rawBytes, sec.offset, sec.length);
+            }
+            return md.digest();
+        }
+    }
+
+    private static class Section {
         int offset;
         int length;
         int lengthWithBlankLine;
         byte[] rawBytes;
-        boolean oldStyle;
 
-        public Entry(int offset, int length,
+        public Section(int offset, int length,
                      int lengthWithBlankLine, byte[] rawBytes)
         {
             this.offset = offset;
@@ -207,18 +249,7 @@
             this.rawBytes = rawBytes;
         }
 
-        public byte[] digest(MessageDigest md)
-        {
-            md.reset();
-            if (oldStyle) {
-                doOldStyle(md,rawBytes, offset, lengthWithBlankLine);
-            } else {
-                md.update(rawBytes, offset, lengthWithBlankLine);
-            }
-            return md.digest();
-        }
-
-        private void doOldStyle(MessageDigest md,
+        private static void doOldStyle(MessageDigest md,
                                 byte[] bytes,
                                 int offset,
                                 int length)
@@ -242,16 +273,6 @@
             }
             md.update(bytes, start, i-start);
         }
-
-
-        /** Netscape doesn't include the new line. Intel and JavaSoft do */
-
-        public byte[] digestWorkaround(MessageDigest md)
-        {
-            md.reset();
-            md.update(rawBytes, offset, length);
-            return md.digest();
-        }
     }
 
     public Entry get(String name, boolean oldStyle) {
--- a/test/javax/security/auth/Subject/doAs/NestedActions.java	Fri Jan 12 22:16:44 2018 +0000
+++ b/test/javax/security/auth/Subject/doAs/NestedActions.java	Mon Jan 15 13:17:33 2018 +0000
@@ -23,7 +23,6 @@
  */
 
 import jdk.testlibrary.ProcessTools;
-import jdk.testlibrary.JarUtils;
 
 import javax.security.auth.Subject;
 import javax.security.auth.x500.X500Principal;
@@ -101,7 +100,7 @@
     public static void main(String[] args) throws IOException {
         if (args.length > 0) {
             if ("jar".equals(args[0]) && args.length > 2) {
-                JarUtils.createJar(args[1], Paths.get(TEST_CLASSES + FS),
+                createJar(args[1],
                     Arrays.copyOfRange(args, 2, args.length));
             } else {
                 runJava(args);
@@ -111,6 +110,21 @@
         }
     }
 
+    static void createJar(String dest, String... files) throws IOException {
+        System.out.println("Create " + dest + " with the following content:");
+        try (JarOutputStream jos = new JarOutputStream(
+                new FileOutputStream(dest), new Manifest())) {
+            for (String file : files) {
+                System.out.println("  " + file);
+                jos.putNextEntry(new JarEntry(file));
+                try (FileInputStream fis = new FileInputStream(
+                        TEST_CLASSES + FS + file)) {
+                    jdk.testlibrary.Utils.transferTo(fis, jos);
+                }
+            }
+        }
+    }
+
 
 
     static void runJava(String[] args) {
--- a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java	Fri Jan 12 22:16:44 2018 +0000
+++ b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java	Mon Jan 15 13:17:33 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 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
@@ -23,15 +23,17 @@
 
 package jdk.testlibrary;
 
-import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
-import java.util.ArrayList;
+import java.nio.file.Paths;
 import java.util.Enumeration;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.jar.JarOutputStream;
@@ -42,31 +44,24 @@
  */
 public final class JarUtils {
 
-
     /**
      * 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.
      */
-    public static void createJar(String dest, Path filesLocation,
-                                 String... fileNames) throws IOException {
+    public static void createJar(String dest, String... files)
+            throws IOException {
         try (JarOutputStream jos = new JarOutputStream(
                 new FileOutputStream(dest), new Manifest())) {
-            for (String fileName : fileNames) {
+            for (String file : files) {
                 System.out.println(String.format("Adding %s to %s",
-                        fileName, dest));
+                        file, dest));
 
                 // add an archive entry, and write a file
-                jos.putNextEntry(new JarEntry(fileName));
-                File file;
-                if (filesLocation != null) {
-                    file = filesLocation.resolve(fileName).toFile();
-                } else {
-                    file = new File(fileName);
-                }
+                jos.putNextEntry(new JarEntry(file));
                 try (FileInputStream fis = new FileInputStream(file)) {
-                    Utils.transferBetweenStreams(fis, jos);
+                    Utils.transferTo(fis, jos);
                 } catch (FileNotFoundException e) {
-                    jos.write(fileName.getBytes());
+                    jos.write(file.getBytes());
                 }
             }
         }
@@ -74,14 +69,6 @@
     }
 
     /**
-     * Create jar file with specified files from current directory.
-     */
-    public static void createJar(String dest, String... files)
-            throws IOException {
-        createJar(dest, null, files);
-    }
-
-    /**
      * Add or remove specified files to existing jar file. If a specified file
      * to be updated or added does not exist, the jar entry will be created
      * with the file name itself as the content.
@@ -96,70 +83,93 @@
      */
     public static void updateJar(String src, String dest, String... files)
             throws IOException {
+        Map<String,Object> changes = new HashMap<>();
+        boolean update = true;
+        for (String file : files) {
+            if (file.equals("-")) {
+                update = false;
+            } else if (update) {
+                try {
+                    Path p = Paths.get(file);
+                    if (Files.exists(p)) {
+                        changes.put(file, p);
+                    } else {
+                        changes.put(file, file);
+                    }
+                } catch (InvalidPathException e) {
+                    // Fallback if file not a valid Path.
+                    changes.put(file, file);
+                }
+            } else {
+                changes.put(file, Boolean.FALSE);
+            }
+        }
+        updateJar(src, dest, changes);
+    }
+
+    /**
+     * Update content of a jar file.
+     *
+     * @param src the original jar file name
+     * @param dest the new jar file name
+     * @param changes a map of changes, key is jar entry name, value is content.
+     *                Value can be Path, byte[] or String. If key exists in
+     *                src but value is Boolean FALSE. The entry is removed.
+     *                Existing entries in src not a key is unmodified.
+     * @throws IOException
+     */
+    public static void updateJar(String src, String dest,
+                                 Map<String,Object> changes)
+            throws IOException {
+
+        // What if input changes is immutable?
+        changes = new HashMap<>(changes);
+
+        System.out.printf("Creating %s from %s...\n", dest, src);
         try (JarOutputStream jos = new JarOutputStream(
                 new FileOutputStream(dest))) {
 
-            // copy each old entry into destination unless the entry name
-            // is in the updated list
-            List<String> updatedFiles = new ArrayList<>();
             try (JarFile srcJarFile = new JarFile(src)) {
                 Enumeration<JarEntry> entries = srcJarFile.entries();
                 while (entries.hasMoreElements()) {
                     JarEntry entry = entries.nextElement();
                     String name = entry.getName();
-                    boolean found = false;
-                    boolean update = true;
-                    for (String file : files) {
-                        if (file.equals("-")) {
-                            update = false;
-                        } else if (name.equals(file)) {
-                            updatedFiles.add(file);
-                            found = true;
-                            break;
-                        }
-                    }
-
-                    if (found) {
-                        if (update) {
-                        System.out.println(String.format("Updating %s with %s",
-                                dest, name));
-                        jos.putNextEntry(new JarEntry(name));
-                        try (FileInputStream fis = new FileInputStream(name)) {
-                            Utils.transferBetweenStreams(fis, jos);
-                            } catch (FileNotFoundException e) {
-                                jos.write(name.getBytes());
-                            }
-                        } else {
-                            System.out.println(String.format("Removing %s from %s",
-                                    name, dest));
-                        }
+                    if (changes.containsKey(name)) {
+                        System.out.println(String.format("- Update %s", name));
+                        updateEntry(jos, name, changes.get(name));
+                        changes.remove(name);
                     } else {
-                        System.out.println(String.format("Copying %s to %s",
-                                name, dest));
+                        System.out.println(String.format("- Copy %s", name));
                         jos.putNextEntry(entry);
-                        Utils.transferBetweenStreams(srcJarFile.
-                                getInputStream(entry), jos);
+                        Utils.transferTo(srcJarFile.getInputStream(entry), jos);
                     }
                 }
             }
-
-            // append new files
-            for (String file : files) {
-                if (file.equals("-")) {
-                    break;
-                }
-                if (!updatedFiles.contains(file)) {
-                    System.out.println(String.format("Adding %s with %s",
-                            dest, file));
-                    jos.putNextEntry(new JarEntry(file));
-                    try (FileInputStream fis = new FileInputStream(file)) {
-                        Utils.transferBetweenStreams(fis, jos);
-                    } catch (FileNotFoundException e) {
-                        jos.write(file.getBytes());
-                    }
-                }
+            for (Map.Entry<String, Object> e : changes.entrySet()) {
+                System.out.println(String.format("- Add %s", e.getKey()));
+                updateEntry(jos, e.getKey(), e.getValue());
             }
         }
         System.out.println();
     }
-}
\ No newline at end of file
+
+    private static void updateEntry(JarOutputStream jos, String name, Object content)
+           throws IOException {
+        if (content instanceof Boolean) {
+            if (((Boolean) content).booleanValue()) {
+                throw new RuntimeException("Boolean value must be FALSE");
+            }
+        } else {
+            jos.putNextEntry(new JarEntry(name));
+            if (content instanceof Path) {
+                Utils.transferTo(Files.newInputStream((Path) content), jos);
+            } else if (content instanceof byte[]) {
+                jos.write((byte[]) content);
+            } else if (content instanceof String) {
+                jos.write(((String) content).getBytes());
+            } else {
+                throw new RuntimeException("Unknown type " + content.getClass());
+            }
+        }
+    }
+}
--- a/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java	Fri Jan 12 22:16:44 2018 +0000
+++ b/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java	Mon Jan 15 13:17:33 2018 +0000
@@ -365,6 +365,21 @@
     }
 
     /**
+     * Verify the exit value of the process
+     *
+     * @param notExpectedExitValue Unexpected exit value from process
+     * @throws RuntimeException If the exit value from the process did match the expected value
+     */
+    public OutputAnalyzer shouldNotHaveExitValue(int notExpectedExitValue) {
+        if (getExitValue() == notExpectedExitValue) {
+            reportDiagnosticSummary();
+            throw new RuntimeException("Unexpected to get exit value of ["
+                    + notExpectedExitValue + "]\n");
+        }
+        return this;
+    }
+
+    /**
      * Report summary that will help to diagnose the problem Currently includes:
      * - standard input produced by the process under test - standard output -
      * exit code Note: the command line is printed by the ProcessTools
--- a/test/lib/testlibrary/jdk/testlibrary/Utils.java	Fri Jan 12 22:16:44 2018 +0000
+++ b/test/lib/testlibrary/jdk/testlibrary/Utils.java	Mon Jan 15 13:17:33 2018 +0000
@@ -38,6 +38,7 @@
 import java.util.List;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
 import java.util.concurrent.TimeUnit;
@@ -80,6 +81,9 @@
     */
     public static final long DEFAULT_TEST_TIMEOUT = TimeUnit.SECONDS.toMillis(120);
 
+    private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
     private Utils() {
         // Private constructor to prevent class instantiation
     }
@@ -292,6 +296,40 @@
     }
 
     /**
+     * Helper method to read all bytes from InputStream
+     *
+     * @param is InputStream to read from
+     * @return array of bytes
+     * @throws IOException
+     */
+    public static byte[] readAllBytes(InputStream is) throws IOException {
+        byte[] buf = new byte[DEFAULT_BUFFER_SIZE];
+        int capacity = buf.length;
+        int nread = 0;
+        int n;
+        for (;;) {
+            // read to EOF which may read more or less than initial buffer size
+            while ((n = is.read(buf, nread, capacity - nread)) > 0)
+                nread += n;
+
+            // if the last call to read returned -1, then we're done
+            if (n < 0)
+                break;
+
+            // need to allocate a larger buffer
+            if (capacity <= MAX_BUFFER_SIZE - capacity) {
+                capacity = capacity << 1;
+            } else {
+                if (capacity == MAX_BUFFER_SIZE)
+                    throw new OutOfMemoryError("Required array size too large");
+                capacity = MAX_BUFFER_SIZE;
+            }
+            buf = Arrays.copyOf(buf, capacity);
+        }
+        return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
+    }
+
+    /**
      * Adjusts the provided timeout value for the TIMEOUT_FACTOR
      * @param tOut the timeout value to be adjusted
      * @return The timeout value adjusted for the value of "test.timeout.factor"
@@ -362,7 +400,7 @@
      * @throws NullPointerException if {@code in} or {@code out} is {@code null}
      *
      */
-    public static long transferBetweenStreams(InputStream in, OutputStream out)
+    public static long transferTo(InputStream in, OutputStream out)
             throws IOException  {
         long transferred = 0;
         byte[] buffer = new byte[BUFFER_SIZE];