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 } |
|