jdk/src/share/classes/sun/security/util/SignatureFileManifest.java
changeset 9391 5cad348bfb7b
parent 9390 76bb81c6327c
parent 9386 d54bd79072b0
child 9397 79de9afa367c
equal deleted inserted replaced
9390:76bb81c6327c 9391:5cad348bfb7b
     1 /*
       
     2  * Copyright (c) 2011, 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 sun.security.util;
       
    27 
       
    28 import java.io.IOException;
       
    29 import java.io.InputStream;
       
    30 import java.util.Arrays;
       
    31 import java.util.jar.Attributes;
       
    32 import java.util.jar.Manifest;
       
    33 
       
    34 /**
       
    35  * This class provides streaming mode reading of manifest files.
       
    36  * Used by {@link SignatureFileVerifier}.
       
    37  */
       
    38 class SignatureFileManifest extends Manifest {
       
    39 
       
    40     /*
       
    41      * Reading a manifest into this object by calling update(byte[]) on chunks.
       
    42      * During the reading, the bytes are saved in (@code current} until a line
       
    43      * is complete and the key-value pair is saved in {@code currentAttr}. When
       
    44      * a section is complete, {@code consumeAttr} is called to merge
       
    45      * {@code currentAttr} into main attributes or a named entry.
       
    46      */
       
    47 
       
    48     // Internal state during update() style reading
       
    49     // 0. not in update mode
       
    50     // 1, in update mode but main attributes not completed yet
       
    51     // 2. main attributes completed, still reading the entries
       
    52     private int state = 0;
       
    53 
       
    54     // The partial line read
       
    55     private byte[] current;
       
    56 
       
    57     // Number of bytes in current
       
    58     private int currentPos = 0;
       
    59 
       
    60     // The current Attribute
       
    61     private Attributes currentAttr;
       
    62 
       
    63     /**
       
    64      * Reads a manifest in chunks.
       
    65      * <p>
       
    66      * This method must be called in a row, reading chunks from a single
       
    67      * manifest file by order. After all chunks are read, caller must call
       
    68      * {@code update(null)} to fully consume the manifest.
       
    69      * <p>
       
    70      * The entry names and attributes read will be merged in with the current
       
    71      * manifest entries. The {@link #read} method cannot be called inside a
       
    72      * row of update calls.
       
    73      * <p>
       
    74      * Along with the calls, caller can call {@link #getMainAttributes()},
       
    75      * {@link #getAttributes(java.lang.String)} or {@link #getEntries()}
       
    76      * to get already available contents. However, in order not to return
       
    77      * partial result, when the main attributes in the new manifest is not
       
    78      * consumed completely, {@link #getMainAttributes()} throws an
       
    79      * {@code IllegalStateException}. When a certain named entry is not
       
    80      * consumed completely, {@link #getAttributes(java.lang.String)}
       
    81      * returns the old {@code Attributes} for the name (if it exists).
       
    82      *
       
    83      * @param data null for last call, otherwise, feeding chunks
       
    84      * @param offset offset into data to begin read
       
    85      * @param length length of data after offset to read
       
    86      * @exception IOException if an I/O error has occurred
       
    87      * @exception IllegalStateException if {@code update(null)} is called
       
    88      * without any previous {@code update(non-null)} call
       
    89      */
       
    90     public void update(byte[] data, int offset, int length) throws IOException {
       
    91 
       
    92         // The last call
       
    93         if (data == null) {
       
    94             if (state == 0) {
       
    95                 throw new IllegalStateException("No data to update");
       
    96             }
       
    97             // We accept manifest not ended with \n or \n\n
       
    98             if (hasLastByte()) {
       
    99                 consumeCurrent();
       
   100             }
       
   101             // We accept empty lines at the end
       
   102             if (!currentAttr.isEmpty()) {
       
   103                 consumeAttr();
       
   104             }
       
   105             state = 0;  // back to non-update state
       
   106             current = null;
       
   107             currentAttr = null;
       
   108             return;
       
   109         }
       
   110 
       
   111         // The first call
       
   112         if (state == 0) {
       
   113             current = new byte[1024];
       
   114             currentAttr = super.getMainAttributes(); // the main attribute
       
   115             state = 1;
       
   116         }
       
   117 
       
   118         int end = offset + length;
       
   119 
       
   120         while (offset < end) {
       
   121             switch (data[offset]) {
       
   122                 case '\r':
       
   123                     break;  // always skip
       
   124                 case '\n':
       
   125                     if (hasLastByte() && lastByte() == '\n') {  // new section
       
   126                         consumeCurrent();
       
   127                         consumeAttr();
       
   128                         if (state == 1) {
       
   129                             state = 2;
       
   130                         }
       
   131                         currentAttr = new Attributes(2);
       
   132                     } else {
       
   133                         if (hasLastByte()) {
       
   134                             // save \n into current but do not parse,
       
   135                             // there might be a continuation later
       
   136                             ensureCapacity();
       
   137                             current[currentPos++] = data[offset];
       
   138                         } else if (state == 1) {
       
   139                             // there can be multiple empty lines between
       
   140                             // sections, but cannot be at the beginning
       
   141                             throw new IOException("invalid manifest format");
       
   142                         }
       
   143                     }
       
   144                     break;
       
   145                 case ' ':
       
   146                     if (!hasLastByte()) {
       
   147                         throw new IOException("invalid manifest format");
       
   148                     } else if (lastByte() == '\n') {
       
   149                         currentPos--;   // continuation, remove last \n
       
   150                     } else {    // a very normal ' '
       
   151                         ensureCapacity();
       
   152                         current[currentPos++] = data[offset];
       
   153                     }
       
   154                     break;
       
   155                 default:
       
   156                     if (hasLastByte() && lastByte() == '\n') {
       
   157                         // The start of a new pair, not continuation
       
   158                         consumeCurrent();   // the last line read
       
   159                     }
       
   160                     ensureCapacity();
       
   161                     current[currentPos++] = data[offset];
       
   162                     break;
       
   163             }
       
   164             offset++;
       
   165         }
       
   166     }
       
   167 
       
   168     /**
       
   169      * Returns the main Attributes for the Manifest.
       
   170      * @exception IllegalStateException the main attributes is being read
       
   171      * @return the main Attributes for the Manifest
       
   172      */
       
   173     public Attributes getMainAttributes() {
       
   174         if (state == 1) {
       
   175             throw new IllegalStateException();
       
   176         }
       
   177         return super.getMainAttributes();
       
   178     }
       
   179 
       
   180     /**
       
   181      * Reads the Manifest from the specified InputStream. The entry
       
   182      * names and attributes read will be merged in with the current
       
   183      * manifest entries.
       
   184      *
       
   185      * @param is the input stream
       
   186      * @exception IOException if an I/O error has occurred
       
   187      * @exception IllegalStateException if called between two {@link #update}
       
   188      * calls
       
   189      */
       
   190     public void read(InputStream is) throws IOException {
       
   191         if (state != 0) {
       
   192             throw new IllegalStateException("Cannot call read between updates");
       
   193         }
       
   194         super.read(is);
       
   195     }
       
   196 
       
   197     /*
       
   198      * ----------  Helper methods  -----------------
       
   199      */
       
   200 
       
   201     private void ensureCapacity() {
       
   202         if (currentPos >= current.length-1) {
       
   203             current = Arrays.copyOf(current, current.length*2);
       
   204         }
       
   205     }
       
   206 
       
   207     private boolean hasLastByte() {
       
   208         return currentPos > 0;
       
   209     }
       
   210 
       
   211     private byte lastByte() {
       
   212         return current[currentPos-1];
       
   213     }
       
   214 
       
   215     // Parse current as key:value and save into currentAttr.
       
   216     // There MUST be something inside current.
       
   217     private void consumeCurrent() throws IOException {
       
   218         // current normally has a \n end, except for the last line
       
   219         if (current[currentPos-1] == '\n') currentPos--;
       
   220         for (int i=0; i<currentPos; i++) {
       
   221             if (current[i] == ':') {
       
   222                 String key = new String(current, 0, 0, i);
       
   223                 i++;
       
   224                 while (i < currentPos && current[i] == ' ') { i++; }
       
   225                 String value = new String(current, i, currentPos-i, "UTF-8");
       
   226                 currentAttr.putValue(key, value);
       
   227                 currentPos = 0;
       
   228                 return;
       
   229             }
       
   230         }
       
   231         throw new IOException("invalid header field");
       
   232     }
       
   233 
       
   234     // Merge currentAttr into Manifest
       
   235     private void consumeAttr() throws IOException {
       
   236         // Only needed for named entries. For the main attribute, key/value
       
   237         // is added into attr directly, but since getMainAttributes() throws
       
   238         // an exception, the partial data is not leaked.
       
   239         if (state != 1) {
       
   240             String name = currentAttr.getValue("Name");
       
   241             if (name != null) {
       
   242                 currentAttr.remove(new Attributes.Name("Name"));
       
   243                 Attributes old = getAttributes(name);
       
   244                 if (old != null) old.putAll(currentAttr);
       
   245                 else getEntries().put(name, currentAttr);
       
   246             } else {
       
   247                 throw new IOException("invalid manifest format");
       
   248             }
       
   249         }
       
   250     }
       
   251 }