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