/*
* Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
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="../../../../technotes/guides/jar/jar.html">
* Manifest format specification</a>.
*
* @author David Connelly
* @see Attributes
* @since 1.2
*/
public class
Manifest implements
Cloneable {
// manifest main attributes
private final
Attributes attr = new
Attributes();
// manifest entries
private final
Map<
String,
Attributes>
entries = new
HashMap<>();
// associated JarVerifier, not null when called by JarFile::getManifest.
private final
JarVerifier jv;
/**
* Constructs a new, empty Manifest.
*/
public
Manifest() {
jv = null;
}
/**
* 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 {
this(null,
is);
}
/**
* Constructs a new Manifest from the specified input stream
* and associates it with a JarVerifier.
*/
Manifest(
JarVerifier jv,
InputStream is) throws
IOException {
read(
is);
this.
jv =
jv;
}
/**
* 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());
jv =
man.
jv;
}
/**
* 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);
}
/**
* Returns the Attributes for the specified entry name, if trusted.
*
* @param name entry name
* @return returns the same result as {@link #getAttributes(String)}
* @throws SecurityException if the associated jar is signed but this entry
* has been modified after signing (i.e. the section in the manifest
* does not exist in SF files of all signers).
*/
Attributes getTrustedAttributes(
String name) {
// Note: Before the verification of MANIFEST.MF/.SF/.RSA files is done,
// jv.isTrustedManifestEntry() isn't able to detect MANIFEST.MF change.
// Users of this method should call SharedSecrets.javaUtilJarAccess()
// .ensureInitialization() first.
Attributes result =
getAttributes(
name);
if (
result != null &&
jv != null && !
jv.
isTrustedManifestEntry(
name)) {
throw new
SecurityException("Untrusted manifest entry: " +
name);
}
return
result;
}
/**
* 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
*/
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
Iterator<
Map.
Entry<
String,
Attributes>>
it =
entries.
entrySet().
iterator();
while (
it.
hasNext()) {
Map.
Entry<
String,
Attributes>
e =
it.
next();
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;
}
}
}
}