提交 75da6813 编写于 作者: C coffeys

8189969: Manifest better manifest entries

Reviewed-by: weijun, igerasim
上级 1a4abce2
...@@ -26,8 +26,10 @@ ...@@ -26,8 +26,10 @@
package sun.security.util; package sun.security.util;
import java.security.*; import java.security.*;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.List;
/** /**
* This class is used to compute digests on sections of the Manifest. * This class is used to compute digests on sections of the Manifest.
...@@ -39,7 +41,7 @@ public class ManifestDigester { ...@@ -39,7 +41,7 @@ public class ManifestDigester {
/** the raw bytes of the manifest */ /** the raw bytes of the manifest */
private byte rawBytes[]; 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 private HashMap<String, Entry> entries; // key is a UTF-8 string
/** state returned by findSection */ /** state returned by findSection */
...@@ -120,8 +122,8 @@ public class ManifestDigester { ...@@ -120,8 +122,8 @@ public class ManifestDigester {
return; // XXX: exception? return; // XXX: exception?
// create an entry for main attributes // create an entry for main attributes
entries.put(MF_MAIN_ATTRS, entries.put(MF_MAIN_ATTRS, new Entry().addSection(
new Entry(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)); new Section(0, pos.endOfSection + 1, pos.startOfNext, rawBytes)));
int start = pos.startOfNext; int start = pos.startOfNext;
while(findSection(start, pos)) { while(findSection(start, pos)) {
...@@ -167,9 +169,10 @@ public class ManifestDigester { ...@@ -167,9 +169,10 @@ public class ManifestDigester {
} }
} }
entries.put(nameBuf.toString(), entries.computeIfAbsent(nameBuf.toString(),
new Entry(start, sectionLen, sectionLenWithBlank, dummy -> new Entry())
rawBytes)); .addSection(new Section(start, sectionLen,
sectionLenWithBlank, rawBytes));
} catch (java.io.UnsupportedEncodingException uee) { } catch (java.io.UnsupportedEncodingException uee) {
throw new IllegalStateException( throw new IllegalStateException(
...@@ -192,33 +195,61 @@ public class ManifestDigester { ...@@ -192,33 +195,61 @@ public class ManifestDigester {
} }
public static class Entry { public static class Entry {
int offset;
int length; // One Entry for one name, and one name can have multiple sections.
int lengthWithBlankLine; // According to the JAR File Specification: "If there are multiple
byte[] rawBytes; // individual sections for the same file entry, the attributes in
// these sections are merged."
private List<Section> sections = new ArrayList<>();
boolean oldStyle; boolean oldStyle;
public Entry(int offset, int length, private Entry addSection(Section sec)
int lengthWithBlankLine, byte[] rawBytes)
{ {
this.offset = offset; sections.add(sec);
this.length = length; return this;
this.lengthWithBlankLine = lengthWithBlankLine;
this.rawBytes = rawBytes;
} }
public byte[] digest(MessageDigest md) public byte[] digest(MessageDigest md)
{ {
md.reset(); md.reset();
for (Section sec : sections) {
if (oldStyle) { if (oldStyle) {
doOldStyle(md,rawBytes, offset, lengthWithBlankLine); Section.doOldStyle(md, sec.rawBytes, sec.offset, sec.lengthWithBlankLine);
} else { } else {
md.update(rawBytes, offset, lengthWithBlankLine); md.update(sec.rawBytes, sec.offset, sec.lengthWithBlankLine);
}
} }
return md.digest(); return md.digest();
} }
private void doOldStyle(MessageDigest md, /** 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;
public Section(int offset, int length,
int lengthWithBlankLine, byte[] rawBytes)
{
this.offset = offset;
this.length = length;
this.lengthWithBlankLine = lengthWithBlankLine;
this.rawBytes = rawBytes;
}
private static void doOldStyle(MessageDigest md,
byte[] bytes, byte[] bytes,
int offset, int offset,
int length) int length)
...@@ -242,16 +273,6 @@ public class ManifestDigester { ...@@ -242,16 +273,6 @@ public class ManifestDigester {
} }
md.update(bytes, start, i-start); 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) { public Entry get(String name, boolean oldStyle) {
......
...@@ -23,7 +23,6 @@ ...@@ -23,7 +23,6 @@
*/ */
import jdk.testlibrary.ProcessTools; import jdk.testlibrary.ProcessTools;
import jdk.testlibrary.JarUtils;
import javax.security.auth.Subject; import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
...@@ -101,7 +100,7 @@ public class NestedActions { ...@@ -101,7 +100,7 @@ public class NestedActions {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
if (args.length > 0) { if (args.length > 0) {
if ("jar".equals(args[0]) && args.length > 2) { 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)); Arrays.copyOfRange(args, 2, args.length));
} else { } else {
runJava(args); runJava(args);
...@@ -111,6 +110,21 @@ public class NestedActions { ...@@ -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) { static void runJava(String[] args) {
......
/* /*
* 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. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
...@@ -23,15 +23,17 @@ ...@@ -23,15 +23,17 @@
package jdk.testlibrary; package jdk.testlibrary;
import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.nio.file.Paths;
import java.util.Enumeration; 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.JarEntry;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
...@@ -42,45 +44,30 @@ import java.util.jar.Manifest; ...@@ -42,45 +44,30 @@ import java.util.jar.Manifest;
*/ */
public final class JarUtils { public final class JarUtils {
/** /**
* Create jar file with specified files. If a specified file does not exist, * 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. * a new jar entry will be created with the file name itself as the content.
*/ */
public static void createJar(String dest, Path filesLocation, public static void createJar(String dest, String... files)
String... fileNames) throws IOException { throws IOException {
try (JarOutputStream jos = new JarOutputStream( try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(dest), new Manifest())) { new FileOutputStream(dest), new Manifest())) {
for (String fileName : fileNames) { for (String file : files) {
System.out.println(String.format("Adding %s to %s", System.out.println(String.format("Adding %s to %s",
fileName, dest)); file, dest));
// add an archive entry, and write a file // add an archive entry, and write a file
jos.putNextEntry(new JarEntry(fileName)); jos.putNextEntry(new JarEntry(file));
File file;
if (filesLocation != null) {
file = filesLocation.resolve(fileName).toFile();
} else {
file = new File(fileName);
}
try (FileInputStream fis = new FileInputStream(file)) { try (FileInputStream fis = new FileInputStream(file)) {
Utils.transferBetweenStreams(fis, jos); Utils.transferTo(fis, jos);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
jos.write(fileName.getBytes()); jos.write(file.getBytes());
} }
} }
} }
System.out.println(); 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 * 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 * to be updated or added does not exist, the jar entry will be created
...@@ -96,70 +83,93 @@ public final class JarUtils { ...@@ -96,70 +83,93 @@ public final class JarUtils {
*/ */
public static void updateJar(String src, String dest, String... files) public static void updateJar(String src, String dest, String... files)
throws IOException { throws IOException {
try (JarOutputStream jos = new JarOutputStream( Map<String,Object> changes = new HashMap<>();
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; boolean update = true;
for (String file : files) { for (String file : files) {
if (file.equals("-")) { if (file.equals("-")) {
update = false; update = false;
} else if (name.equals(file)) { } else if (update) {
updatedFiles.add(file); try {
found = true; Path p = Paths.get(file);
break; if (Files.exists(p)) {
changes.put(file, p);
} else {
changes.put(file, file);
} }
} } catch (InvalidPathException e) {
// Fallback if file not a valid Path.
if (found) { changes.put(file, file);
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 { } else {
System.out.println(String.format("Removing %s from %s", changes.put(file, Boolean.FALSE);
name, dest)); }
} }
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))) {
try (JarFile srcJarFile = new JarFile(src)) {
Enumeration<JarEntry> entries = srcJarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
if (changes.containsKey(name)) {
System.out.println(String.format("- Update %s", name));
updateEntry(jos, name, changes.get(name));
changes.remove(name);
} else { } else {
System.out.println(String.format("Copying %s to %s", System.out.println(String.format("- Copy %s", name));
name, dest));
jos.putNextEntry(entry); jos.putNextEntry(entry);
Utils.transferBetweenStreams(srcJarFile. Utils.transferTo(srcJarFile.getInputStream(entry), jos);
getInputStream(entry), jos);
} }
} }
} }
for (Map.Entry<String, Object> e : changes.entrySet()) {
// append new files System.out.println(String.format("- Add %s", e.getKey()));
for (String file : files) { updateEntry(jos, e.getKey(), e.getValue());
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());
} }
System.out.println();
} }
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());
} }
} }
System.out.println();
} }
} }
...@@ -364,6 +364,21 @@ public final class OutputAnalyzer { ...@@ -364,6 +364,21 @@ public final class OutputAnalyzer {
return this; 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: * Report summary that will help to diagnose the problem Currently includes:
* - standard input produced by the process under test - standard output - * - standard input produced by the process under test - standard output -
......
...@@ -38,6 +38,7 @@ import java.util.ArrayList; ...@@ -38,6 +38,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
...@@ -80,6 +81,9 @@ public final class Utils { ...@@ -80,6 +81,9 @@ public final class Utils {
*/ */
public static final long DEFAULT_TEST_TIMEOUT = TimeUnit.SECONDS.toMillis(120); 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 Utils() {
// Private constructor to prevent class instantiation // Private constructor to prevent class instantiation
} }
...@@ -291,6 +295,40 @@ public final class Utils { ...@@ -291,6 +295,40 @@ public final class Utils {
return output; 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 * Adjusts the provided timeout value for the TIMEOUT_FACTOR
* @param tOut the timeout value to be adjusted * @param tOut the timeout value to be adjusted
...@@ -362,7 +400,7 @@ public final class Utils { ...@@ -362,7 +400,7 @@ public final class Utils {
* @throws NullPointerException if {@code in} or {@code out} is {@code null} * @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 { throws IOException {
long transferred = 0; long transferred = 0;
byte[] buffer = new byte[BUFFER_SIZE]; byte[] buffer = new byte[BUFFER_SIZE];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册