/*
* Copyright (c) 1996, 1999, 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 sun.tools.jar;
import java.io.*;
import java.util.*;
import java.security.*;
import sun.net.www.MessageHeader;
import sun.misc.BASE64Encoder;
import sun.misc.BASE64Decoder;
This is OBSOLETE. DO NOT USE THIS. Use java.util.jar.Manifest
instead. It has to stay here because some apps (namely HJ and HJV)
call directly into it.
Author: David Brown, Benjamin Renaud
/**
* This is OBSOLETE. DO NOT USE THIS. Use java.util.jar.Manifest
* instead. It has to stay here because some apps (namely HJ and HJV)
* call directly into it.
*
* @author David Brown
* @author Benjamin Renaud
*/
public class Manifest {
/* list of headers that all pertain to a particular
* file in the archive
*/
private Vector<MessageHeader> entries = new Vector<>();
private byte[] tmpbuf = new byte[512];
/* a hashtable of entries, for fast lookup */
private Hashtable<String, MessageHeader> tableEntries = new Hashtable<>();
static final String[] hashes = {"SHA"};
static final byte[] EOL = {(byte)'\r', (byte)'\n'};
static final boolean debug = false;
static final String VERSION = "1.0";
static final void debug(String s) {
if (debug)
System.out.println("man> " + s);
}
public Manifest() {}
public Manifest(byte[] bytes) throws IOException {
this(new ByteArrayInputStream(bytes), false);
}
public Manifest(InputStream is) throws IOException {
this(is, true);
}
Parse a manifest from a stream, optionally computing hashes
for the files.
/**
* Parse a manifest from a stream, optionally computing hashes
* for the files.
*/
public Manifest(InputStream is, boolean compute) throws IOException {
if (!is.markSupported()) {
is = new BufferedInputStream(is);
}
/* do not rely on available() here! */
while (true) {
is.mark(1);
if (is.read() == -1) { // EOF
break;
}
is.reset();
MessageHeader m = new MessageHeader(is);
if (compute) {
doHashes(m);
}
addEntry(m);
}
}
/* recursively generate manifests from directory tree */
public Manifest(String[] files) throws IOException {
MessageHeader globals = new MessageHeader();
globals.add("Manifest-Version", VERSION);
String jdkVersion = System.getProperty("java.version");
globals.add("Created-By", "Manifest JDK "+jdkVersion);
addEntry(globals);
addFiles(null, files);
}
public void addEntry(MessageHeader entry) {
entries.addElement(entry);
String name = entry.findValue("Name");
debug("addEntry for name: "+name);
if (name != null) {
tableEntries.put(name, entry);
}
}
public MessageHeader getEntry(String name) {
return tableEntries.get(name);
}
public MessageHeader entryAt(int i) {
return entries.elementAt(i);
}
public Enumeration<MessageHeader> entries() {
return entries.elements();
}
public void addFiles(File dir, String[] files) throws IOException {
if (files == null)
return;
for (int i = 0; i < files.length; i++) {
File file;
if (dir == null) {
file = new File(files[i]);
} else {
file = new File(dir, files[i]);
}
if (file.isDirectory()) {
addFiles(file, file.list());
} else {
addFile(file);
}
}
}
File names are represented internally using "/";
they are converted to the local format for anything else
/**
* File names are represented internally using "/";
* they are converted to the local format for anything else
*/
private final String stdToLocal(String name) {
return name.replace('/', java.io.File.separatorChar);
}
private final String localToStd(String name) {
name = name.replace(java.io.File.separatorChar, '/');
if (name.startsWith("./"))
name = name.substring(2);
else if (name.startsWith("/"))
name = name.substring(1);
return name;
}
public void addFile(File f) throws IOException {
String stdName = localToStd(f.getPath());
if (tableEntries.get(stdName) == null) {
MessageHeader mh = new MessageHeader();
mh.add("Name", stdName);
addEntry(mh);
}
}
public void doHashes(MessageHeader mh) throws IOException {
// If unnamed or is a directory return immediately
String name = mh.findValue("Name");
if (name == null || name.endsWith("/")) {
return;
}
BASE64Encoder enc = new BASE64Encoder();
/* compute hashes, write over any other "Hash-Algorithms" (?) */
for (int j = 0; j < hashes.length; ++j) {
InputStream is = new FileInputStream(stdToLocal(name));
try {
MessageDigest dig = MessageDigest.getInstance(hashes[j]);
int len;
while ((len = is.read(tmpbuf, 0, tmpbuf.length)) != -1) {
dig.update(tmpbuf, 0, len);
}
mh.set(hashes[j] + "-Digest", enc.encode(dig.digest()));
} catch (NoSuchAlgorithmException e) {
throw new JarException("Digest algorithm " + hashes[j] +
" not available.");
} finally {
is.close();
}
}
}
/* Add a manifest file at current position in a stream
*/
public void stream(OutputStream os) throws IOException {
PrintStream ps;
if (os instanceof PrintStream) {
ps = (PrintStream) os;
} else {
ps = new PrintStream(os);
}
/* the first header in the file should be the global one.
* It should say "Manifest-Version: x.x"; if not add it
*/
MessageHeader globals = entries.elementAt(0);
if (globals.findValue("Manifest-Version") == null) {
/* Assume this is a user-defined manifest. If it has a Name: <..>
* field, then it is not global, in which case we just add our own
* global Manifest-version: <version>
* If the first MessageHeader has no Name: <..>, we assume it
* is a global header and so prepend Manifest to it.
*/
String jdkVersion = System.getProperty("java.version");
if (globals.findValue("Name") == null) {
globals.prepend("Manifest-Version", VERSION);
globals.add("Created-By", "Manifest JDK "+jdkVersion);
} else {
ps.print("Manifest-Version: "+VERSION+"\r\n"+
"Created-By: "+jdkVersion+"\r\n\r\n");
}
ps.flush();
}
globals.print(ps);
for (int i = 1; i < entries.size(); ++i) {
MessageHeader mh = entries.elementAt(i);
mh.print(ps);
}
}
public static boolean isManifestName(String name) {
// remove leading /
if (name.charAt(0) == '/') {
name = name.substring(1, name.length());
}
// case insensitive
name = name.toUpperCase();
if (name.equals("META-INF/MANIFEST.MF")) {
return true;
}
return false;
}
}