001: /*
002: * Copyright 1996-1999 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.tools.jar;
027:
028: import java.io.*;
029: import java.util.*;
030: import java.security.*;
031:
032: import sun.net.www.MessageHeader;
033: import sun.misc.BASE64Encoder;
034: import sun.misc.BASE64Decoder;
035:
036: /**
037: * This is OBSOLETE. DO NOT USE THIS. Use java.util.jar.Manifest
038: * instead. It has to stay here because some apps (namely HJ and HJV)
039: * call directly into it.
040: *
041: * @version 1.46, 05/05/07
042: * @author David Brown
043: * @author Benjamin Renaud
044: */
045:
046: public class Manifest {
047:
048: /* list of headers that all pertain to a particular
049: * file in the archive
050: */
051: private Vector entries = new Vector();
052: private byte[] tmpbuf = new byte[512];
053: /* a hashtable of entries, for fast lookup */
054: private Hashtable tableEntries = new Hashtable();
055:
056: static final String[] hashes = { "SHA" };
057: static final byte[] EOL = { (byte) '\r', (byte) '\n' };
058:
059: static final boolean debug = false;
060: static final String VERSION = "1.0";
061:
062: static final void debug(String s) {
063: if (debug)
064: System.out.println("man> " + s);
065: }
066:
067: public Manifest() {
068: }
069:
070: public Manifest(byte[] bytes) throws IOException {
071: this (new ByteArrayInputStream(bytes), false);
072: }
073:
074: public Manifest(InputStream is) throws IOException {
075: this (is, true);
076: }
077:
078: /**
079: * Parse a manifest from a stream, optionally computing hashes
080: * for the files.
081: */
082: public Manifest(InputStream is, boolean compute) throws IOException {
083: if (!is.markSupported()) {
084: is = new BufferedInputStream(is);
085: }
086: /* do not rely on available() here! */
087: while (true) {
088: is.mark(1);
089: if (is.read() == -1) { // EOF
090: break;
091: }
092: is.reset();
093: MessageHeader m = new MessageHeader(is);
094: if (compute) {
095: doHashes(m);
096: }
097: addEntry(m);
098: }
099: }
100:
101: /* recursively generate manifests from directory tree */
102: public Manifest(String[] files) throws IOException {
103: MessageHeader globals = new MessageHeader();
104: globals.add("Manifest-Version", VERSION);
105: String jdkVersion = System.getProperty("java.version");
106: globals.add("Created-By", "Manifest JDK " + jdkVersion);
107: addEntry(globals);
108: addFiles(null, files);
109: }
110:
111: public void addEntry(MessageHeader entry) {
112: entries.addElement(entry);
113: String name = entry.findValue("Name");
114: debug("addEntry for name: " + name);
115: if (name != null) {
116: tableEntries.put(name, entry);
117: }
118: }
119:
120: public MessageHeader getEntry(String name) {
121: return (MessageHeader) tableEntries.get(name);
122: }
123:
124: public MessageHeader entryAt(int i) {
125: return (MessageHeader) entries.elementAt(i);
126: }
127:
128: public Enumeration entries() {
129: return entries.elements();
130: }
131:
132: public void addFiles(File dir, String[] files) throws IOException {
133: if (files == null)
134: return;
135: for (int i = 0; i < files.length; i++) {
136: File file;
137: if (dir == null) {
138: file = new File(files[i]);
139: } else {
140: file = new File(dir, files[i]);
141: }
142: if (file.isDirectory()) {
143: addFiles(file, file.list());
144: } else {
145: addFile(file);
146: }
147: }
148: }
149:
150: /**
151: * File names are represented internally using "/";
152: * they are converted to the local format for anything else
153: */
154:
155: private final String stdToLocal(String name) {
156: return name.replace('/', java.io.File.separatorChar);
157: }
158:
159: private final String localToStd(String name) {
160: name = name.replace(java.io.File.separatorChar, '/');
161: if (name.startsWith("./"))
162: name = name.substring(2);
163: else if (name.startsWith("/"))
164: name = name.substring(1);
165: return name;
166: }
167:
168: public void addFile(File f) throws IOException {
169: String stdName = localToStd(f.getPath());
170: if (tableEntries.get(stdName) == null) {
171: MessageHeader mh = new MessageHeader();
172: mh.add("Name", stdName);
173: addEntry(mh);
174: }
175: }
176:
177: public void doHashes(MessageHeader mh) throws IOException {
178: // If unnamed or is a directory return immediately
179: String name = mh.findValue("Name");
180: if (name == null || name.endsWith("/")) {
181: return;
182: }
183:
184: BASE64Encoder enc = new BASE64Encoder();
185:
186: /* compute hashes, write over any other "Hash-Algorithms" (?) */
187: for (int j = 0; j < hashes.length; ++j) {
188: InputStream is = new FileInputStream(stdToLocal(name));
189: try {
190: MessageDigest dig = MessageDigest
191: .getInstance(hashes[j]);
192:
193: int len;
194: while ((len = is.read(tmpbuf, 0, tmpbuf.length)) != -1) {
195: dig.update(tmpbuf, 0, len);
196: }
197: mh.set(hashes[j] + "-Digest", enc.encode(dig.digest()));
198: } catch (NoSuchAlgorithmException e) {
199: throw new JarException("Digest algorithm " + hashes[j]
200: + " not available.");
201: } finally {
202: is.close();
203: }
204: }
205: }
206:
207: /* Add a manifest file at current position in a stream
208: */
209: public void stream(OutputStream os) throws IOException {
210:
211: PrintStream ps;
212: if (os instanceof PrintStream) {
213: ps = (PrintStream) os;
214: } else {
215: ps = new PrintStream(os);
216: }
217:
218: /* the first header in the file should be the global one.
219: * It should say "Manifest-Version: x.x"; if not add it
220: */
221: MessageHeader globals = (MessageHeader) entries.elementAt(0);
222:
223: if (globals.findValue("Manifest-Version") == null) {
224: /* Assume this is a user-defined manifest. If it has a Name: <..>
225: * field, then it is not global, in which case we just add our own
226: * global Manifest-version: <version>
227: * If the first MessageHeader has no Name: <..>, we assume it
228: * is a global header and so prepend Manifest to it.
229: */
230: String jdkVersion = System.getProperty("java.version");
231:
232: if (globals.findValue("Name") == null) {
233: globals.prepend("Manifest-Version", VERSION);
234: globals.add("Created-By", "Manifest JDK " + jdkVersion);
235: } else {
236: ps.print("Manifest-Version: " + VERSION + "\r\n"
237: + "Created-By: " + jdkVersion + "\r\n\r\n");
238: }
239: ps.flush();
240: }
241:
242: globals.print(ps);
243:
244: for (int i = 1; i < entries.size(); ++i) {
245: MessageHeader mh = (MessageHeader) entries.elementAt(i);
246: mh.print(ps);
247: }
248: }
249:
250: public static boolean isManifestName(String name) {
251:
252: // remove leading /
253: if (name.charAt(0) == '/') {
254: name = name.substring(1, name.length());
255: }
256: // case insensitive
257: name = name.toUpperCase();
258:
259: if (name.equals("META-INF/MANIFEST.MF")) {
260: return true;
261: }
262: return false;
263: }
264: }
|