1 /*
2  * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */

25
26 package java.util.jar;
27
28 import java.io.FilterInputStream;
29 import java.io.DataOutputStream;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.IOException;
33 import java.util.Map;
34 import java.util.HashMap;
35
36 /**
37  * The Manifest class is used to maintain Manifest entry names and their
38  * associated Attributes. There are main Manifest Attributes as well as
39  * per-entry Attributes. For information on the Manifest format, please
40  * see the
41  * <a href="{@docRoot}/../specs/jar/jar.html">
42  * Manifest format specification</a>.
43  *
44  * @author  David Connelly
45  * @see     Attributes
46  * @since   1.2
47  */

48 public class Manifest implements Cloneable {
49     // manifest main attributes
50     private final Attributes attr = new Attributes();
51
52     // manifest entries
53     private final Map<String, Attributes> entries = new HashMap<>();
54
55     // associated JarVerifier, not null when called by JarFile::getManifest.
56     private final JarVerifier jv;
57
58     /**
59      * Constructs a new, empty Manifest.
60      */

61     public Manifest() {
62         jv = null;
63     }
64
65     /**
66      * Constructs a new Manifest from the specified input stream.
67      *
68      * @param is the input stream containing manifest data
69      * @throws IOException if an I/O error has occurred
70      */

71     public Manifest(InputStream is) throws IOException {
72         this(null, is);
73     }
74
75     /**
76      * Constructs a new Manifest from the specified input stream
77      * and associates it with a JarVerifier.
78      */

79     Manifest(JarVerifier jv, InputStream is) throws IOException {
80         read(is);
81         this.jv = jv;
82     }
83
84     /**
85      * Constructs a new Manifest that is a copy of the specified Manifest.
86      *
87      * @param man the Manifest to copy
88      */

89     public Manifest(Manifest man) {
90         attr.putAll(man.getMainAttributes());
91         entries.putAll(man.getEntries());
92         jv = man.jv;
93     }
94
95     /**
96      * Returns the main Attributes for the Manifest.
97      * @return the main Attributes for the Manifest
98      */

99     public Attributes getMainAttributes() {
100         return attr;
101     }
102
103     /**
104      * Returns a Map of the entries contained in this Manifest. Each entry
105      * is represented by a String name (key) and associated Attributes (value).
106      * The Map permits the {@code null} key, but no entry with a null key is
107      * created by {@link #read}, nor is such an entry written by using {@link
108      * #write}.
109      *
110      * @return a Map of the entries contained in this Manifest
111      */

112     public Map<String,Attributes> getEntries() {
113         return entries;
114     }
115
116     /**
117      * Returns the Attributes for the specified entry name.
118      * This method is defined as:
119      * <pre>
120      *      return (Attributes)getEntries().get(name)
121      * </pre>
122      * Though {@code null} is a valid {@code name}, when
123      * {@code getAttributes(null)} is invoked on a {@code Manifest}
124      * obtained from a jar file, {@code null} will be returned.  While jar
125      * files themselves do not allow {@code null}-named attributes, it is
126      * possible to invoke {@link #getEntries} on a {@code Manifest}, and
127      * on that result, invoke {@code put} with a null key and an
128      * arbitrary value.  Subsequent invocations of
129      * {@code getAttributes(null)} will return the just-{@code put}
130      * value.
131      * <p>
132      * Note that this method does not return the manifest's main attributes;
133      * see {@link #getMainAttributes}.
134      *
135      * @param name entry name
136      * @return the Attributes for the specified entry name
137      */

138     public Attributes getAttributes(String name) {
139         return getEntries().get(name);
140     }
141
142     /**
143      * Returns the Attributes for the specified entry name, if trusted.
144      *
145      * @param name entry name
146      * @return returns the same result as {@link #getAttributes(String)}
147      * @throws SecurityException if the associated jar is signed but this entry
148      *      has been modified after signing (i.e. the section in the manifest
149      *      does not exist in SF files of all signers).
150      */

151     Attributes getTrustedAttributes(String name) {
152         // Note: Before the verification of MANIFEST.MF/.SF/.RSA files is done,
153         // jv.isTrustedManifestEntry() isn't able to detect MANIFEST.MF change.
154         // Users of this method should call SharedSecrets.javaUtilJarAccess()
155         // .ensureInitialization() first.
156         Attributes result = getAttributes(name);
157         if (result != null && jv != null && ! jv.isTrustedManifestEntry(name)) {
158             throw new SecurityException("Untrusted manifest entry: " + name);
159         }
160         return result;
161     }
162
163     /**
164      * Clears the main Attributes as well as the entries in this Manifest.
165      */

166     public void clear() {
167         attr.clear();
168         entries.clear();
169     }
170
171     /**
172      * Writes the Manifest to the specified OutputStream.
173      * Attributes.Name.MANIFEST_VERSION must be set in
174      * MainAttributes prior to invoking this method.
175      *
176      * @param out the output stream
177      * @exception IOException if an I/O error has occurred
178      * @see #getMainAttributes
179      */

180     @SuppressWarnings("deprecation")
181     public void write(OutputStream out) throws IOException {
182         DataOutputStream dos = new DataOutputStream(out);
183         // Write out the main attributes for the manifest
184         attr.writeMain(dos);
185         // Now write out the per-entry attributes
186         for (Map.Entry<String, Attributes> e : entries.entrySet()) {
187             StringBuffer buffer = new StringBuffer("Name: ");
188             String value = e.getKey();
189             if (value != null) {
190                 byte[] vb = value.getBytes("UTF8");
191                 value = new String(vb, 0, 0, vb.length);
192             }
193             buffer.append(value);
194             make72Safe(buffer);
195             buffer.append("\r\n");
196             dos.writeBytes(buffer.toString());
197             e.getValue().write(dos);
198         }
199         dos.flush();
200     }
201
202     /**
203      * Adds line breaks to enforce a maximum 72 bytes per line.
204      */

205     static void make72Safe(StringBuffer line) {
206         int length = line.length();
207         int index = 72;
208         while (index < length) {
209             line.insert(index, "\r\n ");
210             index += 74; // + line width + line break ("\r\n")
211             length += 3; // + line break ("\r\n") and space
212         }
213         return;
214     }
215
216     /**
217      * Reads the Manifest from the specified InputStream. The entry
218      * names and attributes read will be merged in with the current
219      * manifest entries.
220      *
221      * @param is the input stream
222      * @exception IOException if an I/O error has occurred
223      */

224     public void read(InputStream is) throws IOException {
225         // Buffered input stream for reading manifest data
226         FastInputStream fis = new FastInputStream(is);
227         // Line buffer
228         byte[] lbuf = new byte[512];
229         // Read the main attributes for the manifest
230         attr.read(fis, lbuf);
231         // Total number of entries, attributes read
232         int ecount = 0, acount = 0;
233         // Average size of entry attributes
234         int asize = 2;
235         // Now parse the manifest entries
236         int len;
237         String name = null;
238         boolean skipEmptyLines = true;
239         byte[] lastline = null;
240
241         while ((len = fis.readLine(lbuf)) != -1) {
242             byte c = lbuf[--len];
243             if (c != '\n' && c != '\r') {
244                 throw new IOException("manifest line too long");
245             }
246             if (len > 0 && lbuf[len-1] == '\r') {
247                 --len;
248             }
249             if (len == 0 && skipEmptyLines) {
250                 continue;
251             }
252             skipEmptyLines = false;
253
254             if (name == null) {
255                 name = parseName(lbuf, len);
256                 if (name == null) {
257                     throw new IOException("invalid manifest format");
258                 }
259                 if (fis.peek() == ' ') {
260                     // name is wrapped
261                     lastline = new byte[len - 6];
262                     System.arraycopy(lbuf, 6, lastline, 0, len - 6);
263                     continue;
264                 }
265             } else {
266                 // continuation line
267                 byte[] buf = new byte[lastline.length + len - 1];
268                 System.arraycopy(lastline, 0, buf, 0, lastline.length);
269                 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
270                 if (fis.peek() == ' ') {
271                     // name is wrapped
272                     lastline = buf;
273                     continue;
274                 }
275                 name = new String(buf, 0, buf.length, "UTF8");
276                 lastline = null;
277             }
278             Attributes attr = getAttributes(name);
279             if (attr == null) {
280                 attr = new Attributes(asize);
281                 entries.put(name, attr);
282             }
283             attr.read(fis, lbuf);
284             ecount++;
285             acount += attr.size();
286             //XXX: Fix for when the average is 0. When it is 0,
287             // you get an Attributes object with an initial
288             // capacity of 0, which tickles a bug in HashMap.
289             asize = Math.max(2, acount / ecount);
290
291             name = null;
292             skipEmptyLines = true;
293         }
294     }
295
296     private String parseName(byte[] lbuf, int len) {
297         if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
298             toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
299             lbuf[4] == ':' && lbuf[5] == ' ') {
300             try {
301                 return new String(lbuf, 6, len - 6, "UTF8");
302             }
303             catch (Exception e) {
304             }
305         }
306         return null;
307     }
308
309     private int toLower(int c) {
310         return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
311     }
312
313     /**
314      * Returns true if the specified Object is also a Manifest and has
315      * the same main Attributes and entries.
316      *
317      * @param o the object to be compared
318      * @return true if the specified Object is also a Manifest and has
319      * the same main Attributes and entries
320      */

