From 1af8aef10c95f39efbb07cc9e5aa4e0d8d2262b7 Mon Sep 17 00:00:00 2001 From: coffeys Date: Mon, 15 Jan 2018 13:17:33 +0000 Subject: [PATCH] 8189969: Manifest better manifest entries Reviewed-by: weijun, igerasim --- .../sun/security/util/ManifestDigester.java | 81 +++++---- .../auth/Subject/doAs/NestedActions.java | 18 +- .../testlibrary/jdk/testlibrary/JarUtils.java | 162 ++++++++++-------- .../jdk/testlibrary/OutputAnalyzer.java | 15 ++ .../testlibrary/jdk/testlibrary/Utils.java | 40 ++++- 5 files changed, 207 insertions(+), 109 deletions(-) diff --git a/src/share/classes/sun/security/util/ManifestDigester.java b/src/share/classes/sun/security/util/ManifestDigester.java index fc9ccd3d5..7105f6962 100644 --- a/src/share/classes/sun/security/util/ManifestDigester.java +++ b/src/share/classes/sun/security/util/ManifestDigester.java @@ -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 @@ public class ManifestDigester { /** the raw bytes of the manifest */ private byte rawBytes[]; - /** the offset/length pair for a section */ + /** the entries grouped by names */ private HashMap entries; // key is a UTF-8 string /** state returned by findSection */ @@ -120,8 +122,8 @@ public class ManifestDigester { 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 @@ public class ManifestDigester { } } - 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 class ManifestDigester { } 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
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 @@ public class ManifestDigester { 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 @@ public class ManifestDigester { } 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) { diff --git a/test/javax/security/auth/Subject/doAs/NestedActions.java b/test/javax/security/auth/Subject/doAs/NestedActions.java index a4f249e0e..deb4a6041 100644 --- a/test/javax/security/auth/Subject/doAs/NestedActions.java +++ b/test/javax/security/auth/Subject/doAs/NestedActions.java @@ -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 class NestedActions { 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 @@ public class NestedActions { } } + 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) { diff --git a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java index 327594d69..1be66e5e3 100644 --- a/test/lib/testlibrary/jdk/testlibrary/JarUtils.java +++ b/test/lib/testlibrary/jdk/testlibrary/JarUtils.java @@ -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,45 +44,30 @@ import java.util.jar.Manifest; */ 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()); } } } System.out.println(); } - /** - * 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 @@ -96,70 +83,93 @@ public final class JarUtils { */ public static void updateJar(String src, String dest, String... files) throws IOException { + Map 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 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 updatedFiles = new ArrayList<>(); try (JarFile srcJarFile = new JarFile(src)) { Enumeration 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 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()); + } + } + } +} diff --git a/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java b/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java index a715d21e2..77312208d 100644 --- a/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java +++ b/test/lib/testlibrary/jdk/testlibrary/OutputAnalyzer.java @@ -364,6 +364,21 @@ public final class OutputAnalyzer { return this; } + /** + * 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 - diff --git a/test/lib/testlibrary/jdk/testlibrary/Utils.java b/test/lib/testlibrary/jdk/testlibrary/Utils.java index 399909f5a..2bedbb1ca 100644 --- a/test/lib/testlibrary/jdk/testlibrary/Utils.java +++ b/test/lib/testlibrary/jdk/testlibrary/Utils.java @@ -38,6 +38,7 @@ import java.util.ArrayList; 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 final class Utils { */ 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 } @@ -291,6 +295,40 @@ public final class Utils { return output; } + /** + * 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 @@ -362,7 +400,7 @@ public final class Utils { * @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]; -- GitLab