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