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