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 android.util.jar;
     19 
     20 import java.io.IOException;
     21 import java.io.InputStream;
     22 import java.io.OutputStream;
     23 import java.nio.ByteBuffer;
     24 import java.nio.CharBuffer;
     25 import java.nio.charset.CharsetEncoder;
     26 import java.nio.charset.CoderResult;
     27 import java.nio.charset.StandardCharsets;
     28 import java.util.HashMap;
     29 import java.util.Iterator;
     30 import java.util.Map;
     31 import java.util.jar.Attributes;
     32 import libcore.io.Streams;
     33 
     34 /**
     35  * The {@code StrictJarManifest} class is used to obtain attribute information for a
     36  * {@code StrictJarFile} and its entries.
     37  *
     38  * @hide
     39  */
     40 public class StrictJarManifest implements Cloneable {
     41     static final int LINE_LENGTH_LIMIT = 72;
     42 
     43     private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' };
     44 
     45     private static final byte[] VALUE_SEPARATOR = new byte[] { ':', ' ' };
     46 
     47     private final Attributes mainAttributes;
     48     private final HashMap<String, Attributes> entries;
     49 
     50     static final class Chunk {
     51         final int start;
     52         final int end;
     53 
     54         Chunk(int start, int end) {
     55             this.start = start;
     56             this.end = end;
     57         }
     58     }
     59 
     60     private HashMap<String, Chunk> chunks;
     61 
     62     /**
     63      * The end of the main attributes section in the manifest is needed in
     64      * verification.
     65      */
     66     private int mainEnd;
     67 
     68     /**
     69      * Creates a new {@code StrictJarManifest} instance.
     70      */
     71     public StrictJarManifest() {
     72         entries = new HashMap<String, Attributes>();
     73         mainAttributes = new Attributes();
     74     }
     75 
     76     /**
     77      * Creates a new {@code StrictJarManifest} instance using the attributes obtained
     78      * from the input stream.
     79      *
     80      * @param is
     81      *            {@code InputStream} to parse for attributes.
     82      * @throws IOException
     83      *             if an IO error occurs while creating this {@code StrictJarManifest}
     84      */
     85     public StrictJarManifest(InputStream is) throws IOException {
     86         this();
     87         read(Streams.readFully(is));
     88     }
     89 
     90     /**
     91      * Creates a new {@code StrictJarManifest} instance. The new instance will have the
     92      * same attributes as those found in the parameter {@code StrictJarManifest}.
     93      *
     94      * @param man
     95      *            {@code StrictJarManifest} instance to obtain attributes from.
     96      */
     97     @SuppressWarnings("unchecked")
     98     public StrictJarManifest(StrictJarManifest man) {
     99         mainAttributes = (Attributes) man.mainAttributes.clone();
    100         entries = (HashMap<String, Attributes>) ((HashMap<String, Attributes>) man
    101                 .getEntries()).clone();
    102     }
    103 
    104     StrictJarManifest(byte[] manifestBytes, boolean readChunks) throws IOException {
    105         this();
    106         if (readChunks) {
    107             chunks = new HashMap<String, Chunk>();
    108         }
    109         read(manifestBytes);
    110     }
    111 
    112     /**
    113      * Resets the both the main attributes as well as the entry attributes
    114      * associated with this {@code StrictJarManifest}.
    115      */
    116     public void clear() {
    117         entries.clear();
    118         mainAttributes.clear();
    119     }
    120 
    121     /**
    122      * Returns the {@code Attributes} associated with the parameter entry
    123      * {@code name}.
    124      *
    125      * @param name
    126      *            the name of the entry to obtain {@code Attributes} from.
    127      * @return the Attributes for the entry or {@code null} if the entry does
    128      *         not exist.
    129      */
    130     public Attributes getAttributes(String name) {
    131         return getEntries().get(name);
    132     }
    133 
    134     /**
    135      * Returns a map containing the {@code Attributes} for each entry in the
    136      * {@code StrictJarManifest}.
    137      *
    138      * @return the map of entry attributes.
    139      */
    140     public Map<String, Attributes> getEntries() {
    141         return entries;
    142     }
    143 
    144     /**
    145      * Returns the main {@code Attributes} of the {@code JarFile}.
    146      *
    147      * @return main {@code Attributes} associated with the source {@code
    148      *         JarFile}.
    149      */
    150     public Attributes getMainAttributes() {
    151         return mainAttributes;
    152     }
    153 
    154     /**
    155      * Creates a copy of this {@code StrictJarManifest}. The returned {@code StrictJarManifest}
    156      * will equal the {@code StrictJarManifest} from which it was cloned.
    157      *
    158      * @return a copy of this instance.
    159      */
    160     @Override
    161     public Object clone() {
    162         return new StrictJarManifest(this);
    163     }
    164 
    165     /**
    166      * Writes this {@code StrictJarManifest}'s name/attributes pairs to the given {@code OutputStream}.
    167      * The {@code MANIFEST_VERSION} or {@code SIGNATURE_VERSION} attribute must be set before
    168      * calling this method, or no attributes will be written.
    169      *
    170      * @throws IOException
    171      *             If an error occurs writing the {@code StrictJarManifest}.
    172      */
    173     public void write(OutputStream os) throws IOException {
    174         write(this, os);
    175     }
    176 
    177     /**
    178      * Merges name/attribute pairs read from the input stream {@code is} into this manifest.
    179      *
    180      * @param is
    181      *            The {@code InputStream} to read from.
    182      * @throws IOException
    183      *             If an error occurs reading the manifest.
    184      */
    185     public void read(InputStream is) throws IOException {
    186         read(Streams.readFullyNoClose(is));
    187     }
    188 
    189     private void read(byte[] buf) throws IOException {
    190         if (buf.length == 0) {
    191             return;
    192         }
    193 
    194         StrictJarManifestReader im = new StrictJarManifestReader(buf, mainAttributes);
    195         mainEnd = im.getEndOfMainSection();
    196         im.readEntries(entries, chunks);
    197     }
    198 
    199     /**
    200      * Returns the hash code for this instance.
    201      *
    202      * @return this {@code StrictJarManifest}'s hashCode.
    203      */
    204     @Override
    205     public int hashCode() {
    206         return mainAttributes.hashCode() ^ getEntries().hashCode();
    207     }
    208 
    209     /**
    210      * Determines if the receiver is equal to the parameter object. Two {@code
    211      * StrictJarManifest}s are equal if they have identical main attributes as well as
    212      * identical entry attributes.
    213      *
    214      * @param o
    215      *            the object to compare against.
    216      * @return {@code true} if the manifests are equal, {@code false} otherwise
    217      */
    218     @Override
    219     public boolean equals(Object o) {
    220         if (o == null) {
    221             return false;
    222         }
    223         if (o.getClass() != this.getClass()) {
    224             return false;
    225         }
    226         if (!mainAttributes.equals(((StrictJarManifest) o).mainAttributes)) {
    227             return false;
    228         }
    229         return getEntries().equals(((StrictJarManifest) o).getEntries());
    230     }
    231 
    232     Chunk getChunk(String name) {
    233         return chunks.get(name);
    234     }
    235 
    236     void removeChunks() {
    237         chunks = null;
    238     }
    239 
    240     int getMainAttributesEnd() {
    241         return mainEnd;
    242     }
    243 
    244     /**
    245      * Writes out the attribute information of the specified manifest to the
    246      * specified {@code OutputStream}
    247      *
    248      * @param manifest
    249      *            the manifest to write out.
    250      * @param out
    251      *            The {@code OutputStream} to write to.
    252      * @throws IOException
    253      *             If an error occurs writing the {@code StrictJarManifest}.
    254      */
    255     static void write(StrictJarManifest manifest, OutputStream out) throws IOException {
    256         CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
    257         ByteBuffer buffer = ByteBuffer.allocate(LINE_LENGTH_LIMIT);
    258 
    259         Attributes.Name versionName = Attributes.Name.MANIFEST_VERSION;
    260         String version = manifest.mainAttributes.getValue(versionName);
    261         if (version == null) {
    262             versionName = Attributes.Name.SIGNATURE_VERSION;
    263             version = manifest.mainAttributes.getValue(versionName);
    264         }
    265         if (version != null) {
    266             writeEntry(out, versionName, version, encoder, buffer);
    267             Iterator<?> entries = manifest.mainAttributes.keySet().iterator();
    268             while (entries.hasNext()) {
    269                 Attributes.Name name = (Attributes.Name) entries.next();
    270                 if (!name.equals(versionName)) {
    271                     writeEntry(out, name, manifest.mainAttributes.getValue(name), encoder, buffer);
    272                 }
    273             }
    274         }
    275         out.write(LINE_SEPARATOR);
    276         Iterator<String> i = manifest.getEntries().keySet().iterator();
    277         while (i.hasNext()) {
    278             String key = i.next();
    279             writeEntry(out, Attributes.Name.NAME, key, encoder, buffer);
    280             Attributes attributes = manifest.entries.get(key);
    281             Iterator<?> entries = attributes.keySet().iterator();
    282             while (entries.hasNext()) {
    283                 Attributes.Name name = (Attributes.Name) entries.next();
    284                 writeEntry(out, name, attributes.getValue(name), encoder, buffer);
    285             }
    286             out.write(LINE_SEPARATOR);
    287         }
    288     }
    289 
    290     private static void writeEntry(OutputStream os, Attributes.Name name,
    291             String value, CharsetEncoder encoder, ByteBuffer bBuf) throws IOException {
    292         String nameString = name.toString();
    293         os.write(nameString.getBytes(StandardCharsets.US_ASCII));
    294         os.write(VALUE_SEPARATOR);
    295 
    296         encoder.reset();
    297         bBuf.clear().limit(LINE_LENGTH_LIMIT - nameString.length() - 2);
    298 
    299         CharBuffer cBuf = CharBuffer.wrap(value);
    300 
    301         while (true) {
    302             CoderResult r = encoder.encode(cBuf, bBuf, true);
    303             if (CoderResult.UNDERFLOW == r) {
    304                 r = encoder.flush(bBuf);
    305             }
    306             os.write(bBuf.array(), bBuf.arrayOffset(), bBuf.position());
    307             os.write(LINE_SEPARATOR);
    308             if (CoderResult.UNDERFLOW == r) {
    309                 break;
    310             }
    311             os.write(' ');
    312             bBuf.clear().limit(LINE_LENGTH_LIMIT - 1);
    313         }
    314     }
    315 }
    316