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