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