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 java.io.DataOutputStream;
  29 import java.io.FilterInputStream;
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.io.OutputStream;
  33 import java.util.HashMap;
  34 import java.util.Map;
  35 
  36 import sun.security.util.SecurityProperties;
  37 
  38 import static java.nio.charset.StandardCharsets.UTF_8;
  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      */
  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      */
  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      */
  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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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")
 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      */
 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      */
 260     static void println(OutputStream out) throws IOException {
 261         out.write('\r');
 262         out.write('\n');
 263     }
 264 
 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      */
 281     public void read(InputStream is) throws IOException {
 282         read(is, null);
 283     }
 284 
 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 
 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             try {
 366                 return new String(lbuf, 6, len - 6, UTF_8);
 367             }
 368             catch (Exception e) {
 369             }
 370         }
 371         return null;
 372     }
 373 
 374     private int toLower(int c) {
 375         return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
 376     }
 377 
 378     /**
 379      * Returns true if the specified Object is also a Manifest and has
 380      * the same main Attributes and entries.
 381      *
 382      * @param o the object to be compared
 383      * @return true if the specified Object is also a Manifest and has
 384      * the same main Attributes and entries
 385      */
 386     public boolean equals(Object o) {
 387         if (o instanceof Manifest) {
 388             Manifest m = (Manifest)o;
 389             return attr.equals(m.getMainAttributes()) &&
 390                    entries.equals(m.getEntries());
 391         } else {
 392             return false;
 393         }
 394     }
 395 
 396     /**
 397      * Returns the hash code for this Manifest.
 398      */
 399     public int hashCode() {
 400         return attr.hashCode() + entries.hashCode();
 401     }
 402 
 403     /**
 404      * Returns a shallow copy of this Manifest.  The shallow copy is
 405      * implemented as follows:
 406      * <pre>
 407      *     public Object clone() { return new Manifest(this); }
 408      * </pre>
 409      * @return a shallow copy of this Manifest
 410      */
 411     public Object clone() {
 412         return new Manifest(this);
 413     }
 414 
 415     /*
 416      * A fast buffered input stream for parsing manifest files.
 417      */
 418     static class FastInputStream extends FilterInputStream {
 419         private byte buf[];
 420         private int count = 0;
 421         private int pos = 0;
 422 
 423         FastInputStream(InputStream in) {
 424             this(in, 8192);
 425         }
 426 
 427         FastInputStream(InputStream in, int size) {
 428             super(in);
 429             buf = new byte[size];
 430         }
 431 
 432         public int read() throws IOException {
 433             if (pos >= count) {
 434                 fill();
 435                 if (pos >= count) {
 436                     return -1;
 437                 }
 438             }
 439             return Byte.toUnsignedInt(buf[pos++]);
 440         }
 441 
 442         public int read(byte[] b, int off, int len) throws IOException {
 443             int avail = count - pos;
 444             if (avail <= 0) {
 445                 if (len >= buf.length) {
 446                     return in.read(b, off, len);
 447                 }
 448                 fill();
 449                 avail = count - pos;
 450                 if (avail <= 0) {
 451                     return -1;
 452                 }
 453             }
 454             if (len > avail) {
 455                 len = avail;
 456             }
 457             System.arraycopy(buf, pos, b, off, len);
 458             pos += len;
 459             return len;
 460         }
 461 
 462         /*
 463          * Reads 'len' bytes from the input stream, or until an end-of-line
 464          * is reached. Returns the number of bytes read.
 465          */
 466         public int readLine(byte[] b, int off, int len) throws IOException {
 467             byte[] tbuf = this.buf;
 468             int total = 0;
 469             while (total < len) {
 470                 int avail = count - pos;
 471                 if (avail <= 0) {
 472                     fill();
 473                     avail = count - pos;
 474                     if (avail <= 0) {
 475                         return -1;
 476                     }
 477                 }
 478                 int n = len - total;
 479                 if (n > avail) {
 480                     n = avail;
 481                 }
 482                 int tpos = pos;
 483                 int maxpos = tpos + n;
 484                 byte c = 0;
 485                 // jar.spec.newline: CRLF | LF | CR (not followed by LF)
 486                 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r');
 487                 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') {
 488                     tpos++;
 489                 }
 490                 n = tpos - pos;
 491                 System.arraycopy(tbuf, pos, b, off, n);
 492                 off += n;
 493                 total += n;
 494                 pos = tpos;
 495                 c = tbuf[tpos-1];
 496                 if (c == '\n') {
 497                     break;
 498                 }
 499                 if (c == '\r') {
 500                     if (count == pos) {
 501                         // try to see if there is a trailing LF
 502                         fill();
 503                         if (pos < count && tbuf[pos] == '\n') {
 504                             if (total < len) {
 505                                 b[off++] = '\n';
 506                                 total++;
 507                             } else {
 508                                 // we should always have big enough lbuf but
 509                                 // just in case we don't, replace the last CR
 510                                 // with LF.
 511                                 b[off - 1] = '\n';
 512                             }
 513                             pos++;
 514                         }
 515                     }
 516                     break;
 517                 }
 518             }
 519             return total;
 520         }
 521 
 522         public byte peek() throws IOException {
 523             if (pos == count)
 524                 fill();
 525             if (pos == count)
 526                 return -1; // nothing left in buffer
 527             return buf[pos];
 528         }
 529 
 530         public int readLine(byte[] b) throws IOException {
 531             return readLine(b, 0, b.length);
 532         }
 533 
 534         public long skip(long n) throws IOException {
 535             if (n <= 0) {
 536                 return 0;
 537             }
 538             long avail = count - pos;
 539             if (avail <= 0) {
 540                 return in.skip(n);
 541             }
 542             if (n > avail) {
 543                 n = avail;
 544             }
 545             pos += n;
 546             return n;
 547         }
 548 
 549         public int available() throws IOException {
 550             return (count - pos) + in.available();
 551         }
 552 
 553         public void close() throws IOException {
 554             if (in != null) {
 555                 in.close();
 556                 in = null;
 557                 buf = null;
 558             }
 559         }
 560 
 561         private void fill() throws IOException {
 562             count = pos = 0;
 563             int n = in.read(buf, 0, buf.length);
 564             if (n > 0) {
 565                 count = n;
 566             }
 567         }
 568     }
 569 }