Home | History | Annotate | Download | only in jar
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package java.util.jar;
     19 
     20 import java.io.ByteArrayInputStream;
     21 import java.io.IOException;
     22 import java.io.InputStream;
     23 import java.io.OutputStream;
     24 import java.lang.reflect.Field;
     25 import java.nio.ByteBuffer;
     26 import java.nio.CharBuffer;
     27 import java.nio.charset.CharsetEncoder;
     28 import java.nio.charset.Charsets;
     29 import java.nio.charset.CoderResult;
     30 import java.util.HashMap;
     31 import java.util.Iterator;
     32 import java.util.Map;
     33 import libcore.io.Streams;
     34 
     35 /**
     36  * The {@code Manifest} class is used to obtain attribute information for a
     37  * {@code JarFile} and its entries.
     38  */
     39 public class Manifest implements Cloneable {
     40     static final int LINE_LENGTH_LIMIT = 72;
     41 
     42     private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
     43 
     44     private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
     45 
     46     private static final Attributes.Name NAME_ATTRIBUTE = new Attributes.Name("Name");
     47 
     48     private static final Field BAIS_BUF = getByteArrayInputStreamField("buf");
     49     private static final Field BAIS_POS = getByteArrayInputStreamField("pos");
     50 
     51     private static Field getByteArrayInputStreamField(String name) {
     52         try {
     53             Field f = ByteArrayInputStream.class.getDeclaredField(name);
     54             f.setAccessible(true);
     55             return f;
     56         } catch (Exception ex) {
     57             throw new AssertionError(ex);
     58         }
     59     }
     60 
     61     private Attributes mainAttributes = new Attributes();
     62 
     63     private HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
     64 
     65     static class Chunk {
     66         int start;
     67         int end;
     68 
     69         Chunk(int start, int end) {
     70             this.start = start;
     71             this.end = end;
     72         }
     73     }
     74 
     75     private HashMap<String, Chunk> chunks;
     76 
     77     /**
     78      * The end of the main attributes section in the manifest is needed in
     79      * verification.
     80      */
     81     private int mainEnd;
     82 
     83     /**
     84      * Creates a new {@code Manifest} instance.
     85      */
     86     public Manifest() {
     87     }
     88 
     89     /**
     90      * Creates a new {@code Manifest} instance using the attributes obtained
     91      * from the input stream.
     92      *
     93      * @param is
     94      *            {@code InputStream} to parse for attributes.
     95      * @throws IOException
     96      *             if an IO error occurs while creating this {@code Manifest}
     97      */
     98     public Manifest(InputStream is) throws IOException {
     99         read(is);
    100     }
    101 
    102     /**
    103      * Creates a new {@code Manifest} instance. The new instance will have the
    104      * same attributes as those found in the parameter {@code Manifest}.
    105      *
    106      * @param man
    107      *            {@code Manifest} instance to obtain attributes from.
    108      */
    109     @SuppressWarnings("unchecked")
    110     public Manifest(Manifest man) {
    111         mainAttributes = (Attributes) man.mainAttributes.clone();
    112         entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
    113                 .getEntries()).clone();
    114     }
    115 
    116     Manifest(InputStream is, boolean readChunks) throws IOException {
    117         if (readChunks) {
    118             chunks = new HashMap<String, Chunk>();
    119         }
    120         read(is);
    121     }
    122 
    123     /**
    124      * Resets the both the main attributes as well as the entry attributes
    125      * associated with this {@code Manifest}.
    126      */
    127     public void clear() {
    128         entries.clear();
    129         mainAttributes.clear();
    130     }
    131 
    132     /**
    133      * Returns the {@code Attributes} associated with the parameter entry
    134      * {@code name}.
    135      *
    136      * @param name
    137      *            the name of the entry to obtain {@code Attributes} from.
    138      * @return the Attributes for the entry or {@code null} if the entry does
    139      *         not exist.
    140      */
    141     public Attributes getAttributes(String name) {
    142         return getEntries().get(name);
    143     }
    144 
    145     /**
    146      * Returns a map containing the {@code Attributes} for each entry in the
    147      * {@code Manifest}.
    148      *
    149      * @return the map of entry attributes.
    150      */
    151     public Map<String, Attributes> getEntries() {
    152         return entries;
    153     }
    154 
    155     /**
    156      * Returns the main {@code Attributes} of the {@code JarFile}.
    157      *
    158      * @return main {@code Attributes} associated with the source {@code
    159      *         JarFile}.
    160      */
    161     public Attributes getMainAttributes() {
    162         return mainAttributes;
    163     }
    164 
    165     /**
    166      * Creates a copy of this {@code Manifest}. The returned {@code Manifest}
    167      * will equal the {@code Manifest} from which it was cloned.
    168      *
    169      * @return a copy of this instance.
    170      */
    171     @Override
    172     public Object clone() {
    173         return new Manifest(this);
    174     }
    175 
    176     /**
    177      * Writes this {@code Manifest}'s name/attributes pairs to the given {@code OutputStream}.
    178      * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
    179      * calling this method, or no attributes will be written.
    180      *
    181      * @throws IOException
    182      *             If an error occurs writing the {@code Manifest}.
    183      */
    184     public void write(OutputStream os) throws IOException {
    185         write(this, os);
    186     }
    187 
    188     /**
    189      * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
    190      *
    191      * @param is
    192      *            The {@code InputStream} to read from.
    193      * @throws IOException
    194      *             If an error occurs reading the manifest.
    195      */
    196     public void read(InputStream is) throws IOException {
    197         byte[] buf;
    198         if (is instanceof ByteArrayInputStream) {
    199             buf = exposeByteArrayInputStreamBytes((ByteArrayInputStream) is);
    200         } else {
    201             buf = Streams.readFullyNoClose(is);
    202         }
    203 
    204         if (buf.length == 0) {
    205             return;
    206         }
    207 
    208         // a workaround for HARMONY-5662
    209         // replace EOF and NUL with another new line
    210         // which does not trigger an error
    211         byte b = buf[buf.length - 1];
    212         if (b == 0 || b == 26) {
    213             buf[buf.length - 1] = '\n';
    214         }
    215 
    216         InitManifest im = new InitManifest(buf, mainAttributes);
    217         mainEnd = im.getPos();
    218         im.initEntries(entries, chunks);
    219     }
    220 
    221     /**
    222      * Returns a byte[] containing all the bytes from a ByteArrayInputStream.
    223      * Where possible, this returns the actual array rather than a copy.
    224      */
    225     private static byte[] exposeByteArrayInputStreamBytes(ByteArrayInputStream bais) {
    226         byte[] buffer;
    227         synchronized (bais) {
    228             byte[] buf;
    229             int pos;
    230             try {
    231                 buf = (byte[]) BAIS_BUF.get(bais);
    232                 pos = BAIS_POS.getInt(bais);
    233             } catch (IllegalAccessException iae) {
    234                 throw new AssertionError(iae);
    235             }
    236             int available = bais.available();
    237             if (pos == 0 && buf.length == available) {
    238                 buffer = buf;
    239             } else {
    240                 buffer = new byte[available];
    241                 System.arraycopy(buf, pos, buffer, 0, available);
    242             }
    243             bais.skip(available);
    244         }
    245         return buffer;
    246     }
    247 
    248     /**
    249      * Returns the hash code for this instance.
    250      *
    251      * @return this {@code Manifest}'s hashCode.
    252      */
    253     @Override
    254     public int hashCode() {
    255         return mainAttributes.hashCode() ^ getEntries().hashCode();
    256     }
    257 
    258     /**
    259      * Determines if the receiver is equal to the parameter object. Two {@code
    260      * Manifest}s are equal if they have identical main attributes as well as
    261      * identical entry attributes.
    262      *
    263      * @param o
    264      *            the object to compare against.
    265      * @return {@code true} if the manifests are equal, {@code false} otherwise
    266      */
    267     @Override
    268     public boolean equals(Object o) {
    269         if (o == null) {
    270             return false;
    271         }
    272         if (o.getClass() != this.getClass()) {
    273             return false;
    274         }
    275         if (!mainAttributes.equals(((Manifest) o).mainAttributes)) {
    276             return false;
    277         }
    278         return getEntries().equals(((Manifest) o).getEntries());
    279     }
    280 
    281     Chunk getChunk(String name) {
    282         return chunks.get(name);
    283     }
    284 
    285     void removeChunks() {
    286         chunks = null;
    287     }
    288 
    289     int getMainAttributesEnd() {
    290         return mainEnd;
    291     }
    292 
    293     /**
    294      * Writes out the attribute information of the specified manifest to the
    295      * specified {@code OutputStream}
    296      *
    297      * @param manifest
    298      *            the manifest to write out.
    299      * @param out
    300      *            The {@code OutputStream} to write to.
    301      * @throws IOException
    302      *             If an error occurs writing the {@code Manifest}.
    303      */
    304     static void write(Manifest manifest, OutputStream out) throws IOException {
    305         CharsetEncoder encoder = Charsets.UTF_8.newEncoder();
    306         ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
    307 
    308         Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
    309         String version = manifest.mainAttributes.getValue(versionName);
    310         if (version == null) {
    311             versionName = Attributes.Name.SIGNATURE_VERSION;
    312             version = manifest.mainAttributes.getValue(versionName);
    313         }
    314         if (version != null) {
    315             writeEntry(out, versionName, version, encoder, buffer);
    316             Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
    317             while (entries.hasNext()) {
    318                 Attributes.Name name = (Attributes.Name) entries.next();
    319                 if (!name.equals(versionName)) {
    320                     writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
    321                 }
    322             }
    323         }
    324         out.write(LINE_SEPARATOR);
    325         Iterator<String> i = manifest.getEntries().keySet().iterator();
    326         while (i.hasNext()) {
    327             String key = i.next();
    328             writeEntry(out, NAME_ATTRIBUTE, key, encoder, buffer);
    329             Attributes attributes = manifest.entries.get(key);
    330             Iterator<?> entries = attributes.keySet().iterator();
    331             while (entries.hasNext()) {
    332                 Attributes.Name name = (Attributes.Name) entries.next();
    333                 writeEntry(out, name, attributes.getValue(name), encoder, buffer);
    334             }
    335             out.write(LINE_SEPARATOR);
    336         }
    337     }
    338 
    339     private static void writeEntry(OutputStream os, Attributes.Name name,
    340             String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
    341         String nameString = name.getName();
    342         os.write(nameString.getBytes(Charsets.US_ASCII));
    343         os.write(VALUE_SEPARATOR);
    344 
    345         encoder.reset();
    346         bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
    347 
    348         CharBuffer cBuf = CharBuffer.wrap(value);
    349 
    350         while (true) {
    351             CoderResult r = encoder.encode(cBuf, bBuf, true);
    352             if (CoderResult.UNDERFLOW == r) {
    353                 r = encoder.flush(bBuf);
    354             }
    355             os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
    356             os.write(LINE_SEPARATOR);
    357             if (CoderResult.UNDERFLOW == r) {
    358                 break;
    359             }
    360             os.write(' ');
    361             bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
    362         }
    363     }
    364 }
    365