8187443: Forest Consolidation: Move files to unified layout

Reviewed-by: darcy, ihse
This commit is contained in:
Erik Joelsson 2017-09-12 19:03:39 +02:00
parent 270fe13182
commit 3789983e89
56923 changed files with 3 additions and 15727 deletions

View 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");
}
}

View 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();
}
}

View 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);
}
}

File diff suppressed because it is too large Load diff

View 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;
}
}

View 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);
}
}

View 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);
}
}

View file

@ -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);
}
}

View 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;
}
}
}
}

View 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&trade; 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);
}
}
}

View 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;