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