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