0N/A/*
2362N/A * Copyright (c) 1996, 1999, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/A
0N/Apackage sun.tools.jar;
0N/A
0N/Aimport java.io.*;
0N/Aimport java.util.*;
0N/Aimport java.security.*;
0N/A
0N/Aimport sun.net.www.MessageHeader;
0N/Aimport sun.misc.BASE64Encoder;
0N/Aimport sun.misc.BASE64Decoder;
0N/A
0N/A/**
0N/A * This is OBSOLETE. DO NOT USE THIS. Use java.util.jar.Manifest
0N/A * instead. It has to stay here because some apps (namely HJ and HJV)
0N/A * call directly into it.
0N/A *
0N/A * @author David Brown
0N/A * @author Benjamin Renaud
0N/A */
0N/A
0N/Apublic class Manifest {
0N/A
0N/A /* list of headers that all pertain to a particular
0N/A * file in the archive
0N/A */
0N/A private Vector entries = new Vector();
0N/A private byte[] tmpbuf = new byte[512];
0N/A /* a hashtable of entries, for fast lookup */
0N/A private Hashtable tableEntries = new Hashtable();
0N/A
0N/A static final String[] hashes = {"SHA"};
0N/A static final byte[] EOL = {(byte)'\r', (byte)'\n'};
0N/A
0N/A static final boolean debug = false;
0N/A static final String VERSION = "1.0";
0N/A static final void debug(String s) {
0N/A if (debug)
0N/A System.out.println("man> " + s);
0N/A }
0N/A
0N/A public Manifest() {}
0N/A
0N/A public Manifest(byte[] bytes) throws IOException {
0N/A this(new ByteArrayInputStream(bytes), false);
0N/A }
0N/A
0N/A public Manifest(InputStream is) throws IOException {
0N/A this(is, true);
0N/A }
0N/A
0N/A /**
0N/A * Parse a manifest from a stream, optionally computing hashes
0N/A * for the files.
0N/A */
0N/A public Manifest(InputStream is, boolean compute) throws IOException {
0N/A if (!is.markSupported()) {
0N/A is = new BufferedInputStream(is);
0N/A }
0N/A /* do not rely on available() here! */
0N/A while (true) {
0N/A is.mark(1);
0N/A if (is.read() == -1) { // EOF
0N/A break;
0N/A }
0N/A is.reset();
0N/A MessageHeader m = new MessageHeader(is);
0N/A if (compute) {
0N/A doHashes(m);
0N/A }
0N/A addEntry(m);
0N/A }
0N/A }
0N/A
0N/A /* recursively generate manifests from directory tree */
0N/A public Manifest(String[] files) throws IOException {
0N/A MessageHeader globals = new MessageHeader();
0N/A globals.add("Manifest-Version", VERSION);
0N/A String jdkVersion = System.getProperty("java.version");
0N/A globals.add("Created-By", "Manifest JDK "+jdkVersion);
0N/A addEntry(globals);
0N/A addFiles(null, files);
0N/A }
0N/A
0N/A public void addEntry(MessageHeader entry) {
0N/A entries.addElement(entry);
0N/A String name = entry.findValue("Name");
0N/A debug("addEntry for name: "+name);
0N/A if (name != null) {
0N/A tableEntries.put(name, entry);
0N/A }
0N/A }
0N/A
0N/A public MessageHeader getEntry(String name) {
0N/A return (MessageHeader) tableEntries.get(name);
0N/A }
0N/A
0N/A public MessageHeader entryAt(int i) {
0N/A return (MessageHeader) entries.elementAt(i);
0N/A }
0N/A
0N/A public Enumeration entries() {
0N/A return entries.elements();
0N/A }
0N/A
0N/A public void addFiles(File dir, String[] files) throws IOException {
0N/A if (files == null)
0N/A return;
0N/A for (int i = 0; i < files.length; i++) {
0N/A File file;
0N/A if (dir == null) {
0N/A file = new File(files[i]);
0N/A } else {
0N/A file = new File(dir, files[i]);
0N/A }
0N/A if (file.isDirectory()) {
0N/A addFiles(file, file.list());
0N/A } else {
0N/A addFile(file);
0N/A }
0N/A }
0N/A }
0N/A
0N/A /**
0N/A * File names are represented internally using "/";
0N/A * they are converted to the local format for anything else
0N/A */
0N/A
0N/A private final String stdToLocal(String name) {
0N/A return name.replace('/', java.io.File.separatorChar);
0N/A }
0N/A
0N/A private final String localToStd(String name) {
0N/A name = name.replace(java.io.File.separatorChar, '/');
0N/A if (name.startsWith("./"))
0N/A name = name.substring(2);
0N/A else if (name.startsWith("/"))
0N/A name = name.substring(1);
0N/A return name;
0N/A }
0N/A
0N/A public void addFile(File f) throws IOException {
0N/A String stdName = localToStd(f.getPath());
0N/A if (tableEntries.get(stdName) == null) {
0N/A MessageHeader mh = new MessageHeader();
0N/A mh.add("Name", stdName);
0N/A addEntry(mh);
0N/A }
0N/A }
0N/A
0N/A public void doHashes(MessageHeader mh) throws IOException {
0N/A // If unnamed or is a directory return immediately
0N/A String name = mh.findValue("Name");
0N/A if (name == null || name.endsWith("/")) {
0N/A return;
0N/A }
0N/A
0N/A BASE64Encoder enc = new BASE64Encoder();
0N/A
0N/A /* compute hashes, write over any other "Hash-Algorithms" (?) */
0N/A for (int j = 0; j < hashes.length; ++j) {
0N/A InputStream is = new FileInputStream(stdToLocal(name));
0N/A try {
0N/A MessageDigest dig = MessageDigest.getInstance(hashes[j]);
0N/A
0N/A int len;
0N/A while ((len = is.read(tmpbuf, 0, tmpbuf.length)) != -1) {
0N/A dig.update(tmpbuf, 0, len);
0N/A }
0N/A mh.set(hashes[j] + "-Digest", enc.encode(dig.digest()));
0N/A } catch (NoSuchAlgorithmException e) {
0N/A throw new JarException("Digest algorithm " + hashes[j] +
0N/A " not available.");
0N/A } finally {
0N/A is.close();
0N/A }
0N/A }
0N/A }
0N/A
0N/A /* Add a manifest file at current position in a stream
0N/A */
0N/A public void stream(OutputStream os) throws IOException {
0N/A
0N/A PrintStream ps;
0N/A if (os instanceof PrintStream) {
0N/A ps = (PrintStream) os;
0N/A } else {
0N/A ps = new PrintStream(os);
0N/A }
0N/A
0N/A /* the first header in the file should be the global one.
0N/A * It should say "Manifest-Version: x.x"; if not add it
0N/A */
0N/A MessageHeader globals = (MessageHeader) entries.elementAt(0);
0N/A
0N/A if (globals.findValue("Manifest-Version") == null) {
0N/A /* Assume this is a user-defined manifest. If it has a Name: <..>
0N/A * field, then it is not global, in which case we just add our own
0N/A * global Manifest-version: <version>
0N/A * If the first MessageHeader has no Name: <..>, we assume it
0N/A * is a global header and so prepend Manifest to it.
0N/A */
0N/A String jdkVersion = System.getProperty("java.version");
0N/A
0N/A if (globals.findValue("Name") == null) {
0N/A globals.prepend("Manifest-Version", VERSION);
0N/A globals.add("Created-By", "Manifest JDK "+jdkVersion);
0N/A } else {
0N/A ps.print("Manifest-Version: "+VERSION+"\r\n"+
0N/A "Created-By: "+jdkVersion+"\r\n\r\n");
0N/A }
0N/A ps.flush();
0N/A }
0N/A
0N/A globals.print(ps);
0N/A
0N/A for (int i = 1; i < entries.size(); ++i) {
0N/A MessageHeader mh = (MessageHeader) entries.elementAt(i);
0N/A mh.print(ps);
0N/A }
0N/A }
0N/A
0N/A public static boolean isManifestName(String name) {
0N/A
0N/A // remove leading /
0N/A if (name.charAt(0) == '/') {
0N/A name = name.substring(1, name.length());
0N/A }
0N/A // case insensitive
0N/A name = name.toUpperCase();
0N/A
0N/A if (name.equals("META-INF/MANIFEST.MF")) {
0N/A return true;
0N/A }
0N/A return false;
0N/A }
0N/A}