1 /*
2  * Copyright (c) 1997, 2019, 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 static java.nio.charset.StandardCharsets.UTF_8;
29 
30 import java.io.DataOutputStream;
31 import java.io.FilterInputStream;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.OutputStream;
35 import java.util.HashMap;
36 import java.util.Map;
37 
38 import sun.security.util.SecurityProperties;
39 
40 /**
41  * The Manifest class is used to maintain Manifest entry names and their
42  * associated Attributes. There are main Manifest Attributes as well as
43  * per-entry Attributes. For information on the Manifest format, please
44  * see the
45  * <a href="{@docRoot}/../specs/jar/jar.html">
46  * Manifest format specification</a>.
47  *
48  * @author  David Connelly
49  * @see     Attributes
50  * @since   1.2
51  */
52 public class Manifest implements Cloneable {
53 
54     // manifest main attributes
55     private final Attributes attr = new Attributes();
56 
57     // manifest entries
58     private final Map<String, Attributes> entries = new HashMap<>();
59 
60     // associated JarVerifier, not null when called by JarFile::getManifest.
61     private final JarVerifier jv;
62 
63     /**
64      * Constructs a new, empty Manifest.
65      */
Manifest()66     public Manifest() {
67         jv = null;
68     }
69 
70     /**
71      * Constructs a new Manifest from the specified input stream.
72      *
73      * @param is the input stream containing manifest data
74      * @throws IOException if an I/O error has occurred
75      */
Manifest(InputStream is)76     public Manifest(InputStream is) throws IOException {
77         this(null, is, null);
78     }
79 
80     /**
81      * Constructs a new Manifest from the specified input stream.
82      *
83      * @param is the input stream containing manifest data
84      * @param jarFilename the name of the corresponding jar archive if available
85      * @throws IOException if an I/O error has occurred
86      */
Manifest(InputStream is, String jarFilename)87     Manifest(InputStream is, String jarFilename) throws IOException {
88         this(null, is, jarFilename);
89     }
90 
91     /**
92      * Constructs a new Manifest from the specified input stream
93      * and associates it with a JarVerifier.
94      *
95      * @param jv the JarVerifier to use
96      * @param is the input stream containing manifest data
97      * @param jarFilename the name of the corresponding jar archive if available
98      * @throws IOException if an I/O error has occurred
99      */
Manifest(JarVerifier jv, InputStream is, String jarFilename)100     Manifest(JarVerifier jv, InputStream is, String jarFilename) throws IOException {
101         read(is, jarFilename);
102         this.jv = jv;
103     }
104 
105     /**
106      * Constructs a new Manifest that is a copy of the specified Manifest.
107      *
108      * @param man the Manifest to copy
109      */
Manifest(Manifest man)110     public Manifest(Manifest man) {
111         attr.putAll(man.getMainAttributes());
112         entries.putAll(man.getEntries());
113         jv = man.jv;
114     }
115 
116     /**
117      * Returns the main Attributes for the Manifest.
118      * @return the main Attributes for the Manifest
119      */
getMainAttributes()120     public Attributes getMainAttributes() {
121         return attr;
122     }
123 
124     /**
125      * Returns a Map of the entries contained in this Manifest. Each entry
126      * is represented by a String name (key) and associated Attributes (value).
127      * The Map permits the {@code null} key, but no entry with a null key is
128      * created by {@link #read}, nor is such an entry written by using {@link
129      * #write}.
130      *
131      * @return a Map of the entries contained in this Manifest
132      */
getEntries()133     public Map<String,Attributes> getEntries() {
134         return entries;
135     }
136 
137     /**
138      * Returns the Attributes for the specified entry name.
139      * This method is defined as:
140      * <pre>
141      *      return (Attributes)getEntries().get(name)
142      * </pre>
143      * Though {@code null} is a valid {@code name}, when
144      * {@code getAttributes(null)} is invoked on a {@code Manifest}
145      * obtained from a jar file, {@code null} will be returned.  While jar
146      * files themselves do not allow {@code null}-named attributes, it is
147      * possible to invoke {@link #getEntries} on a {@code Manifest}, and
148      * on that result, invoke {@code put} with a null key and an
149      * arbitrary value.  Subsequent invocations of
150      * {@code getAttributes(null)} will return the just-{@code put}
151      * value.
152      * <p>
153      * Note that this method does not return the manifest's main attributes;
154      * see {@link #getMainAttributes}.
155      *
156      * @param name entry name
157      * @return the Attributes for the specified entry name
158      */
getAttributes(String name)159     public Attributes getAttributes(String name) {
160         return getEntries().get(name);
161     }
162 
163     /**
164      * Returns the Attributes for the specified entry name, if trusted.
165      *
166      * @param name entry name
167      * @return returns the same result as {@link #getAttributes(String)}
168      * @throws SecurityException if the associated jar is signed but this entry
169      *      has been modified after signing (i.e. the section in the manifest
170      *      does not exist in SF files of all signers).
171      */
getTrustedAttributes(String name)172     Attributes getTrustedAttributes(String name) {
173         // Note: Before the verification of MANIFEST.MF/.SF/.RSA files is done,
174         // jv.isTrustedManifestEntry() isn't able to detect MANIFEST.MF change.
175         // Users of this method should call SharedSecrets.javaUtilJarAccess()
176         // .ensureInitialization() first.
177         Attributes result = getAttributes(name);
178         if (result != null && jv != null && ! jv.isTrustedManifestEntry(name)) {
179             throw new SecurityException("Untrusted manifest entry: " + name);
180         }
181         return result;
182     }
183 
184     /**
185      * Clears the main Attributes as well as the entries in this Manifest.
186      */
clear()187     public void clear() {
188         attr.clear();
189         entries.clear();
190     }
191 
192     /**
193      * Writes the Manifest to the specified OutputStream.
194      * Attributes.Name.MANIFEST_VERSION must be set in
195      * MainAttributes prior to invoking this method.
196      *
197      * @param out the output stream
198      * @throws    IOException if an I/O error has occurred
199      * @see #getMainAttributes
200      */
write(OutputStream out)201     public void write(OutputStream out) throws IOException {
202         DataOutputStream dos = new DataOutputStream(out);
203         // Write out the main attributes for the manifest
204         attr.writeMain(dos);
205         // Now write out the per-entry attributes
206         StringBuilder buffer = entries.isEmpty() ? null : new StringBuilder(72);
207         for (Map.Entry<String, Attributes> e : entries.entrySet()) {
208             buffer.setLength(0);
209             buffer.append("Name: ");
210             buffer.append(e.getKey());
211             println72(dos, buffer.toString());
212             e.getValue().write(dos);
213         }
214         dos.flush();
215     }
216 
217     /**
218      * Adds line breaks to enforce a maximum of 72 bytes per line.
219      *
220      * @deprecation Replaced with {@link #println72}.
221      */
222     @Deprecated(since = "13")
make72Safe(StringBuffer line)223     static void make72Safe(StringBuffer line) {
224         int length = line.length();
225         int index = 72;
226         while (index < length) {
227             line.insert(index, "\r\n ");
228             index += 74; // + line width + line break ("\r\n")
229             length += 3; // + line break ("\r\n") and space
230         }
231     }
232 
233     /**
234      * Writes {@code line} to {@code out} with line breaks and continuation
235      * spaces within the limits of 72 bytes of contents per line followed
236      * by a line break.
237      */
println72(OutputStream out, String line)238     static void println72(OutputStream out, String line) throws IOException {
239         if (!line.isEmpty()) {
240             byte[] lineBytes = line.getBytes(UTF_8);
241             int length = lineBytes.length;
242             // first line can hold one byte more than subsequent lines which
243             // start with a continuation line break space
244             out.write(lineBytes[0]);
245             int pos = 1;
246             while (length - pos > 71) {
247                 out.write(lineBytes, pos, 71);
248                 pos += 71;
249                 println(out);
250                 out.write(' ');
251             }
252             out.write(lineBytes, pos, length - pos);
253         }
254         println(out);
255     }
256 
257     /**
258      * Writes a line break to {@code out}.
259      */
println(OutputStream out)260     static void println(OutputStream out) throws IOException {
261         out.write('\r');
262         out.write('\n');
263     }
264 
getErrorPosition(String filename, final int lineNumber)265     static String getErrorPosition(String filename, final int lineNumber) {
266         if (filename == null ||
267                 !SecurityProperties.INCLUDE_JAR_NAME_IN_EXCEPTIONS) {
268             return "line " + lineNumber;
269         }
270         return "manifest of " + filename + ":" + lineNumber;
271     }
272 
273     /**
274      * Reads the Manifest from the specified InputStream. The entry
275      * names and attributes read will be merged in with the current
276      * manifest entries.
277      *
278      * @param is the input stream
279      * @throws    IOException if an I/O error has occurred
280      */
read(InputStream is)281     public void read(InputStream is) throws IOException {
282         read(is, null);
283     }
284 
read(InputStream is, String jarFilename)285     private void read(InputStream is, String jarFilename) throws IOException {
286         // Buffered input stream for reading manifest data
287         FastInputStream fis = new FastInputStream(is);
288         // Line buffer
289         byte[] lbuf = new byte[512];
290         // Read the main attributes for the manifest
291         int lineNumber = attr.read(fis, lbuf, jarFilename, 0);
292         // Total number of entries, attributes read
293         int ecount = 0, acount = 0;
294         // Average size of entry attributes
295         int asize = 2;
296         // Now parse the manifest entries
297         int len;
298         String name = null;
299         boolean skipEmptyLines = true;
300         byte[] lastline = null;
301 
302         while ((len = fis.readLine(lbuf)) != -1) {
303             byte c = lbuf[--len];
304             lineNumber++;
305 
306             if (c != '\n' && c != '\r') {
307                 throw new IOException("manifest line too long ("
308                            + getErrorPosition(jarFilename, lineNumber) + ")");
309             }
310             if (len > 0 && lbuf[len-1] == '\r') {
311                 --len;
312             }
313             if (len == 0 && skipEmptyLines) {
314                 continue;
315             }
316             skipEmptyLines = false;
317 
318             if (name == null) {
319                 name = parseName(lbuf, len);
320                 if (name == null) {
321                     throw new IOException("invalid manifest format ("
322                               + getErrorPosition(jarFilename, lineNumber) + ")");
323                 }
324                 if (fis.peek() == ' ') {
325                     // name is wrapped
326                     lastline = new byte[len - 6];
327                     System.arraycopy(lbuf, 6, lastline, 0, len - 6);
328                     continue;
329                 }
330             } else {
331                 // continuation line
332                 byte[] buf = new byte[lastline.length + len - 1];
333                 System.arraycopy(lastline, 0, buf, 0, lastline.length);
334                 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
335                 if (fis.peek() == ' ') {
336                     // name is wrapped
337                     lastline = buf;
338                     continue;
339                 }
340                 name = new String(buf, 0, buf.length, UTF_8);
341                 lastline = null;
342             }
343             Attributes attr = getAttributes(name);
344             if (attr == null) {
345                 attr = new Attributes(asize);
346                 entries.put(name, attr);
347             }
348             lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber);
349             ecount++;
350             acount += attr.size();
351             //XXX: Fix for when the average is 0. When it is 0,
352             // you get an Attributes object with an initial
353             // capacity of 0, which tickles a bug in HashMap.
354             asize = Math.max(2, acount / ecount);
355 
356             name = null;
357             skipEmptyLines = true;
358         }
359     }
360 
parseName(byte[] lbuf, int len)361     private String parseName(byte[] lbuf, int len) {
362         if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
363             toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
364             lbuf[4] == ':' && lbuf[5] == ' ') {
365             return new String(lbuf, 6, len - 6, UTF_8);
366         }
367         return null;
368     }
369 
toLower(int c)370     private int toLower(int c) {
371         return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
372     }
373 
374     /**
375      * Returns true if the specified Object is also a Manifest and has
376      * the same main Attributes and entries.
377      *
378      * @param o the object to be compared
379      * @return true if the specified Object is also a Manifest and has
380      * the same main Attributes and entries
381      */
equals(Object o)382     public boolean equals(Object o) {
383         // TODO(b/248243024) Revert this.
384         /*
385         return o instanceof Manifest m
386                 && attr.equals(m.getMainAttributes())
387                 && entries.equals(m.getEntries());
388         */
389         if (o instanceof Manifest) {
390             Manifest other = (Manifest) o;
391 
392             return attr.equals(other.getMainAttributes())
393                     && entries.equals(other.getEntries());
394         }
395         return false;
396     }
397 
398     /**
399      * Returns the hash code for this Manifest.
400      */
hashCode()401     public int hashCode() {
402         return attr.hashCode() + entries.hashCode();
403     }
404 
405     /**
406      * Returns a shallow copy of this Manifest.  The shallow copy is
407      * implemented as follows:
408      * <pre>
409      *     public Object clone() { return new Manifest(this); }
410      * </pre>
411      * @return a shallow copy of this Manifest
412      */
clone()413     public Object clone() {
414         return new Manifest(this);
415     }
416 
417     /*
418      * A fast buffered input stream for parsing manifest files.
419      */
420     static class FastInputStream extends FilterInputStream {
421         private byte buf[];
422         private int count = 0;
423         private int pos = 0;
424 
FastInputStream(InputStream in)425         FastInputStream(InputStream in) {
426             this(in, 8192);
427         }
428 
FastInputStream(InputStream in, int size)429         FastInputStream(InputStream in, int size) {
430             super(in);
431             buf = new byte[size];
432         }
433 
read()434         public int read() throws IOException {
435             if (pos >= count) {
436                 fill();
437                 if (pos >= count) {
438                     return -1;
439                 }
440             }
441             return Byte.toUnsignedInt(buf[pos++]);
442         }
443 
read(byte[] b, int off, int len)444         public int read(byte[] b, int off, int len) throws IOException {
445             int avail = count - pos;
446             if (avail <= 0) {
447                 if (len >= buf.length) {
448                     return in.read(b, off, len);
449                 }
450                 fill();
451                 avail = count - pos;
452                 if (avail <= 0) {
453                     return -1;
454                 }
455             }
456             if (len > avail) {
457                 len = avail;
458             }
459             System.arraycopy(buf, pos, b, off, len);
460             pos += len;
461             return len;
462         }
463 
464         /*
465          * Reads 'len' bytes from the input stream, or until an end-of-line
466          * is reached. Returns the number of bytes read.
467          */
readLine(byte[] b, int off, int len)468         public int readLine(byte[] b, int off, int len) throws IOException {
469             byte[] tbuf = this.buf;
470             int total = 0;
471             while (total < len) {
472                 int avail = count - pos;
473                 if (avail <= 0) {
474                     fill();
475                     avail = count - pos;
476                     if (avail <= 0) {
477                         return -1;
478                     }
479                 }
480                 int n = len - total;
481                 if (n > avail) {
482                     n = avail;
483                 }
484                 int tpos = pos;
485                 int maxpos = tpos + n;
486                 byte c = 0;
487                 // jar.spec.newline: CRLF | LF | CR (not followed by LF)
488                 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r');
489                 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') {
490                     tpos++;
491                 }
492                 n = tpos - pos;
493                 System.arraycopy(tbuf, pos, b, off, n);
494                 off += n;
495                 total += n;
496                 pos = tpos;
497                 c = tbuf[tpos-1];
498                 if (c == '\n') {
499                     break;
500                 }
501                 if (c == '\r') {
502                     if (count == pos) {
503                         // try to see if there is a trailing LF
504                         fill();
505                         if (pos < count && tbuf[pos] == '\n') {
506                             if (total < len) {
507                                 b[off++] = '\n';
508                                 total++;
509                             } else {
510                                 // we should always have big enough lbuf but
511                                 // just in case we don't, replace the last CR
512                                 // with LF.
513                                 b[off - 1] = '\n';
514                             }
515                             pos++;
516                         }
517                     }
518                     break;
519                 }
520             }
521             return total;
522         }
523 
peek()524         public byte peek() throws IOException {
525             if (pos == count)
526                 fill();
527             if (pos == count)
528                 return -1; // nothing left in buffer
529             return buf[pos];
530         }
531 
readLine(byte[] b)532         public int readLine(byte[] b) throws IOException {
533             return readLine(b, 0, b.length);
534         }
535 
skip(long n)536         public long skip(long n) throws IOException {
537             if (n <= 0) {
538                 return 0;
539             }
540             long avail = count - pos;
541             if (avail <= 0) {
542                 return in.skip(n);
543             }
544             if (n > avail) {
545                 n = avail;
546             }
547             pos += n;
548             return n;
549         }
550 
available()551         public int available() throws IOException {
552             return (count - pos) + in.available();
553         }
554 
close()555         public void close() throws IOException {
556             if (in != null) {
557                 in.close();
558                 in = null;
559                 buf = null;
560             }
561         }
562 
fill()563         private void fill() throws IOException {
564             count = pos = 0;
565             int n = in.read(buf, 0, buf.length);
566             if (n > 0) {
567                 count = n;
568             }
569         }
570     }
571 }
572