321     public boolean equals(Object o) {
322         if (o instanceof Manifest) {
323             Manifest m = (Manifest)o;
324             return attr.equals(m.getMainAttributes()) &&
325                    entries.equals(m.getEntries());
326         } else {
327             return false;
328         }
329     }
330
331     /**
332      * Returns the hash code for this Manifest.
333      */

334     public int hashCode() {
335         return attr.hashCode() + entries.hashCode();
336     }
337
338     /**
339      * Returns a shallow copy of this Manifest.  The shallow copy is
340      * implemented as follows:
341      * <pre>
342      *     public Object clone() { return new Manifest(this); }
343      * </pre>
344      * @return a shallow copy of this Manifest
345      */

346     public Object clone() {
347         return new Manifest(this);
348     }
349
350     /*
351      * A fast buffered input stream for parsing manifest files.
352      */

353     static class FastInputStream extends FilterInputStream {
354         private byte buf[];
355         private int count = 0;
356         private int pos = 0;
357
358         FastInputStream(InputStream in) {
359             this(in, 8192);
360         }
361
362         FastInputStream(InputStream in, int size) {
363             super(in);
364             buf = new byte[size];
365         }
366
367         public int read() throws IOException {
368             if (pos >= count) {
369                 fill();
370                 if (pos >= count) {
371                     return -1;
372                 }
373             }
374             return Byte.toUnsignedInt(buf[pos++]);
375         }
376
377         public int read(byte[] b, int off, int len) throws IOException {
378             int avail = count - pos;
379             if (avail <= 0) {
380                 if (len >= buf.length) {
381                     return in.read(b, off, len);
382                 }
383                 fill();
384                 avail = count - pos;
385                 if (avail <= 0) {
386                     return -1;
387                 }
388             }
389             if (len > avail) {
390                 len = avail;
391             }
392             System.arraycopy(buf, pos, b, off, len);
393             pos += len;
394             return len;
395         }
396
397         /*
398          * Reads 'len' bytes from the input stream, or until an end-of-line
399          * is reached. Returns the number of bytes read.
400          */

401         public int readLine(byte[] b, int off, int len) throws IOException {
402             byte[] tbuf = this.buf;
403             int total = 0;
404             while (total < len) {
405                 int avail = count - pos;
406                 if (avail <= 0) {
407                     fill();
408                     avail = count - pos;
409                     if (avail <= 0) {
410                         return -1;
411                     }
412                 }
413                 int n = len - total;
414                 if (n > avail) {
415                     n = avail;
416                 }
417                 int tpos = pos;
418                 int maxpos = tpos + n;
419                 byte c = 0;
420                 // jar.spec.newline: CRLF | LF | CR (not followed by LF)
421                 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r');
422                 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') {
423                     tpos++;
424                 }
425                 n = tpos - pos;
426                 System.arraycopy(tbuf, pos, b, off, n);
427                 off += n;
428                 total += n;
429                 pos = tpos;
430                 c = tbuf[tpos-1];
431                 if (c == '\n') {
432                     break;
433                 }
434                 if (c == '\r') {
435                     if (count == pos) {
436                         // try to see if there is a trailing LF
437                         fill();
438                         if (pos < count && tbuf[pos] == '\n') {
439                             if (total < len) {
440                                 b[off++] = '\n';
441                                 total++;
442                             } else {
443                                 // we should always have big enough lbuf but
444                                 // just in case we don't, replace the last CR
445                                 // with LF.
446                                 b[off - 1] = '\n';
447                             }
448                             pos++;
449                         }
450                     }
451                     break;
452                 }
453             }
454             return total;
455         }
456
457         public byte peek() throws IOException {
458             if (pos == count)
459                 fill();
460             if (pos == count)
461                 return -1; // nothing left in buffer
462             return buf[pos];
463         }
464
465         public int readLine(byte[] b) throws IOException {
466             return readLine(b, 0, b.length);
467         }
468
469         public long skip(long n) throws IOException {
470             if (n <= 0) {
471                 return 0;
472             }
473             long avail = count - pos;
474             if (avail <= 0) {
475                 return in.skip(n);
476             }
477             if (n > avail) {
478                 n = avail;
479             }
480             pos += n;
481             return n;
482         }
483
484         public int available() throws IOException {
485             return (count - pos) + in.available();
486         }
487
488         public void close() throws IOException {
489             if (in != null) {
490                 in.close();
491                 in = null;
492                 buf = null;
493             }
494         }
495
496         private void fill() throws IOException {
497             count = pos = 0;
498             int n = in.read(buf, 0, buf.length);
499             if (n > 0) {
500                 count = n;
501             }
502         }
503     }
504 }
505