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.ByteArrayOutputStream;
     21 import java.io.IOException;
     22 import java.nio.charset.StandardCharsets;
     23 import java.util.HashMap;
     24 import java.util.Map;
     25 
     26 /**
     27  * Reads a JAR file manifest. The specification is here:
     28  * http://java.sun.com/javase/6/docs/technotes/guides/jar/jar.html
     29  */
     30 class ManifestReader {
     31     // There are relatively few unique attribute names,
     32     // but a manifest might have thousands of entries.
     33     private final HashMap<String, Attributes.Name> attributeNameCache = new HashMap<String, Attributes.Name>();
     34 
     35     private final ByteArrayOutputStream valueBuffer = new ByteArrayOutputStream(80);
     36 
     37     private final byte[] buf;
     38 
     39     private final int endOfMainSection;
     40 
     41     private int pos;
     42 
     43     private Attributes.Name name;
     44 
     45     private String value;
     46 
     47     private int consecutiveLineBreaks = 0;
     48 
     49     public ManifestReader(byte[] buf, Attributes main) throws IOException {
     50         this.buf = buf;
     51         while (readHeader()) {
     52             main.put(name, value);
     53         }
     54         this.endOfMainSection = pos;
     55     }
     56 
     57     public void readEntries(Map<String, Attributes> entries, Map<String, Manifest.Chunk> chunks) throws IOException {
     58         int mark = pos;
     59         while (readHeader()) {
     60             if (!Attributes.Name.NAME.equals(name)) {
     61                 throw new IOException("Entry is not named");
     62             }
     63             String entryNameValue = value;
     64 
     65             Attributes entry = entries.get(entryNameValue);
     66             if (entry == null) {
     67                 entry = new Attributes(12);
     68             }
     69 
     70             while (readHeader()) {
     71                 entry.put(name, value);
     72             }
     73 
     74             if (chunks != null) {
     75                 if (chunks.get(entryNameValue) != null) {
     76                     // TODO A bug: there might be several verification chunks for
     77                     // the same name. I believe they should be used to update
     78                     // signature in order of appearance; there are two ways to fix
     79                     // this: either use a list of chunks, or decide on used
     80                     // signature algorithm in advance and reread the chunks while
     81                     // updating the signature; for now a defensive error is thrown
     82                     throw new IOException("A jar verifier does not support more than one entry with the same name");
     83                 }
     84                 chunks.put(entryNameValue, new Manifest.Chunk(mark, pos));
     85                 mark = pos;
     86             }
     87 
     88             entries.put(entryNameValue, entry);
     89         }
     90     }
     91 
     92     public int getEndOfMainSection() {
     93         return endOfMainSection;
     94     }
     95 
     96     /**
     97      * Read a single line from the manifest buffer.
     98      */
     99     private boolean readHeader() throws IOException {
    100         if (consecutiveLineBreaks > 1) {
    101             // break a section on an empty line
    102             consecutiveLineBreaks = 0;
    103             return false;
    104         }
    105         readName();
    106         consecutiveLineBreaks = 0;
    107         readValue();
    108         // if the last line break is missed, the line
    109         // is ignored by the reference implementation
    110         return consecutiveLineBreaks > 0;
    111     }
    112 
    113     private void readName() throws IOException {
    114         int mark = pos;
    115 
    116         while (pos < buf.length) {
    117             if (buf[pos++] != ':') {
    118                 continue;
    119             }
    120 
    121             String nameString = new String(buf, mark, pos - mark - 1, StandardCharsets.US_ASCII);
    122 
    123             if (buf[pos++] != ' ') {
    124                 throw new IOException(String.format("Invalid value for attribute '%s'", nameString));
    125             }
    126 
    127             try {
    128                 name = attributeNameCache.get(nameString);
    129                 if (name == null) {
    130                     name = new Attributes.Name(nameString);
    131                     attributeNameCache.put(nameString, name);
    132                 }
    133             } catch (IllegalArgumentException e) {
    134                 // new Attributes.Name() throws IllegalArgumentException but we declare IOException
    135                 throw new IOException(e.getMessage());
    136             }
    137             return;
    138         }
    139     }
    140 
    141     private void readValue() throws IOException {
    142         boolean lastCr = false;
    143         int mark = pos;
    144         int last = pos;
    145         valueBuffer.reset();
    146         while (pos < buf.length) {
    147             byte next = buf[pos++];
    148             switch (next) {
    149             case 0:
    150                 throw new IOException("NUL character in a manifest");
    151             case '\n':
    152                 if (lastCr) {
    153                     lastCr = false;
    154                 } else {
    155                     consecutiveLineBreaks++;
    156                 }
    157                 continue;
    158             case '\r':
    159                 lastCr = true;
    160                 consecutiveLineBreaks++;
    161                 continue;
    162             case ' ':
    163                 if (consecutiveLineBreaks == 1) {
    164                     valueBuffer.write(buf, mark, last - mark);
    165                     mark = pos;
    166                     consecutiveLineBreaks = 0;
    167                     continue;
    168                 }
    169             }
    170 
    171             if (consecutiveLineBreaks >= 1) {
    172                 pos--;
    173                 break;
    174             }
    175             last = pos;
    176         }
    177 
    178         valueBuffer.write(buf, mark, last - mark);
    179         // A bit frustrating that that Charset.forName will be called
    180         // again.
    181         value = valueBuffer.toString(StandardCharsets.UTF_8.name());
    182     }
    183 }
    184