mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
8187443: Forest Consolidation: Move files to unified layout
Reviewed-by: darcy, ihse
This commit is contained in:
parent
270fe13182
commit
3789983e89
56923 changed files with 3 additions and 15727 deletions
658
src/java.base/share/classes/java/util/jar/Attributes.java
Normal file
658
src/java.base/share/classes/java/util/jar/Attributes.java
Normal file
|
@ -0,0 +1,658 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Collection;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import sun.util.logging.PlatformLogger;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* The Attributes class maps Manifest attribute names to associated string
|
||||
* values. Valid attribute names are case-insensitive, are restricted to
|
||||
* the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
|
||||
* characters in length. Attribute values can contain any characters and
|
||||
* will be UTF8-encoded when written to the output stream. See the
|
||||
* <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
|
||||
* for more information about valid attribute names and values.
|
||||
*
|
||||
* <p>This map and its views have a predictable iteration order, namely the
|
||||
* order that keys were inserted into the map, as with {@link LinkedHashMap}.
|
||||
*
|
||||
* @author David Connelly
|
||||
* @see Manifest
|
||||
* @since 1.2
|
||||
*/
|
||||
public class Attributes implements Map<Object,Object>, Cloneable {
|
||||
/**
|
||||
* The attribute name-value mappings.
|
||||
*/
|
||||
protected Map<Object,Object> map;
|
||||
|
||||
/**
|
||||
* Constructs a new, empty Attributes object with default size.
|
||||
*/
|
||||
public Attributes() {
|
||||
this(11);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new, empty Attributes object with the specified
|
||||
* initial size.
|
||||
*
|
||||
* @param size the initial number of attributes
|
||||
*/
|
||||
public Attributes(int size) {
|
||||
map = new LinkedHashMap<>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Attributes object with the same attribute name-value
|
||||
* mappings as in the specified Attributes.
|
||||
*
|
||||
* @param attr the specified Attributes
|
||||
*/
|
||||
public Attributes(Attributes attr) {
|
||||
map = new LinkedHashMap<>(attr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the value of the specified attribute name, or null if the
|
||||
* attribute name was not found.
|
||||
*
|
||||
* @param name the attribute name
|
||||
* @return the value of the specified attribute name, or null if
|
||||
* not found.
|
||||
*/
|
||||
public Object get(Object name) {
|
||||
return map.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified attribute name, specified as
|
||||
* a string, or null if the attribute was not found. The attribute
|
||||
* name is case-insensitive.
|
||||
* <p>
|
||||
* This method is defined as:
|
||||
* <pre>
|
||||
* return (String)get(new Attributes.Name((String)name));
|
||||
* </pre>
|
||||
*
|
||||
* @param name the attribute name as a string
|
||||
* @return the String value of the specified attribute name, or null if
|
||||
* not found.
|
||||
* @throws IllegalArgumentException if the attribute name is invalid
|
||||
*/
|
||||
public String getValue(String name) {
|
||||
return (String)get(new Attributes.Name(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified Attributes.Name, or null if the
|
||||
* attribute was not found.
|
||||
* <p>
|
||||
* This method is defined as:
|
||||
* <pre>
|
||||
* return (String)get(name);
|
||||
* </pre>
|
||||
*
|
||||
* @param name the Attributes.Name object
|
||||
* @return the String value of the specified Attribute.Name, or null if
|
||||
* not found.
|
||||
*/
|
||||
public String getValue(Name name) {
|
||||
return (String)get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified attribute name
|
||||
* (key) in this Map. If the Map previously contained a mapping for
|
||||
* the attribute name, the old value is replaced.
|
||||
*
|
||||
* @param name the attribute name
|
||||
* @param value the attribute value
|
||||
* @return the previous value of the attribute, or null if none
|
||||
* @exception ClassCastException if the name is not a Attributes.Name
|
||||
* or the value is not a String
|
||||
*/
|
||||
public Object put(Object name, Object value) {
|
||||
return map.put((Attributes.Name)name, (String)value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associates the specified value with the specified attribute name,
|
||||
* specified as a String. The attributes name is case-insensitive.
|
||||
* If the Map previously contained a mapping for the attribute name,
|
||||
* the old value is replaced.
|
||||
* <p>
|
||||
* This method is defined as:
|
||||
* <pre>
|
||||
* return (String)put(new Attributes.Name(name), value);
|
||||
* </pre>
|
||||
*
|
||||
* @param name the attribute name as a string
|
||||
* @param value the attribute value
|
||||
* @return the previous value of the attribute, or null if none
|
||||
* @exception IllegalArgumentException if the attribute name is invalid
|
||||
*/
|
||||
public String putValue(String name, String value) {
|
||||
return (String)put(new Name(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the attribute with the specified name (key) from this Map.
|
||||
* Returns the previous attribute value, or null if none.
|
||||
*
|
||||
* @param name attribute name
|
||||
* @return the previous value of the attribute, or null if none
|
||||
*/
|
||||
public Object remove(Object name) {
|
||||
return map.remove(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Map maps one or more attribute names (keys)
|
||||
* to the specified value.
|
||||
*
|
||||
* @param value the attribute value
|
||||
* @return true if this Map maps one or more attribute names to
|
||||
* the specified value
|
||||
*/
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Map contains the specified attribute name (key).
|
||||
*
|
||||
* @param name the attribute name
|
||||
* @return true if this Map contains the specified attribute name
|
||||
*/
|
||||
public boolean containsKey(Object name) {
|
||||
return map.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies all of the attribute name-value mappings from the specified
|
||||
* Attributes to this Map. Duplicate mappings will be replaced.
|
||||
*
|
||||
* @param attr the Attributes to be stored in this map
|
||||
* @exception ClassCastException if attr is not an Attributes
|
||||
*/
|
||||
public void putAll(Map<?,?> attr) {
|
||||
// ## javac bug?
|
||||
if (!Attributes.class.isInstance(attr))
|
||||
throw new ClassCastException();
|
||||
for (Map.Entry<?,?> me : (attr).entrySet())
|
||||
put(me.getKey(), me.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all attributes from this Map.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of attributes in this Map.
|
||||
*/
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this Map contains no attributes.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Set view of the attribute names (keys) contained in this Map.
|
||||
*/
|
||||
public Set<Object> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Collection view of the attribute values contained in this Map.
|
||||
*/
|
||||
public Collection<Object> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Collection view of the attribute name-value mappings
|
||||
* contained in this Map.
|
||||
*/
|
||||
public Set<Map.Entry<Object,Object>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the specified Attributes object with this Map for equality.
|
||||
* Returns true if the given object is also an instance of Attributes
|
||||
* and the two Attributes objects represent the same mappings.
|
||||
*
|
||||
* @param o the Object to be compared
|
||||
* @return true if the specified Object is equal to this Map
|
||||
*/
|
||||
public boolean equals(Object o) {
|
||||
return map.equals(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code value for this Map.
|
||||
*/
|
||||
public int hashCode() {
|
||||
return map.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the Attributes, implemented as follows:
|
||||
* <pre>
|
||||
* public Object clone() { return new Attributes(this); }
|
||||
* </pre>
|
||||
* Since the attribute names and values are themselves immutable,
|
||||
* the Attributes returned can be safely modified without affecting
|
||||
* the original.
|
||||
*/
|
||||
public Object clone() {
|
||||
return new Attributes(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes the current attributes to the specified data output stream.
|
||||
* XXX Need to handle UTF8 values and break up lines longer than 72 bytes
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
void write(DataOutputStream os) throws IOException {
|
||||
for (Entry<Object, Object> e : entrySet()) {
|
||||
StringBuffer buffer = new StringBuffer(
|
||||
((Name) e.getKey()).toString());
|
||||
buffer.append(": ");
|
||||
|
||||
String value = (String) e.getValue();
|
||||
if (value != null) {
|
||||
byte[] vb = value.getBytes("UTF8");
|
||||
value = new String(vb, 0, 0, vb.length);
|
||||
}
|
||||
buffer.append(value);
|
||||
|
||||
buffer.append("\r\n");
|
||||
Manifest.make72Safe(buffer);
|
||||
os.writeBytes(buffer.toString());
|
||||
}
|
||||
os.writeBytes("\r\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes the current attributes to the specified data output stream,
|
||||
* make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
|
||||
* attributes first.
|
||||
*
|
||||
* XXX Need to handle UTF8 values and break up lines longer than 72 bytes
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
void writeMain(DataOutputStream out) throws IOException
|
||||
{
|
||||
// write out the *-Version header first, if it exists
|
||||
String vername = Name.MANIFEST_VERSION.toString();
|
||||
String version = getValue(vername);
|
||||
if (version == null) {
|
||||
vername = Name.SIGNATURE_VERSION.toString();
|
||||
version = getValue(vername);
|
||||
}
|
||||
|
||||
if (version != null) {
|
||||
out.writeBytes(vername+": "+version+"\r\n");
|
||||
}
|
||||
|
||||
// write out all attributes except for the version
|
||||
// we wrote out earlier
|
||||
for (Entry<Object, Object> e : entrySet()) {
|
||||
String name = ((Name) e.getKey()).toString();
|
||||
if ((version != null) && !(name.equalsIgnoreCase(vername))) {
|
||||
|
||||
StringBuffer buffer = new StringBuffer(name);
|
||||
buffer.append(": ");
|
||||
|
||||
String value = (String) e.getValue();
|
||||
if (value != null) {
|
||||
byte[] vb = value.getBytes("UTF8");
|
||||
value = new String(vb, 0, 0, vb.length);
|
||||
}
|
||||
buffer.append(value);
|
||||
|
||||
buffer.append("\r\n");
|
||||
Manifest.make72Safe(buffer);
|
||||
out.writeBytes(buffer.toString());
|
||||
}
|
||||
}
|
||||
out.writeBytes("\r\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads attributes from the specified input stream.
|
||||
* XXX Need to handle UTF8 values.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
|
||||
String name = null, value = null;
|
||||
byte[] lastline = null;
|
||||
|
||||
int len;
|
||||
while ((len = is.readLine(lbuf)) != -1) {
|
||||
boolean lineContinued = false;
|
||||
if (lbuf[--len] != '\n') {
|
||||
throw new IOException("line too long");
|
||||
}
|
||||
if (len > 0 && lbuf[len-1] == '\r') {
|
||||
--len;
|
||||
}
|
||||
if (len == 0) {
|
||||
break;
|
||||
}
|
||||
int i = 0;
|
||||
if (lbuf[0] == ' ') {
|
||||
// continuation of previous line
|
||||
if (name == null) {
|
||||
throw new IOException("misplaced continuation line");
|
||||
}
|
||||
lineContinued = true;
|
||||
byte[] buf = new byte[lastline.length + len - 1];
|
||||
System.arraycopy(lastline, 0, buf, 0, lastline.length);
|
||||
System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
|
||||
if (is.peek() == ' ') {
|
||||
lastline = buf;
|
||||
continue;
|
||||
}
|
||||
value = new String(buf, 0, buf.length, "UTF8");
|
||||
lastline = null;
|
||||
} else {
|
||||
while (lbuf[i++] != ':') {
|
||||
if (i >= len) {
|
||||
throw new IOException("invalid header field");
|
||||
}
|
||||
}
|
||||
if (lbuf[i++] != ' ') {
|
||||
throw new IOException("invalid header field");
|
||||
}
|
||||
name = new String(lbuf, 0, 0, i - 2);
|
||||
if (is.peek() == ' ') {
|
||||
lastline = new byte[len - i];
|
||||
System.arraycopy(lbuf, i, lastline, 0, len - i);
|
||||
continue;
|
||||
}
|
||||
value = new String(lbuf, i, len - i, "UTF8");
|
||||
}
|
||||
try {
|
||||
if ((putValue(name, value) != null) && (!lineContinued)) {
|
||||
PlatformLogger.getLogger("java.util.jar").warning(
|
||||
"Duplicate name in Manifest: " + name
|
||||
+ ".\n"
|
||||
+ "Ensure that the manifest does not "
|
||||
+ "have duplicate entries, and\n"
|
||||
+ "that blank lines separate "
|
||||
+ "individual sections in both your\n"
|
||||
+ "manifest and in the META-INF/MANIFEST.MF "
|
||||
+ "entry in the jar file.");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IOException("invalid header field name: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Attributes.Name class represents an attribute name stored in
|
||||
* this Map. Valid attribute names are case-insensitive, are restricted
|
||||
* to the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed
|
||||
* 70 characters in length. Attribute values can contain any characters
|
||||
* and will be UTF8-encoded when written to the output stream. See the
|
||||
* <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
|
||||
* for more information about valid attribute names and values.
|
||||
*/
|
||||
public static class Name {
|
||||
private String name;
|
||||
private int hashCode = -1;
|
||||
|
||||
/**
|
||||
* Constructs a new attribute name using the given string name.
|
||||
*
|
||||
* @param name the attribute string name
|
||||
* @exception IllegalArgumentException if the attribute name was
|
||||
* invalid
|
||||
* @exception NullPointerException if the attribute name was null
|
||||
*/
|
||||
public Name(String name) {
|
||||
if (name == null) {
|
||||
throw new NullPointerException("name");
|
||||
}
|
||||
if (!isValid(name)) {
|
||||
throw new IllegalArgumentException(name);
|
||||
}
|
||||
this.name = name.intern();
|
||||
}
|
||||
|
||||
private static boolean isValid(String name) {
|
||||
int len = name.length();
|
||||
if (len > 70 || len == 0) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (!isValid(name.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValid(char c) {
|
||||
return isAlpha(c) || isDigit(c) || c == '_' || c == '-';
|
||||
}
|
||||
|
||||
private static boolean isAlpha(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
private static boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares this attribute name to another for equality.
|
||||
* @param o the object to compare
|
||||
* @return true if this attribute name is equal to the
|
||||
* specified attribute object
|
||||
*/
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Name) {
|
||||
Comparator<String> c = String.CASE_INSENSITIVE_ORDER;
|
||||
return c.compare(name, ((Name)o).name) == 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the hash value for this attribute name.
|
||||
*/
|
||||
public int hashCode() {
|
||||
if (hashCode == -1) {
|
||||
hashCode = name.toLowerCase(Locale.ROOT).hashCode();
|
||||
}
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the attribute name as a String.
|
||||
*/
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Manifest-Version}
|
||||
* manifest attribute. This attribute indicates the version number
|
||||
* of the manifest standard to which a JAR file's manifest conforms.
|
||||
* @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest">
|
||||
* Manifest and Signature Specification</a>
|
||||
*/
|
||||
public static final Name MANIFEST_VERSION = new Name("Manifest-Version");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Signature-Version}
|
||||
* manifest attribute used when signing JAR files.
|
||||
* @see <a href="{@docRoot}/../specs/jar/jar.html#JAR_Manifest">
|
||||
* Manifest and Signature Specification</a>
|
||||
*/
|
||||
public static final Name SIGNATURE_VERSION = new Name("Signature-Version");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Content-Type}
|
||||
* manifest attribute.
|
||||
*/
|
||||
public static final Name CONTENT_TYPE = new Name("Content-Type");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Class-Path}
|
||||
* manifest attribute.
|
||||
* @see <a href="{@docRoot}/../specs/jar/jar.html#classpath">
|
||||
* JAR file specification</a>
|
||||
*/
|
||||
public static final Name CLASS_PATH = new Name("Class-Path");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Main-Class} manifest
|
||||
* attribute used for launching applications packaged in JAR files.
|
||||
* The {@code Main-Class} attribute is used in conjunction
|
||||
* with the {@code -jar} command-line option of the
|
||||
* {@code java} application launcher.
|
||||
*/
|
||||
public static final Name MAIN_CLASS = new Name("Main-Class");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Sealed} manifest attribute
|
||||
* used for sealing.
|
||||
* @see <a href="{@docRoot}/../specs/jar/jar.html#sealing">
|
||||
* Package Sealing</a>
|
||||
*/
|
||||
public static final Name SEALED = new Name("Sealed");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Extension-List} manifest attribute
|
||||
* used for the extension mechanism that is no longer supported.
|
||||
*/
|
||||
public static final Name EXTENSION_LIST = new Name("Extension-List");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Extension-Name} manifest attribute.
|
||||
* used for the extension mechanism that is no longer supported.
|
||||
*/
|
||||
public static final Name EXTENSION_NAME = new Name("Extension-Name");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Extension-Installation} manifest attribute.
|
||||
*
|
||||
* @deprecated Extension mechanism is no longer supported.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final Name EXTENSION_INSTALLATION = new Name("Extension-Installation");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Implementation-Title}
|
||||
* manifest attribute used for package versioning.
|
||||
*/
|
||||
public static final Name IMPLEMENTATION_TITLE = new Name("Implementation-Title");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Implementation-Version}
|
||||
* manifest attribute used for package versioning.
|
||||
*/
|
||||
public static final Name IMPLEMENTATION_VERSION = new Name("Implementation-Version");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Implementation-Vendor}
|
||||
* manifest attribute used for package versioning.
|
||||
*/
|
||||
public static final Name IMPLEMENTATION_VENDOR = new Name("Implementation-Vendor");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Implementation-Vendor-Id}
|
||||
* manifest attribute.
|
||||
*
|
||||
* @deprecated Extension mechanism is no longer supported.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final Name IMPLEMENTATION_VENDOR_ID = new Name("Implementation-Vendor-Id");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Implementation-URL}
|
||||
* manifest attribute.
|
||||
*
|
||||
* @deprecated Extension mechanism is no longer supported.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final Name IMPLEMENTATION_URL = new Name("Implementation-URL");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Specification-Title}
|
||||
* manifest attribute used for package versioning.
|
||||
*/
|
||||
public static final Name SPECIFICATION_TITLE = new Name("Specification-Title");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Specification-Version}
|
||||
* manifest attribute used for package versioning.
|
||||
*/
|
||||
public static final Name SPECIFICATION_VERSION = new Name("Specification-Version");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Specification-Vendor}
|
||||
* manifest attribute used for package versioning.
|
||||
*/
|
||||
public static final Name SPECIFICATION_VENDOR = new Name("Specification-Vendor");
|
||||
|
||||
/**
|
||||
* {@code Name} object for {@code Multi-Release}
|
||||
* manifest attribute that indicates this is a multi-release JAR file.
|
||||
*
|
||||
* @since 9
|
||||
*/
|
||||
public static final Name MULTI_RELEASE = new Name("Multi-Release");
|
||||
}
|
||||
}
|
131
src/java.base/share/classes/java/util/jar/JarEntry.java
Normal file
131
src/java.base/share/classes/java/util/jar/JarEntry.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 2013, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.cert.Certificate;
|
||||
|
||||
/**
|
||||
* This class is used to represent a JAR file entry.
|
||||
*
|
||||
* @since 1.2
|
||||
*/
|
||||
public
|
||||
class JarEntry extends ZipEntry {
|
||||
Attributes attr;
|
||||
Certificate[] certs;
|
||||
CodeSigner[] signers;
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarEntry</code> for the specified JAR file
|
||||
* entry name.
|
||||
*
|
||||
* @param name the JAR file entry name
|
||||
* @exception NullPointerException if the entry name is <code>null</code>
|
||||
* @exception IllegalArgumentException if the entry name is longer than
|
||||
* 0xFFFF bytes.
|
||||
*/
|
||||
public JarEntry(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarEntry</code> with fields taken from the
|
||||
* specified <code>ZipEntry</code> object.
|
||||
* @param ze the <code>ZipEntry</code> object to create the
|
||||
* <code>JarEntry</code> from
|
||||
*/
|
||||
public JarEntry(ZipEntry ze) {
|
||||
super(ze);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarEntry</code> with fields taken from the
|
||||
* specified <code>JarEntry</code> object.
|
||||
*
|
||||
* @param je the <code>JarEntry</code> to copy
|
||||
*/
|
||||
public JarEntry(JarEntry je) {
|
||||
this((ZipEntry)je);
|
||||
this.attr = je.attr;
|
||||
this.certs = je.certs;
|
||||
this.signers = je.signers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>Manifest</code> <code>Attributes</code> for this
|
||||
* entry, or <code>null</code> if none.
|
||||
*
|
||||
* @return the <code>Manifest</code> <code>Attributes</code> for this
|
||||
* entry, or <code>null</code> if none
|
||||
* @throws IOException if an I/O error has occurred
|
||||
*/
|
||||
public Attributes getAttributes() throws IOException {
|
||||
return attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>Certificate</code> objects for this entry, or
|
||||
* <code>null</code> if none. This method can only be called once
|
||||
* the <code>JarEntry</code> has been completely verified by reading
|
||||
* from the entry input stream until the end of the stream has been
|
||||
* reached. Otherwise, this method will return <code>null</code>.
|
||||
*
|
||||
* <p>The returned certificate array comprises all the signer certificates
|
||||
* that were used to verify this entry. Each signer certificate is
|
||||
* followed by its supporting certificate chain (which may be empty).
|
||||
* Each signer certificate and its supporting certificate chain are ordered
|
||||
* bottom-to-top (i.e., with the signer certificate first and the (root)
|
||||
* certificate authority last).
|
||||
*
|
||||
* @return the <code>Certificate</code> objects for this entry, or
|
||||
* <code>null</code> if none.
|
||||
*/
|
||||
public Certificate[] getCertificates() {
|
||||
return certs == null ? null : certs.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>CodeSigner</code> objects for this entry, or
|
||||
* <code>null</code> if none. This method can only be called once
|
||||
* the <code>JarEntry</code> has been completely verified by reading
|
||||
* from the entry input stream until the end of the stream has been
|
||||
* reached. Otherwise, this method will return <code>null</code>.
|
||||
*
|
||||
* <p>The returned array comprises all the code signers that have signed
|
||||
* this entry.
|
||||
*
|
||||
* @return the <code>CodeSigner</code> objects for this entry, or
|
||||
* <code>null</code> if none.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public CodeSigner[] getCodeSigners() {
|
||||
return signers == null ? null : signers.clone();
|
||||
}
|
||||
}
|
52
src/java.base/share/classes/java/util/jar/JarException.java
Normal file
52
src/java.base/share/classes/java/util/jar/JarException.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 2008, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
/**
|
||||
* Signals that an error of some sort has occurred while reading from
|
||||
* or writing to a JAR file.
|
||||
*
|
||||
* @author David Connelly
|
||||
* @since 1.2
|
||||
*/
|
||||
public
|
||||
class JarException extends java.util.zip.ZipException {
|
||||
private static final long serialVersionUID = 7159778400963954473L;
|
||||
|
||||
/**
|
||||
* Constructs a JarException with no detail message.
|
||||
*/
|
||||
public JarException() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a JarException with the specified detail message.
|
||||
* @param s the detail message
|
||||
*/
|
||||
public JarException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
1209
src/java.base/share/classes/java/util/jar/JarFile.java
Normal file
1209
src/java.base/share/classes/java/util/jar/JarFile.java
Normal file
File diff suppressed because it is too large
Load diff
233
src/java.base/share/classes/java/util/jar/JarInputStream.java
Normal file
233
src/java.base/share/classes/java/util/jar/JarInputStream.java
Normal file
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 2011, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.util.zip.*;
|
||||
import java.io.*;
|
||||
import sun.security.util.ManifestEntryVerifier;
|
||||
import jdk.internal.util.jar.JarIndex;
|
||||
|
||||
/**
|
||||
* The <code>JarInputStream</code> class is used to read the contents of
|
||||
* a JAR file from any input stream. It extends the class
|
||||
* <code>java.util.zip.ZipInputStream</code> with support for reading
|
||||
* an optional <code>Manifest</code> entry. The <code>Manifest</code>
|
||||
* can be used to store meta-information about the JAR file and its entries.
|
||||
*
|
||||
* @author David Connelly
|
||||
* @see Manifest
|
||||
* @see java.util.zip.ZipInputStream
|
||||
* @since 1.2
|
||||
*/
|
||||
public
|
||||
class JarInputStream extends ZipInputStream {
|
||||
private Manifest man;
|
||||
private JarEntry first;
|
||||
private JarVerifier jv;
|
||||
private ManifestEntryVerifier mev;
|
||||
private final boolean doVerify;
|
||||
private boolean tryManifest;
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarInputStream</code> and reads the optional
|
||||
* manifest. If a manifest is present, also attempts to verify
|
||||
* the signatures if the JarInputStream is signed.
|
||||
* @param in the actual input stream
|
||||
* @exception IOException if an I/O error has occurred
|
||||
*/
|
||||
public JarInputStream(InputStream in) throws IOException {
|
||||
this(in, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarInputStream</code> and reads the optional
|
||||
* manifest. If a manifest is present and verify is true, also attempts
|
||||
* to verify the signatures if the JarInputStream is signed.
|
||||
*
|
||||
* @param in the actual input stream
|
||||
* @param verify whether or not to verify the JarInputStream if
|
||||
* it is signed.
|
||||
* @exception IOException if an I/O error has occurred
|
||||
*/
|
||||
public JarInputStream(InputStream in, boolean verify) throws IOException {
|
||||
super(in);
|
||||
this.doVerify = verify;
|
||||
|
||||
// This implementation assumes the META-INF/MANIFEST.MF entry
|
||||
// should be either the first or the second entry (when preceded
|
||||
// by the dir META-INF/). It skips the META-INF/ and then
|
||||
// "consumes" the MANIFEST.MF to initialize the Manifest object.
|
||||
JarEntry e = (JarEntry)super.getNextEntry();
|
||||
if (e != null && e.getName().equalsIgnoreCase("META-INF/"))
|
||||
e = (JarEntry)super.getNextEntry();
|
||||
first = checkManifest(e);
|
||||
}
|
||||
|
||||
private JarEntry checkManifest(JarEntry e)
|
||||
throws IOException
|
||||
{
|
||||
if (e != null && JarFile.MANIFEST_NAME.equalsIgnoreCase(e.getName())) {
|
||||
man = new Manifest();
|
||||
byte bytes[] = getBytes(new BufferedInputStream(this));
|
||||
man.read(new ByteArrayInputStream(bytes));
|
||||
closeEntry();
|
||||
if (doVerify) {
|
||||
jv = new JarVerifier(bytes);
|
||||
mev = new ManifestEntryVerifier(man);
|
||||
}
|
||||
return (JarEntry)super.getNextEntry();
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
private byte[] getBytes(InputStream is)
|
||||
throws IOException
|
||||
{
|
||||
byte[] buffer = new byte[8192];
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
|
||||
int n;
|
||||
while ((n = is.read(buffer, 0, buffer.length)) != -1) {
|
||||
baos.write(buffer, 0, n);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the <code>Manifest</code> for this JAR file, or
|
||||
* <code>null</code> if none.
|
||||
*
|
||||
* @return the <code>Manifest</code> for this JAR file, or
|
||||
* <code>null</code> if none.
|
||||
*/
|
||||
public Manifest getManifest() {
|
||||
return man;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next ZIP file entry and positions the stream at the
|
||||
* beginning of the entry data. If verification has been enabled,
|
||||
* any invalid signature detected while positioning the stream for
|
||||
* the next entry will result in an exception.
|
||||
* @exception ZipException if a ZIP file error has occurred
|
||||
* @exception IOException if an I/O error has occurred
|
||||
* @exception SecurityException if any of the jar file entries
|
||||
* are incorrectly signed.
|
||||
*/
|
||||
public ZipEntry getNextEntry() throws IOException {
|
||||
JarEntry e;
|
||||
if (first == null) {
|
||||
e = (JarEntry)super.getNextEntry();
|
||||
if (tryManifest) {
|
||||
e = checkManifest(e);
|
||||
tryManifest = false;
|
||||
}
|
||||
} else {
|
||||
e = first;
|
||||
if (first.getName().equalsIgnoreCase(JarIndex.INDEX_NAME))
|
||||
tryManifest = true;
|
||||
first = null;
|
||||
}
|
||||
if (jv != null && e != null) {
|
||||
// At this point, we might have parsed all the meta-inf
|
||||
// entries and have nothing to verify. If we have
|
||||
// nothing to verify, get rid of the JarVerifier object.
|
||||
if (jv.nothingToVerify() == true) {
|
||||
jv = null;
|
||||
mev = null;
|
||||
} else {
|
||||
jv.beginEntry(e, mev);
|
||||
}
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the next JAR file entry and positions the stream at the
|
||||
* beginning of the entry data. If verification has been enabled,
|
||||
* any invalid signature detected while positioning the stream for
|
||||
* the next entry will result in an exception.
|
||||
* @return the next JAR file entry, or null if there are no more entries
|
||||
* @exception ZipException if a ZIP file error has occurred
|
||||
* @exception IOException if an I/O error has occurred
|
||||
* @exception SecurityException if any of the jar file entries
|
||||
* are incorrectly signed.
|
||||
*/
|
||||
public JarEntry getNextJarEntry() throws IOException {
|
||||
return (JarEntry)getNextEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads from the current JAR file entry into an array of bytes.
|
||||
* If <code>len</code> is not zero, the method
|
||||
* blocks until some input is available; otherwise, no
|
||||
* bytes are read and <code>0</code> is returned.
|
||||
* If verification has been enabled, any invalid signature
|
||||
* on the current entry will be reported at some point before the
|
||||
* end of the entry is reached.
|
||||
* @param b the buffer into which the data is read
|
||||
* @param off the start offset in the destination array <code>b</code>
|
||||
* @param len the maximum number of bytes to read
|
||||
* @return the actual number of bytes read, or -1 if the end of the
|
||||
* entry is reached
|
||||
* @exception NullPointerException If <code>b</code> is <code>null</code>.
|
||||
* @exception IndexOutOfBoundsException If <code>off</code> is negative,
|
||||
* <code>len</code> is negative, or <code>len</code> is greater than
|
||||
* <code>b.length - off</code>
|
||||
* @exception ZipException if a ZIP file error has occurred
|
||||
* @exception IOException if an I/O error has occurred
|
||||
* @exception SecurityException if any of the jar file entries
|
||||
* are incorrectly signed.
|
||||
*/
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int n;
|
||||
if (first == null) {
|
||||
n = super.read(b, off, len);
|
||||
} else {
|
||||
n = -1;
|
||||
}
|
||||
if (jv != null) {
|
||||
jv.update(n, b, off, len, mev);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarEntry</code> (<code>ZipEntry</code>) for the
|
||||
* specified JAR file entry name. The manifest attributes of
|
||||
* the specified JAR file entry name will be copied to the new
|
||||
* <CODE>JarEntry</CODE>.
|
||||
*
|
||||
* @param name the name of the JAR/ZIP file entry
|
||||
* @return the <code>JarEntry</code> object just created
|
||||
*/
|
||||
protected ZipEntry createZipEntry(String name) {
|
||||
JarEntry e = new JarEntry(name);
|
||||
if (man != null) {
|
||||
e.attr = man.getAttributes(name);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
149
src/java.base/share/classes/java/util/jar/JarOutputStream.java
Normal file
149
src/java.base/share/classes/java/util/jar/JarOutputStream.java
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 2012, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.util.zip.*;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* The <code>JarOutputStream</code> class is used to write the contents
|
||||
* of a JAR file to any output stream. It extends the class
|
||||
* <code>java.util.zip.ZipOutputStream</code> with support
|
||||
* for writing an optional <code>Manifest</code> entry. The
|
||||
* <code>Manifest</code> can be used to specify meta-information about
|
||||
* the JAR file and its entries.
|
||||
*
|
||||
* @author David Connelly
|
||||
* @see Manifest
|
||||
* @see java.util.zip.ZipOutputStream
|
||||
* @since 1.2
|
||||
*/
|
||||
public
|
||||
class JarOutputStream extends ZipOutputStream {
|
||||
private static final int JAR_MAGIC = 0xCAFE;
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarOutputStream</code> with the specified
|
||||
* <code>Manifest</code>. The manifest is written as the first
|
||||
* entry to the output stream.
|
||||
*
|
||||
* @param out the actual output stream
|
||||
* @param man the optional <code>Manifest</code>
|
||||
* @exception IOException if an I/O error has occurred
|
||||
*/
|
||||
public JarOutputStream(OutputStream out, Manifest man) throws IOException {
|
||||
super(out);
|
||||
if (man == null) {
|
||||
throw new NullPointerException("man");
|
||||
}
|
||||
ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
|
||||
putNextEntry(e);
|
||||
man.write(new BufferedOutputStream(this));
|
||||
closeEntry();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new <code>JarOutputStream</code> with no manifest.
|
||||
* @param out the actual output stream
|
||||
* @exception IOException if an I/O error has occurred
|
||||
*/
|
||||
public JarOutputStream(OutputStream out) throws IOException {
|
||||
super(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins writing a new JAR file entry and positions the stream
|
||||
* to the start of the entry data. This method will also close
|
||||
* any previous entry. The default compression method will be
|
||||
* used if no compression method was specified for the entry.
|
||||
* The current time will be used if the entry has no set modification
|
||||
* time.
|
||||
*
|
||||
* @param ze the ZIP/JAR entry to be written
|
||||
* @exception ZipException if a ZIP error has occurred
|
||||
* @exception IOException if an I/O error has occurred
|
||||
*/
|
||||
public void putNextEntry(ZipEntry ze) throws IOException {
|
||||
if (firstEntry) {
|
||||
// Make sure that extra field data for first JAR
|
||||
// entry includes JAR magic number id.
|
||||
byte[] edata = ze.getExtra();
|
||||
if (edata == null || !hasMagic(edata)) {
|
||||
if (edata == null) {
|
||||
edata = new byte[4];
|
||||
} else {
|
||||
// Prepend magic to existing extra data
|
||||
byte[] tmp = new byte[edata.length + 4];
|
||||
System.arraycopy(edata, 0, tmp, 4, edata.length);
|
||||
edata = tmp;
|
||||
}
|
||||
set16(edata, 0, JAR_MAGIC); // extra field id
|
||||
set16(edata, 2, 0); // extra field size
|
||||
ze.setExtra(edata);
|
||||
}
|
||||
firstEntry = false;
|
||||
}
|
||||
super.putNextEntry(ze);
|
||||
}
|
||||
|
||||
private boolean firstEntry = true;
|
||||
|
||||
/*
|
||||
* Returns true if specified byte array contains the
|
||||
* jar magic extra field id.
|
||||
*/
|
||||
private static boolean hasMagic(byte[] edata) {
|
||||
try {
|
||||
int i = 0;
|
||||
while (i < edata.length) {
|
||||
if (get16(edata, i) == JAR_MAGIC) {
|
||||
return true;
|
||||
}
|
||||
i += get16(edata, i + 2) + 4;
|
||||
}
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// Invalid extra field data
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fetches unsigned 16-bit value from byte array at specified offset.
|
||||
* The bytes are assumed to be in Intel (little-endian) byte order.
|
||||
*/
|
||||
private static int get16(byte[] b, int off) {
|
||||
return Byte.toUnsignedInt(b[off]) | ( Byte.toUnsignedInt(b[off+1]) << 8);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets 16-bit value at specified offset. The bytes are assumed to
|
||||
* be in Intel (little-endian) byte order.
|
||||
*/
|
||||
private static void set16(byte[] b, int off, int value) {
|
||||
b[off+0] = (byte)value;
|
||||
b[off+1] = (byte)(value >> 8);
|
||||
}
|
||||
}
|
871
src/java.base/share/classes/java/util/jar/JarVerifier.java
Normal file
871
src/java.base/share/classes/java/util/jar/JarVerifier.java
Normal file
|
@ -0,0 +1,871 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 2017, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.security.*;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import jdk.internal.util.jar.JarIndex;
|
||||
import sun.security.util.ManifestDigester;
|
||||
import sun.security.util.ManifestEntryVerifier;
|
||||
import sun.security.util.SignatureFileVerifier;
|
||||
import sun.security.util.Debug;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Roland Schemers
|
||||
*/
|
||||
class JarVerifier {
|
||||
|
||||
/* Are we debugging ? */
|
||||
static final Debug debug = Debug.getInstance("jar");
|
||||
|
||||
/* a table mapping names to code signers, for jar entries that have
|
||||
had their actual hashes verified */
|
||||
private Hashtable<String, CodeSigner[]> verifiedSigners;
|
||||
|
||||
/* a table mapping names to code signers, for jar entries that have
|
||||
passed the .SF/.DSA/.EC -> MANIFEST check */
|
||||
private Hashtable<String, CodeSigner[]> sigFileSigners;
|
||||
|
||||
/* a hash table to hold .SF bytes */
|
||||
private Hashtable<String, byte[]> sigFileData;
|
||||
|
||||
/** "queue" of pending PKCS7 blocks that we couldn't parse
|
||||
* until we parsed the .SF file */
|
||||
private ArrayList<SignatureFileVerifier> pendingBlocks;
|
||||
|
||||
/* cache of CodeSigner objects */
|
||||
private ArrayList<CodeSigner[]> signerCache;
|
||||
|
||||
/* Are we parsing a block? */
|
||||
private boolean parsingBlockOrSF = false;
|
||||
|
||||
/* Are we done parsing META-INF entries? */
|
||||
private boolean parsingMeta = true;
|
||||
|
||||
/* Are there are files to verify? */
|
||||
private boolean anyToVerify = true;
|
||||
|
||||
/* The output stream to use when keeping track of files we are interested
|
||||
in */
|
||||
private ByteArrayOutputStream baos;
|
||||
|
||||
/** The ManifestDigester object */
|
||||
private volatile ManifestDigester manDig;
|
||||
|
||||
/** the bytes for the manDig object */
|
||||
byte manifestRawBytes[] = null;
|
||||
|
||||
/** controls eager signature validation */
|
||||
boolean eagerValidation;
|
||||
|
||||
/** makes code source singleton instances unique to us */
|
||||
private Object csdomain = new Object();
|
||||
|
||||
/** collect -DIGEST-MANIFEST values for blacklist */
|
||||
private List<Object> manifestDigests;
|
||||
|
||||
public JarVerifier(byte rawBytes[]) {
|
||||
manifestRawBytes = rawBytes;
|
||||
sigFileSigners = new Hashtable<>();
|
||||
verifiedSigners = new Hashtable<>();
|
||||
sigFileData = new Hashtable<>(11);
|
||||
pendingBlocks = new ArrayList<>();
|
||||
baos = new ByteArrayOutputStream();
|
||||
manifestDigests = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method scans to see which entry we're parsing and
|
||||
* keeps various state information depending on what type of
|
||||
* file is being parsed.
|
||||
*/
|
||||
public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
|
||||
throws IOException
|
||||
{
|
||||
if (je == null)
|
||||
return;
|
||||
|
||||
if (debug != null) {
|
||||
debug.println("beginEntry "+je.getName());
|
||||
}
|
||||
|
||||
String name = je.getName();
|
||||
|
||||
/*
|
||||
* Assumptions:
|
||||
* 1. The manifest should be the first entry in the META-INF directory.
|
||||
* 2. The .SF/.DSA/.EC files follow the manifest, before any normal entries
|
||||
* 3. Any of the following will throw a SecurityException:
|
||||
* a. digest mismatch between a manifest section and
|
||||
* the SF section.
|
||||
* b. digest mismatch between the actual jar entry and the manifest
|
||||
*/
|
||||
|
||||
if (parsingMeta) {
|
||||
String uname = name.toUpperCase(Locale.ENGLISH);
|
||||
if ((uname.startsWith("META-INF/") ||
|
||||
uname.startsWith("/META-INF/"))) {
|
||||
|
||||
if (je.isDirectory()) {
|
||||
mev.setEntry(null, je);
|
||||
return;
|
||||
}
|
||||
|
||||
if (uname.equals(JarFile.MANIFEST_NAME) ||
|
||||
uname.equals(JarIndex.INDEX_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SignatureFileVerifier.isBlockOrSF(uname)) {
|
||||
/* We parse only DSA, RSA or EC PKCS7 blocks. */
|
||||
parsingBlockOrSF = true;
|
||||
baos.reset();
|
||||
mev.setEntry(null, je);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a META-INF entry is not MF or block or SF, they should
|
||||
// be normal entries. According to 2 above, no more block or
|
||||
// SF will appear. Let's doneWithMeta.
|
||||
}
|
||||
}
|
||||
|
||||
if (parsingMeta) {
|
||||
doneWithMeta();
|
||||
}
|
||||
|
||||
if (je.isDirectory()) {
|
||||
mev.setEntry(null, je);
|
||||
return;
|
||||
}
|
||||
|
||||
// be liberal in what you accept. If the name starts with ./, remove
|
||||
// it as we internally canonicalize it with out the ./.
|
||||
if (name.startsWith("./"))
|
||||
name = name.substring(2);
|
||||
|
||||
// be liberal in what you accept. If the name starts with /, remove
|
||||
// it as we internally canonicalize it with out the /.
|
||||
if (name.startsWith("/"))
|
||||
name = name.substring(1);
|
||||
|
||||
// only set the jev object for entries that have a signature
|
||||
// (either verified or not)
|
||||
if (!name.equals(JarFile.MANIFEST_NAME)) {
|
||||
if (sigFileSigners.get(name) != null ||
|
||||
verifiedSigners.get(name) != null) {
|
||||
mev.setEntry(name, je);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// don't compute the digest for this entry
|
||||
mev.setEntry(null, je);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* update a single byte.
|
||||
*/
|
||||
|
||||
public void update(int b, ManifestEntryVerifier mev)
|
||||
throws IOException
|
||||
{
|
||||
if (b != -1) {
|
||||
if (parsingBlockOrSF) {
|
||||
baos.write(b);
|
||||
} else {
|
||||
mev.update((byte)b);
|
||||
}
|
||||
} else {
|
||||
processEntry(mev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update an array of bytes.
|
||||
*/
|
||||
|
||||
public void update(int n, byte[] b, int off, int len,
|
||||
ManifestEntryVerifier mev)
|
||||
throws IOException
|
||||
{
|
||||
if (n != -1) {
|
||||
if (parsingBlockOrSF) {
|
||||
baos.write(b, off, n);
|
||||
} else {
|
||||
mev.update(b, off, n);
|
||||
}
|
||||
} else {
|
||||
processEntry(mev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* called when we reach the end of entry in one of the read() methods.
|
||||
*/
|
||||
private void processEntry(ManifestEntryVerifier mev)
|
||||
throws IOException
|
||||
{
|
||||
if (!parsingBlockOrSF) {
|
||||
JarEntry je = mev.getEntry();
|
||||
if ((je != null) && (je.signers == null)) {
|
||||
je.signers = mev.verify(verifiedSigners, sigFileSigners);
|
||||
je.certs = mapSignersToCertArray(je.signers);
|
||||
}
|
||||
} else {
|
||||
|
||||
try {
|
||||
parsingBlockOrSF = false;
|
||||
|
||||
if (debug != null) {
|
||||
debug.println("processEntry: processing block");
|
||||
}
|
||||
|
||||
String uname = mev.getEntry().getName()
|
||||
.toUpperCase(Locale.ENGLISH);
|
||||
|
||||
if (uname.endsWith(".SF")) {
|
||||
String key = uname.substring(0, uname.length()-3);
|
||||
byte bytes[] = baos.toByteArray();
|
||||
// add to sigFileData in case future blocks need it
|
||||
sigFileData.put(key, bytes);
|
||||
// check pending blocks, we can now process
|
||||
// anyone waiting for this .SF file
|
||||
for (SignatureFileVerifier sfv : pendingBlocks) {
|
||||
if (sfv.needSignatureFile(key)) {
|
||||
if (debug != null) {
|
||||
debug.println(
|
||||
"processEntry: processing pending block");
|
||||
}
|
||||
|
||||
sfv.setSignatureFile(bytes);
|
||||
sfv.process(sigFileSigners, manifestDigests);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// now we are parsing a signature block file
|
||||
|
||||
String key = uname.substring(0, uname.lastIndexOf('.'));
|
||||
|
||||
if (signerCache == null)
|
||||
signerCache = new ArrayList<>();
|
||||
|
||||
if (manDig == null) {
|
||||
synchronized(manifestRawBytes) {
|
||||
if (manDig == null) {
|
||||
manDig = new ManifestDigester(manifestRawBytes);
|
||||
manifestRawBytes = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SignatureFileVerifier sfv =
|
||||
new SignatureFileVerifier(signerCache,
|
||||
manDig, uname, baos.toByteArray());
|
||||
|
||||
if (sfv.needSignatureFileBytes()) {
|
||||
// see if we have already parsed an external .SF file
|
||||
byte[] bytes = sigFileData.get(key);
|
||||
|
||||
if (bytes == null) {
|
||||
// put this block on queue for later processing
|
||||
// since we don't have the .SF bytes yet
|
||||
// (uname, block);
|
||||
if (debug != null) {
|
||||
debug.println("adding pending block");
|
||||
}
|
||||
pendingBlocks.add(sfv);
|
||||
return;
|
||||
} else {
|
||||
sfv.setSignatureFile(bytes);
|
||||
}
|
||||
}
|
||||
sfv.process(sigFileSigners, manifestDigests);
|
||||
|
||||
} catch (IOException | CertificateException |
|
||||
NoSuchAlgorithmException | SignatureException e) {
|
||||
if (debug != null) debug.println("processEntry caught: "+e);
|
||||
// ignore and treat as unsigned
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of java.security.cert.Certificate objects for
|
||||
* the given file in the jar.
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public java.security.cert.Certificate[] getCerts(String name)
|
||||
{
|
||||
return mapSignersToCertArray(getCodeSigners(name));
|
||||
}
|
||||
|
||||
public java.security.cert.Certificate[] getCerts(JarFile jar, JarEntry entry)
|
||||
{
|
||||
return mapSignersToCertArray(getCodeSigners(jar, entry));
|
||||
}
|
||||
|
||||
/**
|
||||
* return an array of CodeSigner objects for
|
||||
* the given file in the jar. this array is not cloned.
|
||||
*
|
||||
*/
|
||||
public CodeSigner[] getCodeSigners(String name)
|
||||
{
|
||||
return verifiedSigners.get(name);
|
||||
}
|
||||
|
||||
public CodeSigner[] getCodeSigners(JarFile jar, JarEntry entry)
|
||||
{
|
||||
String name = entry.getName();
|
||||
if (eagerValidation && sigFileSigners.get(name) != null) {
|
||||
/*
|
||||
* Force a read of the entry data to generate the
|
||||
* verification hash.
|
||||
*/
|
||||
try {
|
||||
InputStream s = jar.getInputStream(entry);
|
||||
byte[] buffer = new byte[1024];
|
||||
int n = buffer.length;
|
||||
while (n != -1) {
|
||||
n = s.read(buffer, 0, buffer.length);
|
||||
}
|
||||
s.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
return getCodeSigners(name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of signers into an array of concatenated certificate
|
||||
* arrays.
|
||||
*/
|
||||
private static java.security.cert.Certificate[] mapSignersToCertArray(
|
||||
CodeSigner[] signers) {
|
||||
|
||||
if (signers != null) {
|
||||
ArrayList<java.security.cert.Certificate> certChains = new ArrayList<>();
|
||||
for (CodeSigner signer : signers) {
|
||||
certChains.addAll(
|
||||
signer.getSignerCertPath().getCertificates());
|
||||
}
|
||||
|
||||
// Convert into a Certificate[]
|
||||
return certChains.toArray(
|
||||
new java.security.cert.Certificate[certChains.size()]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if there no files to verify.
|
||||
* should only be called after all the META-INF entries
|
||||
* have been processed.
|
||||
*/
|
||||
boolean nothingToVerify()
|
||||
{
|
||||
return (anyToVerify == false);
|
||||
}
|
||||
|
||||
/**
|
||||
* called to let us know we have processed all the
|
||||
* META-INF entries, and if we re-read one of them, don't
|
||||
* re-process it. Also gets rid of any data structures
|
||||
* we needed when parsing META-INF entries.
|
||||
*/
|
||||
void doneWithMeta()
|
||||
{
|
||||
parsingMeta = false;
|
||||
anyToVerify = !sigFileSigners.isEmpty();
|
||||
baos = null;
|
||||
sigFileData = null;
|
||||
pendingBlocks = null;
|
||||
signerCache = null;
|
||||
manDig = null;
|
||||
// MANIFEST.MF is always treated as signed and verified,
|
||||
// move its signers from sigFileSigners to verifiedSigners.
|
||||
if (sigFileSigners.containsKey(JarFile.MANIFEST_NAME)) {
|
||||
CodeSigner[] codeSigners = sigFileSigners.remove(JarFile.MANIFEST_NAME);
|
||||
verifiedSigners.put(JarFile.MANIFEST_NAME, codeSigners);
|
||||
}
|
||||
}
|
||||
|
||||
static class VerifierStream extends java.io.InputStream {
|
||||
|
||||
private InputStream is;
|
||||
private JarVerifier jv;
|
||||
private ManifestEntryVerifier mev;
|
||||
private long numLeft;
|
||||
|
||||
VerifierStream(Manifest man,
|
||||
JarEntry je,
|
||||
InputStream is,
|
||||
JarVerifier jv) throws IOException
|
||||
{
|
||||
this.is = is;
|
||||
this.jv = jv;
|
||||
this.mev = new ManifestEntryVerifier(man);
|
||||
this.jv.beginEntry(je, mev);
|
||||
this.numLeft = je.getSize();
|
||||
if (this.numLeft == 0)
|
||||
this.jv.update(-1, this.mev);
|
||||
}
|
||||
|
||||
public int read() throws IOException
|
||||
{
|
||||
if (numLeft > 0) {
|
||||
int b = is.read();
|
||||
jv.update(b, mev);
|
||||
numLeft--;
|
||||
if (numLeft == 0)
|
||||
jv.update(-1, mev);
|
||||
return b;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int read(byte b[], int off, int len) throws IOException {
|
||||
if ((numLeft > 0) && (numLeft < len)) {
|
||||
len = (int)numLeft;
|
||||
}
|
||||
|
||||
if (numLeft > 0) {
|
||||
int n = is.read(b, off, len);
|
||||
jv.update(n, b, off, len, mev);
|
||||
numLeft -= n;
|
||||
if (numLeft == 0)
|
||||
jv.update(-1, b, off, len, mev);
|
||||
return n;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void close()
|
||||
throws IOException
|
||||
{
|
||||
if (is != null)
|
||||
is.close();
|
||||
is = null;
|
||||
mev = null;
|
||||
jv = null;
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return is.available();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Extended JavaUtilJarAccess CodeSource API Support
|
||||
|
||||
private Map<URL, Map<CodeSigner[], CodeSource>> urlToCodeSourceMap = new HashMap<>();
|
||||
private Map<CodeSigner[], CodeSource> signerToCodeSource = new HashMap<>();
|
||||
private URL lastURL;
|
||||
private Map<CodeSigner[], CodeSource> lastURLMap;
|
||||
|
||||
/*
|
||||
* Create a unique mapping from codeSigner cache entries to CodeSource.
|
||||
* In theory, multiple URLs origins could map to a single locally cached
|
||||
* and shared JAR file although in practice there will be a single URL in use.
|
||||
*/
|
||||
private synchronized CodeSource mapSignersToCodeSource(URL url, CodeSigner[] signers) {
|
||||
Map<CodeSigner[], CodeSource> map;
|
||||
if (url == lastURL) {
|
||||
map = lastURLMap;
|
||||
} else {
|
||||
map = urlToCodeSourceMap.get(url);
|
||||
if (map == null) {
|
||||
map = new HashMap<>();
|
||||
urlToCodeSourceMap.put(url, map);
|
||||
}
|
||||
lastURLMap = map;
|
||||
lastURL = url;
|
||||
}
|
||||
CodeSource cs = map.get(signers);
|
||||
if (cs == null) {
|
||||
cs = new VerifierCodeSource(csdomain, url, signers);
|
||||
signerToCodeSource.put(signers, cs);
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
|
||||
private CodeSource[] mapSignersToCodeSources(URL url, List<CodeSigner[]> signers, boolean unsigned) {
|
||||
List<CodeSource> sources = new ArrayList<>();
|
||||
|
||||
for (CodeSigner[] signer : signers) {
|
||||
sources.add(mapSignersToCodeSource(url, signer));
|
||||
}
|
||||
if (unsigned) {
|
||||
sources.add(mapSignersToCodeSource(url, null));
|
||||
}
|
||||
return sources.toArray(new CodeSource[sources.size()]);
|
||||
}
|
||||
private CodeSigner[] emptySigner = new CodeSigner[0];
|
||||
|
||||
/*
|
||||
* Match CodeSource to a CodeSigner[] in the signer cache.
|
||||
*/
|
||||
private CodeSigner[] findMatchingSigners(CodeSource cs) {
|
||||
if (cs instanceof VerifierCodeSource) {
|
||||
VerifierCodeSource vcs = (VerifierCodeSource) cs;
|
||||
if (vcs.isSameDomain(csdomain)) {
|
||||
return ((VerifierCodeSource) cs).getPrivateSigners();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* In practice signers should always be optimized above
|
||||
* but this handles a CodeSource of any type, just in case.
|
||||
*/
|
||||
CodeSource[] sources = mapSignersToCodeSources(cs.getLocation(), getJarCodeSigners(), true);
|
||||
List<CodeSource> sourceList = new ArrayList<>();
|
||||
for (CodeSource source : sources) {
|
||||
sourceList.add(source);
|
||||
}
|
||||
int j = sourceList.indexOf(cs);
|
||||
if (j != -1) {
|
||||
CodeSigner[] match;
|
||||
match = ((VerifierCodeSource) sourceList.get(j)).getPrivateSigners();
|
||||
if (match == null) {
|
||||
match = emptySigner;
|
||||
}
|
||||
return match;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Instances of this class hold uncopied references to internal
|
||||
* signing data that can be compared by object reference identity.
|
||||
*/
|
||||
private static class VerifierCodeSource extends CodeSource {
|
||||
private static final long serialVersionUID = -9047366145967768825L;
|
||||
|
||||
URL vlocation;
|
||||
CodeSigner[] vsigners;
|
||||
java.security.cert.Certificate[] vcerts;
|
||||
Object csdomain;
|
||||
|
||||
VerifierCodeSource(Object csdomain, URL location, CodeSigner[] signers) {
|
||||
super(location, signers);
|
||||
this.csdomain = csdomain;
|
||||
vlocation = location;
|
||||
vsigners = signers; // from signerCache
|
||||
}
|
||||
|
||||
VerifierCodeSource(Object csdomain, URL location, java.security.cert.Certificate[] certs) {
|
||||
super(location, certs);
|
||||
this.csdomain = csdomain;
|
||||
vlocation = location;
|
||||
vcerts = certs; // from signerCache
|
||||
}
|
||||
|
||||
/*
|
||||
* All VerifierCodeSource instances are constructed based on
|
||||
* singleton signerCache or signerCacheCert entries for each unique signer.
|
||||
* No CodeSigner<->Certificate[] conversion is required.
|
||||
* We use these assumptions to optimize equality comparisons.
|
||||
*/
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof VerifierCodeSource) {
|
||||
VerifierCodeSource that = (VerifierCodeSource) obj;
|
||||
|
||||
/*
|
||||
* Only compare against other per-signer singletons constructed
|
||||
* on behalf of the same JarFile instance. Otherwise, compare
|
||||
* things the slower way.
|
||||
*/
|
||||
if (isSameDomain(that.csdomain)) {
|
||||
if (that.vsigners != this.vsigners
|
||||
|| that.vcerts != this.vcerts) {
|
||||
return false;
|
||||
}
|
||||
if (that.vlocation != null) {
|
||||
return that.vlocation.equals(this.vlocation);
|
||||
} else if (this.vlocation != null) {
|
||||
return this.vlocation.equals(that.vlocation);
|
||||
} else { // both null
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
boolean isSameDomain(Object csdomain) {
|
||||
return this.csdomain == csdomain;
|
||||
}
|
||||
|
||||
private CodeSigner[] getPrivateSigners() {
|
||||
return vsigners;
|
||||
}
|
||||
|
||||
private java.security.cert.Certificate[] getPrivateCertificates() {
|
||||
return vcerts;
|
||||
}
|
||||
}
|
||||
private Map<String, CodeSigner[]> signerMap;
|
||||
|
||||
private synchronized Map<String, CodeSigner[]> signerMap() {
|
||||
if (signerMap == null) {
|
||||
/*
|
||||
* Snapshot signer state so it doesn't change on us. We care
|
||||
* only about the asserted signatures. Verification of
|
||||
* signature validity happens via the JarEntry apis.
|
||||
*/
|
||||
signerMap = new HashMap<>(verifiedSigners.size() + sigFileSigners.size());
|
||||
signerMap.putAll(verifiedSigners);
|
||||
signerMap.putAll(sigFileSigners);
|
||||
}
|
||||
return signerMap;
|
||||
}
|
||||
|
||||
public synchronized Enumeration<String> entryNames(JarFile jar, final CodeSource[] cs) {
|
||||
final Map<String, CodeSigner[]> map = signerMap();
|
||||
final Iterator<Map.Entry<String, CodeSigner[]>> itor = map.entrySet().iterator();
|
||||
boolean matchUnsigned = false;
|
||||
|
||||
/*
|
||||
* Grab a single copy of the CodeSigner arrays. Check
|
||||
* to see if we can optimize CodeSigner equality test.
|
||||
*/
|
||||
List<CodeSigner[]> req = new ArrayList<>(cs.length);
|
||||
for (CodeSource c : cs) {
|
||||
CodeSigner[] match = findMatchingSigners(c);
|
||||
if (match != null) {
|
||||
if (match.length > 0) {
|
||||
req.add(match);
|
||||
} else {
|
||||
matchUnsigned = true;
|
||||
}
|
||||
} else {
|
||||
matchUnsigned = true;
|
||||
}
|
||||
}
|
||||
|
||||
final List<CodeSigner[]> signersReq = req;
|
||||
final Enumeration<String> enum2 = (matchUnsigned) ? unsignedEntryNames(jar) : emptyEnumeration;
|
||||
|
||||
return new Enumeration<>() {
|
||||
|
||||
String name;
|
||||
|
||||
public boolean hasMoreElements() {
|
||||
if (name != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
while (itor.hasNext()) {
|
||||
Map.Entry<String, CodeSigner[]> e = itor.next();
|
||||
if (signersReq.contains(e.getValue())) {
|
||||
name = e.getKey();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
while (enum2.hasMoreElements()) {
|
||||
name = enum2.nextElement();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String nextElement() {
|
||||
if (hasMoreElements()) {
|
||||
String value = name;
|
||||
name = null;
|
||||
return value;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Like entries() but screens out internal JAR mechanism entries
|
||||
* and includes signed entries with no ZIP data.
|
||||
*/
|
||||
public Enumeration<JarEntry> entries2(final JarFile jar, Enumeration<? extends ZipEntry> e) {
|
||||
final Map<String, CodeSigner[]> map = new HashMap<>();
|
||||
map.putAll(signerMap());
|
||||
final Enumeration<? extends ZipEntry> enum_ = e;
|
||||
return new Enumeration<>() {
|
||||
|
||||
Enumeration<String> signers = null;
|
||||
JarEntry entry;
|
||||
|
||||
public boolean hasMoreElements() {
|
||||
if (entry != null) {
|
||||
return true;
|
||||
}
|
||||
while (enum_.hasMoreElements()) {
|
||||
ZipEntry ze = enum_.nextElement();
|
||||
if (JarVerifier.isSigningRelated(ze.getName())) {
|
||||
continue;
|
||||
}
|
||||
entry = jar.newEntry(ze);
|
||||
return true;
|
||||
}
|
||||
if (signers == null) {
|
||||
signers = Collections.enumeration(map.keySet());
|
||||
}
|
||||
while (signers.hasMoreElements()) {
|
||||
String name = signers.nextElement();
|
||||
entry = jar.newEntry(new ZipEntry(name));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Any map entries left?
|
||||
return false;
|
||||
}
|
||||
|
||||
public JarEntry nextElement() {
|
||||
if (hasMoreElements()) {
|
||||
JarEntry je = entry;
|
||||
map.remove(je.getName());
|
||||
entry = null;
|
||||
return je;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
private Enumeration<String> emptyEnumeration = new Enumeration<String>() {
|
||||
|
||||
public boolean hasMoreElements() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String nextElement() {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
|
||||
// true if file is part of the signature mechanism itself
|
||||
static boolean isSigningRelated(String name) {
|
||||
return SignatureFileVerifier.isSigningRelated(name);
|
||||
}
|
||||
|
||||
private Enumeration<String> unsignedEntryNames(JarFile jar) {
|
||||
final Map<String, CodeSigner[]> map = signerMap();
|
||||
final Enumeration<JarEntry> entries = jar.entries();
|
||||
return new Enumeration<>() {
|
||||
|
||||
String name;
|
||||
|
||||
/*
|
||||
* Grab entries from ZIP directory but screen out
|
||||
* metadata.
|
||||
*/
|
||||
public boolean hasMoreElements() {
|
||||
if (name != null) {
|
||||
return true;
|
||||
}
|
||||
while (entries.hasMoreElements()) {
|
||||
String value;
|
||||
ZipEntry e = entries.nextElement();
|
||||
value = e.getName();
|
||||
if (e.isDirectory() || isSigningRelated(value)) {
|
||||
continue;
|
||||
}
|
||||
if (map.get(value) == null) {
|
||||
name = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String nextElement() {
|
||||
if (hasMoreElements()) {
|
||||
String value = name;
|
||||
name = null;
|
||||
return value;
|
||||
}
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
};
|
||||
}
|
||||
private List<CodeSigner[]> jarCodeSigners;
|
||||
|
||||
private synchronized List<CodeSigner[]> getJarCodeSigners() {
|
||||
CodeSigner[] signers;
|
||||
if (jarCodeSigners == null) {
|
||||
HashSet<CodeSigner[]> set = new HashSet<>();
|
||||
set.addAll(signerMap().values());
|
||||
jarCodeSigners = new ArrayList<>();
|
||||
jarCodeSigners.addAll(set);
|
||||
}
|
||||
return jarCodeSigners;
|
||||
}
|
||||
|
||||
public synchronized CodeSource[] getCodeSources(JarFile jar, URL url) {
|
||||
boolean hasUnsigned = unsignedEntryNames(jar).hasMoreElements();
|
||||
|
||||
return mapSignersToCodeSources(url, getJarCodeSigners(), hasUnsigned);
|
||||
}
|
||||
|
||||
public CodeSource getCodeSource(URL url, String name) {
|
||||
CodeSigner[] signers;
|
||||
|
||||
signers = signerMap().get(name);
|
||||
return mapSignersToCodeSource(url, signers);
|
||||
}
|
||||
|
||||
public CodeSource getCodeSource(URL url, JarFile jar, JarEntry je) {
|
||||
CodeSigner[] signers;
|
||||
|
||||
return mapSignersToCodeSource(url, getCodeSigners(jar, je));
|
||||
}
|
||||
|
||||
public void setEagerValidation(boolean eager) {
|
||||
eagerValidation = eager;
|
||||
}
|
||||
|
||||
public synchronized List<Object> getManifestDigests() {
|
||||
return Collections.unmodifiableList(manifestDigests);
|
||||
}
|
||||
|
||||
static CodeSource getUnsignedCS(URL url) {
|
||||
return new VerifierCodeSource(null, url, (java.security.cert.Certificate[]) null);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2002, 2013, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSource;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import jdk.internal.misc.JavaUtilJarAccess;
|
||||
|
||||
class JavaUtilJarAccessImpl implements JavaUtilJarAccess {
|
||||
public boolean jarFileHasClassPathAttribute(JarFile jar) throws IOException {
|
||||
return jar.hasClassPathAttribute();
|
||||
}
|
||||
|
||||
public CodeSource[] getCodeSources(JarFile jar, URL url) {
|
||||
return jar.getCodeSources(url);
|
||||
}
|
||||
|
||||
public CodeSource getCodeSource(JarFile jar, URL url, String name) {
|
||||
return jar.getCodeSource(url, name);
|
||||
}
|
||||
|
||||
public Enumeration<String> entryNames(JarFile jar, CodeSource[] cs) {
|
||||
return jar.entryNames(cs);
|
||||
}
|
||||
|
||||
public Enumeration<JarEntry> entries2(JarFile jar) {
|
||||
return jar.entries2();
|
||||
}
|
||||
|
||||
public void setEagerValidation(JarFile jar, boolean eager) {
|
||||
jar.setEagerValidation(eager);
|
||||
}
|
||||
|
||||
public List<Object> getManifestDigests(JarFile jar) {
|
||||
return jar.getManifestDigests();
|
||||
}
|
||||
|
||||
public String getRealName(JarFile jar, JarEntry entry) {
|
||||
return jar.getRealName(entry);
|
||||
}
|
||||
}
|
446
src/java.base/share/classes/java/util/jar/Manifest.java
Normal file
446
src/java.base/share/classes/java/util/jar/Manifest.java
Normal file
|
@ -0,0 +1,446 @@
|
|||
/*
|
||||
* Copyright (c) 1997, 2013, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util.jar;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* The Manifest class is used to maintain Manifest entry names and their
|
||||
* associated Attributes. There are main Manifest Attributes as well as
|
||||
* per-entry Attributes. For information on the Manifest format, please
|
||||
* see the
|
||||
* <a href="{@docRoot}/../specs/jar/jar.html">
|
||||
* Manifest format specification</a>.
|
||||
*
|
||||
* @author David Connelly
|
||||
* @see Attributes
|
||||
* @since 1.2
|
||||
*/
|
||||
public class Manifest implements Cloneable {
|
||||
// manifest main attributes
|
||||
private Attributes attr = new Attributes();
|
||||
|
||||
// manifest entries
|
||||
private Map<String, Attributes> entries = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs a new, empty Manifest.
|
||||
*/
|
||||
public Manifest() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Manifest from the specified input stream.
|
||||
*
|
||||
* @param is the input stream containing manifest data
|
||||
* @throws IOException if an I/O error has occurred
|
||||
*/
|
||||
public Manifest(InputStream is) throws IOException {
|
||||
read(is);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new Manifest that is a copy of the specified Manifest.
|
||||
*
|
||||
* @param man the Manifest to copy
|
||||
*/
|
||||
public Manifest(Manifest man) {
|
||||
attr.putAll(man.getMainAttributes());
|
||||
entries.putAll(man.getEntries());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the main Attributes for the Manifest.
|
||||
* @return the main Attributes for the Manifest
|
||||
*/
|
||||
public Attributes getMainAttributes() {
|
||||
return attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map of the entries contained in this Manifest. Each entry
|
||||
* is represented by a String name (key) and associated Attributes (value).
|
||||
* The Map permits the {@code null} key, but no entry with a null key is
|
||||
* created by {@link #read}, nor is such an entry written by using {@link
|
||||
* #write}.
|
||||
*
|
||||
* @return a Map of the entries contained in this Manifest
|
||||
*/
|
||||
public Map<String,Attributes> getEntries() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Attributes for the specified entry name.
|
||||
* This method is defined as:
|
||||
* <pre>
|
||||
* return (Attributes)getEntries().get(name)
|
||||
* </pre>
|
||||
* Though {@code null} is a valid {@code name}, when
|
||||
* {@code getAttributes(null)} is invoked on a {@code Manifest}
|
||||
* obtained from a jar file, {@code null} will be returned. While jar
|
||||
* files themselves do not allow {@code null}-named attributes, it is
|
||||
* possible to invoke {@link #getEntries} on a {@code Manifest}, and
|
||||
* on that result, invoke {@code put} with a null key and an
|
||||
* arbitrary value. Subsequent invocations of
|
||||
* {@code getAttributes(null)} will return the just-{@code put}
|
||||
* value.
|
||||
* <p>
|
||||
* Note that this method does not return the manifest's main attributes;
|
||||
* see {@link #getMainAttributes}.
|
||||
*
|
||||
* @param name entry name
|
||||
* @return the Attributes for the specified entry name
|
||||
*/
|
||||
public Attributes getAttributes(String name) {
|
||||
return getEntries().get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the main Attributes as well as the entries in this Manifest.
|
||||
*/
|
||||
public void clear() {
|
||||
attr.clear();
|
||||
entries.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the Manifest to the specified OutputStream.
|
||||
* Attributes.Name.MANIFEST_VERSION must be set in
|
||||
* MainAttributes prior to invoking this method.
|
||||
*
|
||||
* @param out the output stream
|
||||
* @exception IOException if an I/O error has occurred
|
||||
* @see #getMainAttributes
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void write(OutputStream out) throws IOException {
|
||||
DataOutputStream dos = new DataOutputStream(out);
|
||||
// Write out the main attributes for the manifest
|
||||
attr.writeMain(dos);
|
||||
// Now write out the pre-entry attributes
|
||||
for (Map.Entry<String, Attributes> e : entries.entrySet()) {
|
||||
StringBuffer buffer = new StringBuffer("Name: ");
|
||||
String value = e.getKey();
|
||||
if (value != null) {
|
||||
byte[] vb = value.getBytes("UTF8");
|
||||
value = new String(vb, 0, 0, vb.length);
|
||||
}
|
||||
buffer.append(value);
|
||||
buffer.append("\r\n");
|
||||
make72Safe(buffer);
|
||||
dos.writeBytes(buffer.toString());
|
||||
e.getValue().write(dos);
|
||||
}
|
||||
dos.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds line breaks to enforce a maximum 72 bytes per line.
|
||||
*/
|
||||
static void make72Safe(StringBuffer line) {
|
||||
int length = line.length();
|
||||
if (length > 72) {
|
||||
int index = 70;
|
||||
while (index < length - 2) {
|
||||
line.insert(index, "\r\n ");
|
||||
index += 72;
|
||||
length += 3;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the Manifest from the specified InputStream. The entry
|
||||
* names and attributes read will be merged in with the current
|
||||
* manifest entries.
|
||||
*
|
||||
* @param is the input stream
|
||||
* @exception IOException if an I/O error has occurred
|
||||
*/
|
||||
public void read(InputStream is) throws IOException {
|
||||
// Buffered input stream for reading manifest data
|
||||
FastInputStream fis = new FastInputStream(is);
|
||||
// Line buffer
|
||||
byte[] lbuf = new byte[512];
|
||||
// Read the main attributes for the manifest
|
||||
attr.read(fis, lbuf);
|
||||
// Total number of entries, attributes read
|
||||
int ecount = 0, acount = 0;
|
||||
// Average size of entry attributes
|
||||
int asize = 2;
|
||||
// Now parse the manifest entries
|
||||
int len;
|
||||
String name = null;
|
||||
boolean skipEmptyLines = true;
|
||||
byte[] lastline = null;
|
||||
|
||||
while ((len = fis.readLine(lbuf)) != -1) {
|
||||
if (lbuf[--len] != '\n') {
|
||||
throw new IOException("manifest line too long");
|
||||
}
|
||||
if (len > 0 && lbuf[len-1] == '\r') {
|
||||
--len;
|
||||
}
|
||||
if (len == 0 && skipEmptyLines) {
|
||||
continue;
|
||||
}
|
||||
skipEmptyLines = false;
|
||||
|
||||
if (name == null) {
|
||||
name = parseName(lbuf, len);
|
||||
if (name == null) {
|
||||
throw new IOException("invalid manifest format");
|
||||
}
|
||||
if (fis.peek() == ' ') {
|
||||
// name is wrapped
|
||||
lastline = new byte[len - 6];
|
||||
System.arraycopy(lbuf, 6, lastline, 0, len - 6);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// continuation line
|
||||
byte[] buf = new byte[lastline.length + len - 1];
|
||||
System.arraycopy(lastline, 0, buf, 0, lastline.length);
|
||||
System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
|
||||
if (fis.peek() == ' ') {
|
||||
// name is wrapped
|
||||
lastline = buf;
|
||||
continue;
|
||||
}
|
||||
name = new String(buf, 0, buf.length, "UTF8");
|
||||
lastline = null;
|
||||
}
|
||||
Attributes attr = getAttributes(name);
|
||||
if (attr == null) {
|
||||
attr = new Attributes(asize);
|
||||
entries.put(name, attr);
|
||||
}
|
||||
attr.read(fis, lbuf);
|
||||
ecount++;
|
||||
acount += attr.size();
|
||||
//XXX: Fix for when the average is 0. When it is 0,
|
||||
// you get an Attributes object with an initial
|
||||
// capacity of 0, which tickles a bug in HashMap.
|
||||
asize = Math.max(2, acount / ecount);
|
||||
|
||||
name = null;
|
||||
skipEmptyLines = true;
|
||||
}
|
||||
}
|
||||
|
||||
private String parseName(byte[] lbuf, int len) {
|
||||
if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
|
||||
toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
|
||||
lbuf[4] == ':' && lbuf[5] == ' ') {
|
||||
try {
|
||||
return new String(lbuf, 6, len - 6, "UTF8");
|
||||
}
|
||||
catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int toLower(int c) {
|
||||
return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified Object is also a Manifest and has
|
||||
* the same main Attributes and entries.
|
||||
*
|
||||
* @param o the object to be compared
|
||||
* @return true if the specified Object is also a Manifest and has
|
||||
* the same main Attributes and entries
|
||||
*/
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Manifest) {
|
||||
Manifest m = (Manifest)o;
|
||||
return attr.equals(m.getMainAttributes()) &&
|
||||
entries.equals(m.getEntries());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hash code for this Manifest.
|
||||
*/
|
||||
public int hashCode() {
|
||||
return attr.hashCode() + entries.hashCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a shallow copy of this Manifest. The shallow copy is
|
||||
* implemented as follows:
|
||||
* <pre>
|
||||
* public Object clone() { return new Manifest(this); }
|
||||
* </pre>
|
||||
* @return a shallow copy of this Manifest
|
||||
*/
|
||||
public Object clone() {
|
||||
return new Manifest(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* A fast buffered input stream for parsing manifest files.
|
||||
*/
|
||||
static class FastInputStream extends FilterInputStream {
|
||||
private byte buf[];
|
||||
private int count = 0;
|
||||
private int pos = 0;
|
||||
|
||||
FastInputStream(InputStream in) {
|
||||
this(in, 8192);
|
||||
}
|
||||
|
||||
FastInputStream(InputStream in, int size) {
|
||||
super(in);
|
||||
buf = new byte[size];
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (pos >= count) {
|
||||
fill();
|
||||
if (pos >= count) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return Byte.toUnsignedInt(buf[pos++]);
|
||||
}
|
||||
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int avail = count - pos;
|
||||
if (avail <= 0) {
|
||||
if (len >= buf.length) {
|
||||
return in.read(b, off, len);
|
||||
}
|
||||
fill();
|
||||
avail = count - pos;
|
||||
if (avail <= 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (len > avail) {
|
||||
len = avail;
|
||||
}
|
||||
System.arraycopy(buf, pos, b, off, len);
|
||||
pos += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reads 'len' bytes from the input stream, or until an end-of-line
|
||||
* is reached. Returns the number of bytes read.
|
||||
*/
|
||||
public int readLine(byte[] b, int off, int len) throws IOException {
|
||||
byte[] tbuf = this.buf;
|
||||
int total = 0;
|
||||
while (total < len) {
|
||||
int avail = count - pos;
|
||||
if (avail <= 0) {
|
||||
fill();
|
||||
avail = count - pos;
|
||||
if (avail <= 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int n = len - total;
|
||||
if (n > avail) {
|
||||
n = avail;
|
||||
}
|
||||
int tpos = pos;
|
||||
int maxpos = tpos + n;
|
||||
while (tpos < maxpos && tbuf[tpos++] != '\n') ;
|
||||
n = tpos - pos;
|
||||
System.arraycopy(tbuf, pos, b, off, n);
|
||||
off += n;
|
||||
total += n;
|
||||
pos = tpos;
|
||||
if (tbuf[tpos-1] == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public byte peek() throws IOException {
|
||||
if (pos == count)
|
||||
fill();
|
||||
if (pos == count)
|
||||
return -1; // nothing left in buffer
|
||||
return buf[pos];
|
||||
}
|
||||
|
||||
public int readLine(byte[] b) throws IOException {
|
||||
return readLine(b, 0, b.length);
|
||||
}
|
||||
|
||||
public long skip(long n) throws IOException {
|
||||
if (n <= 0) {
|
||||
return 0;
|
||||
}
|
||||
long avail = count - pos;
|
||||
if (avail <= 0) {
|
||||
return in.skip(n);
|
||||
}
|
||||
if (n > avail) {
|
||||
n = avail;
|
||||
}
|
||||
pos += n;
|
||||
return n;
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return (count - pos) + in.available();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
if (in != null) {
|
||||
in.close();
|
||||
in = null;
|
||||
buf = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void fill() throws IOException {
|
||||
count = pos = 0;
|
||||
int n = in.read(buf, 0, buf.length);
|
||||
if (n > 0) {
|
||||
count = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
724
src/java.base/share/classes/java/util/jar/Pack200.java
Normal file
724
src/java.base/share/classes/java/util/jar/Pack200.java
Normal file
|
@ -0,0 +1,724 @@
|
|||
/*
|
||||
* Copyright (c) 2003, 2017, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package java.util.jar;
|
||||
|
||||
import java.util.SortedMap;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import sun.security.action.GetPropertyAction;
|
||||
|
||||
|
||||
/**
|
||||
* Transforms a JAR file to or from a packed stream in Pack200 format.
|
||||
* Please refer to <a href="{@docRoot}/../specs/pack-spec.html">Network Transfer Format JSR 200 Specification</a>
|
||||
* <p>
|
||||
* Typically the packer engine is used by application developers
|
||||
* to deploy or host JAR files on a website.
|
||||
* The unpacker engine is used by deployment applications to
|
||||
* transform the byte-stream back to JAR format.
|
||||
* <p>
|
||||
* Here is an example using packer and unpacker:
|
||||
* <pre>{@code
|
||||
* import java.util.jar.Pack200;
|
||||
* import java.util.jar.Pack200.*;
|
||||
* ...
|
||||
* // Create the Packer object
|
||||
* Packer packer = Pack200.newPacker();
|
||||
*
|
||||
* // Initialize the state by setting the desired properties
|
||||
* Map p = packer.properties();
|
||||
* // take more time choosing codings for better compression
|
||||
* p.put(Packer.EFFORT, "7"); // default is "5"
|
||||
* // use largest-possible archive segments (>10% better compression).
|
||||
* p.put(Packer.SEGMENT_LIMIT, "-1");
|
||||
* // reorder files for better compression.
|
||||
* p.put(Packer.KEEP_FILE_ORDER, Packer.FALSE);
|
||||
* // smear modification times to a single value.
|
||||
* p.put(Packer.MODIFICATION_TIME, Packer.LATEST);
|
||||
* // ignore all JAR deflation requests,
|
||||
* // transmitting a single request to use "store" mode.
|
||||
* p.put(Packer.DEFLATE_HINT, Packer.FALSE);
|
||||
* // discard debug attributes
|
||||
* p.put(Packer.CODE_ATTRIBUTE_PFX+"LineNumberTable", Packer.STRIP);
|
||||
* // throw an error if an attribute is unrecognized
|
||||
* p.put(Packer.UNKNOWN_ATTRIBUTE, Packer.ERROR);
|
||||
* // pass one class file uncompressed:
|
||||
* p.put(Packer.PASS_FILE_PFX+0, "mutants/Rogue.class");
|
||||
* try {
|
||||
* JarFile jarFile = new JarFile("/tmp/testref.jar");
|
||||
* FileOutputStream fos = new FileOutputStream("/tmp/test.pack");
|
||||
* // Call the packer
|
||||
* packer.pack(jarFile, fos);
|
||||
* jarFile.close();
|
||||
* fos.close();
|
||||
*
|
||||
* File f = new File("/tmp/test.pack");
|
||||
* FileOutputStream fostream = new FileOutputStream("/tmp/test.jar");
|
||||
* JarOutputStream jostream = new JarOutputStream(fostream);
|
||||
* Unpacker unpacker = Pack200.newUnpacker();
|
||||
* // Call the unpacker
|
||||
* unpacker.unpack(f, jostream);
|
||||
* // Must explicitly close the output.
|
||||
* jostream.close();
|
||||
* } catch (IOException ioe) {
|
||||
* ioe.printStackTrace();
|
||||
* }
|
||||
* }</pre>
|
||||
* <p>
|
||||
* A Pack200 file compressed with gzip can be hosted on HTTP/1.1 web servers.
|
||||
* The deployment applications can use "Accept-Encoding=pack200-gzip". This
|
||||
* indicates to the server that the client application desires a version of
|
||||
* the file encoded with Pack200 and further compressed with gzip. Please
|
||||
* refer to the Java Deployment Guide for techniques and details.
|
||||
* <p>
|
||||
* Unless otherwise noted, passing a {@code null} argument to a constructor or
|
||||
* method in this class will cause a {@link NullPointerException} to be thrown.
|
||||
*
|
||||
* @author John Rose
|
||||
* @author Kumar Srinivasan
|
||||
* @since 1.5
|
||||
*/
|
||||
public abstract class Pack200 {
|
||||
private Pack200() {} //prevent instantiation
|
||||
|
||||
// Static methods of the Pack200 class.
|
||||
/**
|
||||
* Obtain new instance of a class that implements Packer.
|
||||
* <ul>
|
||||
* <li><p>If the system property {@code java.util.jar.Pack200.Packer}
|
||||
* is defined, then the value is taken to be the fully-qualified name
|
||||
* of a concrete implementation class, which must implement Packer.
|
||||
* This class is loaded and instantiated. If this process fails
|
||||
* then an unspecified error is thrown.</p></li>
|
||||
*
|
||||
* <li><p>If an implementation has not been specified with the system
|
||||
* property, then the system-default implementation class is instantiated,
|
||||
* and the result is returned.</p></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note: The returned object is not guaranteed to operate
|
||||
* correctly if multiple threads use it at the same time.
|
||||
* A multi-threaded application should either allocate multiple
|
||||
* packer engines, or else serialize use of one engine with a lock.
|
||||
*
|
||||
* @return A newly allocated Packer engine.
|
||||
*/
|
||||
public static synchronized Packer newPacker() {
|
||||
return (Packer) newInstance(PACK_PROVIDER);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain new instance of a class that implements Unpacker.
|
||||
* <ul>
|
||||
* <li><p>If the system property {@code java.util.jar.Pack200.Unpacker}
|
||||
* is defined, then the value is taken to be the fully-qualified
|
||||
* name of a concrete implementation class, which must implement Unpacker.
|
||||
* The class is loaded and instantiated. If this process fails
|
||||
* then an unspecified error is thrown.</p></li>
|
||||
*
|
||||
* <li><p>If an implementation has not been specified with the
|
||||
* system property, then the system-default implementation class
|
||||
* is instantiated, and the result is returned.</p></li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Note: The returned object is not guaranteed to operate
|
||||
* correctly if multiple threads use it at the same time.
|
||||
* A multi-threaded application should either allocate multiple
|
||||
* unpacker engines, or else serialize use of one engine with a lock.
|
||||
*
|
||||
* @return A newly allocated Unpacker engine.
|
||||
*/
|
||||
|
||||
public static Unpacker newUnpacker() {
|
||||
return (Unpacker) newInstance(UNPACK_PROVIDER);
|
||||
}
|
||||
|
||||
// Interfaces
|
||||
/**
|
||||
* The packer engine applies various transformations to the input JAR file,
|
||||
* making the pack stream highly compressible by a compressor such as
|
||||
* gzip or zip. An instance of the engine can be obtained
|
||||
* using {@link #newPacker}.
|
||||
|
||||
* The high degree of compression is achieved
|
||||
* by using a number of techniques described in the JSR 200 specification.
|
||||
* Some of the techniques are sorting, re-ordering and co-location of the
|
||||
* constant pool.
|
||||
* <p>
|
||||
* The pack engine is initialized to an initial state as described
|
||||
* by their properties below.
|
||||
* The initial state can be manipulated by getting the
|
||||
* engine properties (using {@link #properties}) and storing
|
||||
* the modified properties on the map.
|
||||
* The resource files will be passed through with no changes at all.
|
||||
* The class files will not contain identical bytes, since the unpacker
|
||||
* is free to change minor class file features such as constant pool order.
|
||||
* However, the class files will be semantically identical,
|
||||
* as specified in
|
||||
* <cite>The Java™ Virtual Machine Specification</cite>.
|
||||
* <p>
|
||||
* By default, the packer does not change the order of JAR elements.
|
||||
* Also, the modification time and deflation hint of each
|
||||
* JAR element is passed unchanged.
|
||||
* (Any other ZIP-archive information, such as extra attributes
|
||||
* giving Unix file permissions, are lost.)
|
||||
* <p>
|
||||
* Note that packing and unpacking a JAR will in general alter the
|
||||
* bytewise contents of classfiles in the JAR. This means that packing
|
||||
* and unpacking will in general invalidate any digital signatures
|
||||
* which rely on bytewise images of JAR elements. In order both to sign
|
||||
* and to pack a JAR, you must first pack and unpack the JAR to
|
||||
* "normalize" it, then compute signatures on the unpacked JAR elements,
|
||||
* and finally repack the signed JAR.
|
||||
* Both packing steps should
|
||||
* use precisely the same options, and the segment limit may also
|
||||
* need to be set to "-1", to prevent accidental variation of segment
|
||||
* boundaries as class file sizes change slightly.
|
||||
* <p>
|
||||
* (Here's why this works: Any reordering the packer does
|
||||
* of any classfile structures is idempotent, so the second packing
|
||||
* does not change the orderings produced by the first packing.
|
||||
* Also, the unpacker is guaranteed by the JSR 200 specification
|
||||
* to produce a specific bytewise image for any given transmission
|
||||
* ordering of archive elements.)
|
||||
* <p>
|
||||
* In order to maintain backward compatibility, the pack file's version is
|
||||
* set to accommodate the class files present in the input JAR file. In
|
||||
* other words, the pack file version will be the latest, if the class files
|
||||
* are the latest and conversely the pack file version will be the oldest
|
||||
* if the class file versions are also the oldest. For intermediate class
|
||||
* file versions the corresponding pack file version will be used.
|
||||
* For example:
|
||||
* If the input JAR-files are solely comprised of 1.5 (or lesser)
|
||||
* class files, a 1.5 compatible pack file is produced. This will also be
|
||||
* the case for archives that have no class files.
|
||||
* If the input JAR-files contains a 1.6 class file, then the pack file
|
||||
* version will be set to 1.6.
|
||||
* <p>
|
||||
* Note: Unless otherwise noted, passing a {@code null} argument to a
|
||||
* constructor or method in this class will cause a {@link NullPointerException}
|
||||
* to be thrown.
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
public interface Packer {
|
||||
/**
|
||||
* This property is a numeral giving the estimated target size N
|
||||
* (in bytes) of each archive segment.
|
||||
* If a single input file requires more than N bytes,
|
||||
* it will be given its own archive segment.
|
||||
* <p>
|
||||
* As a special case, a value of -1 will produce a single large
|
||||
* segment with all input files, while a value of 0 will
|
||||
* produce one segment for each class.
|
||||
* Larger archive segments result in less fragmentation and
|
||||
* better compression, but processing them requires more memory.
|
||||
* <p>
|
||||
* The size of each segment is estimated by counting the size of each
|
||||
* input file to be transmitted in the segment, along with the size
|
||||
* of its name and other transmitted properties.
|
||||
* <p>
|
||||
* The default is -1, which means the packer will always create a single
|
||||
* segment output file. In cases where extremely large output files are
|
||||
* generated, users are strongly encouraged to use segmenting or break
|
||||
* up the input file into smaller JARs.
|
||||
* <p>
|
||||
* A 10Mb JAR packed without this limit will
|
||||
* typically pack about 10% smaller, but the packer may require
|
||||
* a larger Java heap (about ten times the segment limit).
|
||||
*/
|
||||
String SEGMENT_LIMIT = "pack.segment.limit";
|
||||
|
||||
/**
|
||||
* If this property is set to {@link #TRUE}, the packer will transmit
|
||||
* all elements in their original order within the source archive.
|
||||
* <p>
|
||||
* If it is set to {@link #FALSE}, the packer may reorder elements,
|
||||
* and also remove JAR directory entries, which carry no useful
|
||||
* information for Java applications.
|
||||
* (Typically this enables better compression.)
|
||||
* <p>
|
||||
* The default is {@link #TRUE}, which preserves the input information,
|
||||
* but may cause the transmitted archive to be larger than necessary.
|
||||
*/
|
||||
String KEEP_FILE_ORDER = "pack.keep.file.order";
|
||||
|
||||
|
||||
/**
|
||||
* If this property is set to a single decimal digit, the packer will
|
||||
* use the indicated amount of effort in compressing the archive.
|
||||
* Level 1 may produce somewhat larger size and faster compression speed,
|
||||
* while level 9 will take much longer but may produce better compression.
|
||||
* <p>
|
||||
* The special value 0 instructs the packer to copy through the
|
||||
* original JAR file directly, with no compression. The JSR 200
|
||||
* standard requires any unpacker to understand this special case
|
||||
* as a pass-through of the entire archive.
|
||||
* <p>
|
||||
* The default is 5, investing a modest amount of time to
|
||||
* produce reasonable compression.
|
||||
*/
|
||||
String EFFORT = "pack.effort";
|
||||
|
||||
/**
|
||||
* If this property is set to {@link #TRUE} or {@link #FALSE}, the packer
|
||||
* will set the deflation hint accordingly in the output archive, and
|
||||
* will not transmit the individual deflation hints of archive elements.
|
||||
* <p>
|
||||
* If this property is set to the special string {@link #KEEP}, the packer
|
||||
* will attempt to determine an independent deflation hint for each
|
||||
* available element of the input archive, and transmit this hint separately.
|
||||
* <p>
|
||||
* The default is {@link #KEEP}, which preserves the input information,
|
||||
* but may cause the transmitted archive to be larger than necessary.
|
||||
* <p>
|
||||
* It is up to the unpacker implementation
|
||||
* to take action upon the hint to suitably compress the elements of
|
||||
* the resulting unpacked jar.
|
||||
* <p>
|
||||
* The deflation hint of a ZIP or JAR element indicates
|
||||
* whether the element was deflated or stored directly.
|
||||
*/
|
||||
String DEFLATE_HINT = "pack.deflate.hint";
|
||||
|
||||
/**
|
||||
* If this property is set to the special string {@link #LATEST},
|
||||
* the packer will attempt to determine the latest modification time,
|
||||
* among all the available entries in the original archive or the latest
|
||||
* modification time of all the available entries in each segment.
|
||||
* This single value will be transmitted as part of the segment and applied
|
||||
* to all the entries in each segment, {@link #SEGMENT_LIMIT}.
|
||||
* <p>
|
||||
* This can marginally decrease the transmitted size of the
|
||||
* archive, at the expense of setting all installed files to a single
|
||||
* date.
|
||||
* <p>
|
||||
* If this property is set to the special string {@link #KEEP},
|
||||
* the packer transmits a separate modification time for each input
|
||||
* element.
|
||||
* <p>
|
||||
* The default is {@link #KEEP}, which preserves the input information,
|
||||
* but may cause the transmitted archive to be larger than necessary.
|
||||
* <p>
|
||||
* It is up to the unpacker implementation to take action to suitably
|
||||
* set the modification time of each element of its output file.
|
||||
* @see #SEGMENT_LIMIT
|
||||
*/
|
||||
String MODIFICATION_TIME = "pack.modification.time";
|
||||
|
||||
/**
|
||||
* Indicates that a file should be passed through bytewise, with no
|
||||
* compression. Multiple files may be specified by specifying
|
||||
* additional properties with distinct strings appended, to
|
||||
* make a family of properties with the common prefix.
|
||||
* <p>
|
||||
* There is no pathname transformation, except
|
||||
* that the system file separator is replaced by the JAR file
|
||||
* separator '/'.
|
||||
* <p>
|
||||
* The resulting file names must match exactly as strings with their
|
||||
* occurrences in the JAR file.
|
||||
* <p>
|
||||
* If a property value is a directory name, all files under that
|
||||
* directory will be passed also.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <pre>{@code
|
||||
* Map p = packer.properties();
|
||||
* p.put(PASS_FILE_PFX+0, "mutants/Rogue.class");
|
||||
* p.put(PASS_FILE_PFX+1, "mutants/Wolverine.class");
|
||||
* p.put(PASS_FILE_PFX+2, "mutants/Storm.class");
|
||||
* # Pass all files in an entire directory hierarchy:
|
||||
* p.put(PASS_FILE_PFX+3, "police/");
|
||||
* }</pre>
|
||||
*/
|
||||
String PASS_FILE_PFX = "pack.pass.file.";
|
||||
|
||||
/// Attribute control.
|
||||
|
||||
/**
|
||||
* Indicates the action to take when a class-file containing an unknown
|
||||
* attribute is encountered. Possible values are the strings {@link #ERROR},
|
||||
* {@link #STRIP}, and {@link #PASS}.
|
||||
* <p>
|
||||
* The string {@link #ERROR} means that the pack operation
|
||||
* as a whole will fail, with an exception of type {@code IOException}.
|
||||
* The string
|
||||
* {@link #STRIP} means that the attribute will be dropped.
|
||||
* The string
|
||||
* {@link #PASS} means that the whole class-file will be passed through
|
||||
* (as if it were a resource file) without compression, with a suitable warning.
|
||||
* This is the default value for this property.
|
||||
* <p>
|
||||
* Examples:
|
||||
* <pre>{@code
|
||||
* Map p = pack200.getProperties();
|
||||
* p.put(UNKNOWN_ATTRIBUTE, ERROR);
|
||||
* p.put(UNKNOWN_ATTRIBUTE, STRIP);
|
||||
* p.put(UNKNOWN_ATTRIBUTE, PASS);
|
||||
* }</pre>
|
||||
*/
|
||||
String UNKNOWN_ATTRIBUTE = "pack.unknown.attribute";
|
||||
|
||||
/**
|
||||
* When concatenated with a class attribute name,
|
||||
* indicates the format of that attribute,
|
||||
* using the layout language specified in the JSR 200 specification.
|
||||
* <p>
|
||||
* For example, the effect of this option is built in:
|
||||
* {@code pack.class.attribute.SourceFile=RUH}.
|
||||
* <p>
|
||||
* The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} are
|
||||
* also allowed, with the same meaning as {@link #UNKNOWN_ATTRIBUTE}.
|
||||
* This provides a way for users to request that specific attributes be
|
||||
* refused, stripped, or passed bitwise (with no class compression).
|
||||
* <p>
|
||||
* Code like this might be used to support attributes for JCOV:
|
||||
* <pre>{@code
|
||||
* Map p = packer.properties();
|
||||
* p.put(CODE_ATTRIBUTE_PFX+"CoverageTable", "NH[PHHII]");
|
||||
* p.put(CODE_ATTRIBUTE_PFX+"CharacterRangeTable", "NH[PHPOHIIH]");
|
||||
* p.put(CLASS_ATTRIBUTE_PFX+"SourceID", "RUH");
|
||||
* p.put(CLASS_ATTRIBUTE_PFX+"CompilationID", "RUH");
|
||||
* }</pre>
|
||||
* <p>
|
||||
* Code like this might be used to strip debugging attributes:
|
||||
* <pre>{@code
|
||||
* Map p = packer.properties();
|
||||
* p.put(CODE_ATTRIBUTE_PFX+"LineNumberTable", STRIP);
|
||||
* p.put(CODE_ATTRIBUTE_PFX+"LocalVariableTable", STRIP);
|
||||
* p.put(CLASS_ATTRIBUTE_PFX+"SourceFile", STRIP);
|
||||
* }</pre>
|
||||
*/
|
||||
String CLASS_ATTRIBUTE_PFX = "pack.class.attribute.";
|
||||
|
||||
/**
|
||||
* When concatenated with a field attribute name,
|
||||
* indicates the format of that attribute.
|
||||
* For example, the effect of this option is built in:
|
||||
* {@code pack.field.attribute.Deprecated=}.
|
||||
* The special strings {@link #ERROR}, {@link #STRIP}, and
|
||||
* {@link #PASS} are also allowed.
|
||||
* @see #CLASS_ATTRIBUTE_PFX
|
||||
*/
|
||||
String FIELD_ATTRIBUTE_PFX = "pack.field.attribute.";
|
||||
|
||||
/**
|
||||
* When concatenated with a method attribute name,
|
||||
* indicates the format of that attribute.
|
||||
* For example, the effect of this option is built in:
|
||||
* {@code pack.method.attribute.Exceptions=NH[RCH]}.
|
||||
* The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS}
|
||||
* are also allowed.
|
||||
* @see #CLASS_ATTRIBUTE_PFX
|
||||
*/
|
||||
String METHOD_ATTRIBUTE_PFX = "pack.method.attribute.";
|
||||
|
||||
/**
|
||||
* When concatenated with a code attribute name,
|
||||
* indicates the format of that attribute.
|
||||
* For example, the effect of this option is built in:
|
||||
* {@code pack.code.attribute.LocalVariableTable=NH[PHOHRUHRSHH]}.
|
||||
* The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS}
|
||||
* are also allowed.
|
||||
* @see #CLASS_ATTRIBUTE_PFX
|
||||
*/
|
||||
String CODE_ATTRIBUTE_PFX = "pack.code.attribute.";
|
||||
|
||||
/**
|
||||
* The packer's progress as a percentage, as periodically
|
||||
* updated by the packer.
|
||||
* Values of 0 - 100 are normal, and -1 indicates a stall.
|
||||
* Progress can be monitored by polling the value of this
|
||||
* property.
|
||||
* <p>
|
||||
* At a minimum, the packer must set progress to 0
|
||||
* at the beginning of a packing operation, and to 100
|
||||
* at the end.
|
||||
*/
|
||||
String PROGRESS = "pack.progress";
|
||||
|
||||
/** The string "keep", a possible value for certain properties.
|
||||
* @see #DEFLATE_HINT
|
||||
* @see #MODIFICATION_TIME
|
||||
*/
|
||||
String KEEP = "keep";
|
||||
|
||||
/** The string "pass", a possible value for certain properties.
|
||||
* @see #UNKNOWN_ATTRIBUTE
|
||||
* @see #CLASS_ATTRIBUTE_PFX
|
||||
* @see #FIELD_ATTRIBUTE_PFX
|
||||
* @see #METHOD_ATTRIBUTE_PFX
|
||||
* @see #CODE_ATTRIBUTE_PFX
|
||||
*/
|
||||
String PASS = "pass";
|
||||
|
||||
/** The string "strip", a possible value for certain properties.
|
||||
* @see #UNKNOWN_ATTRIBUTE
|
||||
* @see #CLASS_ATTRIBUTE_PFX
|
||||
* @see #FIELD_ATTRIBUTE_PFX
|
||||
* @see #METHOD_ATTRIBUTE_PFX
|
||||
* @see #CODE_ATTRIBUTE_PFX
|
||||
*/
|
||||
String STRIP = "strip";
|
||||
|
||||
/** The string "error", a possible value for certain properties.
|
||||
* @see #UNKNOWN_ATTRIBUTE
|
||||
* @see #CLASS_ATTRIBUTE_PFX
|
||||
* @see #FIELD_ATTRIBUTE_PFX
|
||||
* @see #METHOD_ATTRIBUTE_PFX
|
||||
* @see #CODE_ATTRIBUTE_PFX
|
||||
*/
|
||||
String ERROR = "error";
|
||||
|
||||
/** The string "true", a possible value for certain properties.
|
||||
* @see #KEEP_FILE_ORDER
|
||||
* @see #DEFLATE_HINT
|
||||
*/
|
||||
String TRUE = "true";
|
||||
|
||||
/** The string "false", a possible value for certain properties.
|
||||
* @see #KEEP_FILE_ORDER
|
||||
* @see #DEFLATE_HINT
|
||||
*/
|
||||
String FALSE = "false";
|
||||
|
||||
/** The string "latest", a possible value for certain properties.
|
||||
* @see #MODIFICATION_TIME
|
||||
*/
|
||||
String LATEST = "latest";
|
||||
|
||||
/**
|
||||
* Get the set of this engine's properties.
|
||||
* This set is a "live view", so that changing its
|
||||
* contents immediately affects the Packer engine, and
|
||||
* changes from the engine (such as progress indications)
|
||||
* are immediately visible in the map.
|
||||
*
|
||||
* <p>The property map may contain pre-defined implementation
|
||||
* specific and default properties. Users are encouraged to
|
||||
* read the information and fully understand the implications,
|
||||
* before modifying pre-existing properties.
|
||||
* <p>
|
||||
* Implementation specific properties are prefixed with a
|
||||
* package name associated with the implementor, beginning
|
||||
* with {@code com.} or a similar prefix.
|
||||
* All property names beginning with {@code pack.} and
|
||||
* {@code unpack.} are reserved for use by this API.
|
||||
* <p>
|
||||
* Unknown properties may be ignored or rejected with an
|
||||
* unspecified error, and invalid entries may cause an
|
||||
* unspecified error to be thrown.
|
||||
*
|
||||
* <p>
|
||||
* The returned map implements all optional {@link SortedMap} operations
|
||||
* @return A sorted association of property key strings to property
|
||||
* values.
|
||||
*/
|
||||
SortedMap<String,String> properties();
|
||||
|
||||
/**
|
||||
* Takes a JarFile and converts it into a Pack200 archive.
|
||||
* <p>
|
||||
* Closes its input but not its output. (Pack200 archives are appendable.)
|
||||
* @param in a JarFile
|
||||
* @param out an OutputStream
|
||||
* @exception IOException if an error is encountered.
|
||||
*/
|
||||
void pack(JarFile in, OutputStream out) throws IOException ;
|
||||
|
||||
/**
|
||||
* Takes a JarInputStream and converts it into a Pack200 archive.
|
||||
* <p>
|
||||
* Closes its input but not its output. (Pack200 archives are appendable.)
|
||||
* <p>
|
||||
* The modification time and deflation hint attributes are not available,
|
||||
* for the JAR manifest file and its containing directory.
|
||||
*
|
||||
* @see #MODIFICATION_TIME
|
||||
* @see #DEFLATE_HINT
|
||||
* @param in a JarInputStream
|
||||
* @param out an OutputStream
|
||||
* @exception IOException if an error is encountered.
|
||||
*/
|
||||
void pack(JarInputStream in, OutputStream out) throws IOException ;
|
||||
}
|
||||
|
||||
/**
|
||||
* The unpacker engine converts the packed stream to a JAR file.
|
||||
* An instance of the engine can be obtained
|
||||
* using {@link #newUnpacker}.
|
||||
* <p>
|
||||
* Every JAR file produced by this engine will include the string
|
||||
* "{@code PACK200}" as a zip file comment.
|
||||
* This allows a deployer to detect if a JAR archive was packed and unpacked.
|
||||
* <p>
|
||||
* Note: Unless otherwise noted, passing a {@code null} argument to a
|
||||
* constructor or method in this class will cause a {@link NullPointerException}
|
||||
* to be thrown.
|
||||
* <p>
|
||||
* This version of the unpacker is compatible with all previous versions.
|
||||
* @since 1.5
|
||||
*/
|
||||
public interface Unpacker {
|
||||
|
||||
/** The string "keep", a possible value for certain properties.
|
||||
* @see #DEFLATE_HINT
|
||||
*/
|
||||
String KEEP = "keep";
|
||||
|
||||
/** The string "true", a possible value for certain properties.
|
||||
* @see #DEFLATE_HINT
|
||||
*/
|
||||
String TRUE = "true";
|
||||
|
||||
/** The string "false", a possible value for certain properties.
|
||||
* @see #DEFLATE_HINT
|
||||
*/
|
||||
String FALSE = "false";
|
||||
|
||||
/**
|
||||
* Property indicating that the unpacker should
|
||||
* ignore all transmitted values for DEFLATE_HINT,
|
||||
* replacing them by the given value, {@link #TRUE} or {@link #FALSE}.
|
||||
* The default value is the special string {@link #KEEP},
|
||||
* which asks the unpacker to preserve all transmitted
|
||||
* deflation hints.
|
||||
*/
|
||||
String DEFLATE_HINT = "unpack.deflate.hint";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The unpacker's progress as a percentage, as periodically
|
||||
* updated by the unpacker.
|
||||
* Values of 0 - 100 are normal, and -1 indicates a stall.
|
||||
* Progress can be monitored by polling the value of this
|
||||
* property.
|
||||
* <p>
|
||||
* At a minimum, the unpacker must set progress to 0
|
||||
* at the beginning of an unpacking operation, and to 100
|
||||
* at the end.
|
||||
*/
|
||||
String PROGRESS = "unpack.progress";
|
||||
|
||||
/**
|
||||
* Get the set of this engine's properties. This set is
|
||||
* a "live view", so that changing its
|
||||
* contents immediately affects the Unpacker engine, and
|
||||
* changes from the engine (such as progress indications)
|
||||
* are immediately visible in the map.
|
||||
*
|
||||
* <p>The property map may contain pre-defined implementation
|
||||
* specific and default properties. Users are encouraged to
|
||||
* read the information and fully understand the implications,
|
||||
* before modifying pre-existing properties.
|
||||
* <p>
|
||||
* Implementation specific properties are prefixed with a
|
||||
* package name associated with the implementor, beginning
|
||||
* with {@code com.} or a similar prefix.
|
||||
* All property names beginning with {@code pack.} and
|
||||
* {@code unpack.} are reserved for use by this API.
|
||||
* <p>
|
||||
* Unknown properties may be ignored or rejected with an
|
||||
* unspecified error, and invalid entries may cause an
|
||||
* unspecified error to be thrown.
|
||||
*
|
||||
* @return A sorted association of option key strings to option values.
|
||||
*/
|
||||
SortedMap<String,String> properties();
|
||||
|
||||
/**
|
||||
* Read a Pack200 archive, and write the encoded JAR to
|
||||
* a JarOutputStream.
|
||||
* The entire contents of the input stream will be read.
|
||||
* It may be more efficient to read the Pack200 archive
|
||||
* to a file and pass the File object, using the alternate
|
||||
* method described below.
|
||||
* <p>
|
||||
* Closes its input but not its output. (The output can accumulate more elements.)
|
||||
* @param in an InputStream.
|
||||
* @param out a JarOutputStream.
|
||||
* @exception IOException if an error is encountered.
|
||||
*/
|
||||
void unpack(InputStream in, JarOutputStream out) throws IOException;
|
||||
|
||||
/**
|
||||
* Read a Pack200 archive, and write the encoded JAR to
|
||||
* a JarOutputStream.
|
||||
* <p>
|
||||
* Does not close its output. (The output can accumulate more elements.)
|
||||
* @param in a File.
|
||||
* @param out a JarOutputStream.
|
||||
* @exception IOException if an error is encountered.
|
||||
*/
|
||||
void unpack(File in, JarOutputStream out) throws IOException;
|
||||
}
|
||||
|
||||
// Private stuff....
|
||||
|
||||
private static final String PACK_PROVIDER = "java.util.jar.Pack200.Packer";
|
||||
private static final String UNPACK_PROVIDER = "java.util.jar.Pack200.Unpacker";
|
||||
|
||||
private static Class<?> packerImpl;
|
||||
private static Class<?> unpackerImpl;
|
||||
|
||||
private static synchronized Object newInstance(String prop) {
|
||||
String implName = "(unknown)";
|
||||
try {
|
||||
Class<?> impl = (PACK_PROVIDER.equals(prop))? packerImpl: unpackerImpl;
|
||||
if (impl == null) {
|
||||
// The first time, we must decide which class to use.
|
||||
implName = GetPropertyAction.privilegedGetProperty(prop,"");
|
||||
if (implName != null && !implName.equals(""))
|
||||
impl = Class.forName(implName);
|
||||
else if (PACK_PROVIDER.equals(prop))
|
||||
impl = com.sun.java.util.jar.pack.PackerImpl.class;
|
||||
else
|
||||
impl = com.sun.java.util.jar.pack.UnpackerImpl.class;
|
||||
}
|
||||
// We have a class. Now instantiate it.
|
||||
@SuppressWarnings("deprecation")
|
||||
Object result = impl.newInstance();
|
||||
return result;
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new Error("Class not found: " + implName +
|
||||
":\ncheck property " + prop +
|
||||
" in your properties file.", e);
|
||||
} catch (InstantiationException e) {
|
||||
throw new Error("Could not instantiate: " + implName +
|
||||
":\ncheck property " + prop +
|
||||
" in your properties file.", e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new Error("Cannot access class: " + implName +
|
||||
":\ncheck property " + prop +
|
||||
" in your properties file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
49
src/java.base/share/classes/java/util/jar/package-info.java
Normal file
49
src/java.base/share/classes/java/util/jar/package-info.java
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright (c) 1998, 2017, 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides classes for reading and writing the JAR (Java ARchive)
|
||||
* file format, which is based on the standard ZIP file format with an
|
||||
* optional manifest file. The manifest stores meta-information about
|
||||
* the JAR file contents and is also used for signing JAR files.
|
||||
*
|
||||
* <h2>Package Specification</h2>
|
||||
*
|
||||
* The <code>java.util.jar</code> package is based on the following
|
||||
* specifications:
|
||||
*
|
||||
* <ul>
|
||||
* <li><b>Info-ZIP file format</b> - The JAR format is based on the Info-ZIP
|
||||
* file format. See
|
||||
* <a href="../zip/package-summary.html#package.description">java.util.zip
|
||||
* package description.</a> <p>
|
||||
* In JAR files, all file names must be encoded in the UTF-8 encoding.
|
||||
* <li><a href="{@docRoot}/../specs/jar/jar.html">
|
||||
* Manifest and Signature Specification</a> - The manifest format specification.
|
||||
* </ul>
|
||||
*
|
||||
* @since 1.2
|
||||
*/
|
||||
package java.util.jar;
|
Loading…
Add table
Add a link
Reference in a new issue