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