< prev index next >
1 /*
2 * Copyright (c) 2015, 2016, 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 */
24 package java.net.http;
25
26 import java.io.IOException;
27 import java.io.UnsupportedEncodingException;
28 import java.nio.ByteBuffer;
29 import java.nio.charset.Charset;
30 import java.nio.charset.StandardCharsets;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Locale;
37 import java.util.Map;
38 import java.util.Optional;
39 import java.util.Set;
40
41 /**
42 * Reads response headers off channel, in blocking mode. Entire header
43 * block is collected in a byte[]. The offset location of the start of
44 * each header name is recorded in an array to facilitate later searching.
45 *
46 * The location of "Content-length" is recorded explicitly. Similar approach
47 * could be taken for other common headers.
48 *
49 * This class is not thread-safe
50 */
51 class ResponseHeaders implements HttpHeaders1 {
52
53 static final int DATA_SIZE = 16 * 1024; // initial space for headers
54 static final int NUM_HEADERS = 50; // initial expected max number of headers
55
56 final HttpConnection connection;
57 byte[] data;
58 int contentlen = -2; // means not initialized
59 ByteBuffer buffer;
60
61 /**
62 * Following used for scanning the array looking for:
63 * - well known headers
64 * - end of header block
65 */
66 int[] headerOffsets; // index into data
67 int numHeaders;
68 int count;
69
70 ByteBuffer residue; // after headers processed, data may be here
71
72 ResponseHeaders(HttpConnection connection, ByteBuffer buffer) {
73 this.connection = connection;
74 initOffsets();
75 this.buffer = buffer;
76 data = new byte[DATA_SIZE];
77 }
78
79 int getContentLength() throws IOException {
80 if (contentlen != -2) {
81 return contentlen;
82 }
83 int[] search = findHeaderValue("Content-length");
84 if (search[0] == -1) {
85 contentlen = -1;
86 return -1;
87 }
88
89 int i = search[0];
90
91 while (data[i] == ' ' || data[i] == '\t') {
92 i++;
93 if (i == data.length || data[i] == CR || data[i] == LF) {
94 throw new IOException("Bad header");
95 }
96 }
97 contentlen = 0;
98 int digit = data[i++] - 0x30;
99 while (digit >= 0 && digit <= 9) {
100 contentlen = contentlen * 10 + digit;
101 digit = data[i++] - 0x30;
102 }
103 return contentlen;
104 }
105
106 void log() {
107 populateMap(false);
108 }
109
110 void populateMap(boolean clearOffsets) {
111 StringBuilder sb;
112
113 for (int i = 0; i < numHeaders; i++) {
114 sb = new StringBuilder(32);
115 int offset = headerOffsets[i];
116 if (offset == -1) {
117 continue;
118 }
119 int j;
120 for (j=0; data[offset+j] != ':'; j++) {
121 // byte to char promotion ok for US-ASCII
122 sb.append((char)data[offset+j]);
123 }
124 String name = sb.toString();
125 List<String> l = getOrCreate(name);
126 addEntry(l, name, offset + j + 1);
127 // clear the offset
128 if (clearOffsets)
129 headerOffsets[i] = -1;
130 }
131 }
132
133 void addEntry(List<String> l, String name, int j) {
134
135 while (data[j] == ' ' || data[j] == '\t') {
136 j++;
137 }
138
139 int vstart = j;
140 // TODO: back slash ??
141
142 while (data[j] != CR) {
143 j++;
144 }
145 try {
146 String value = new String(data, vstart, j - vstart, "US-ASCII");
147 l.add(value);
148 } catch (UnsupportedEncodingException e) {
149 // can't happen
150 throw new InternalError(e);
151 }
152 }
153
154 // returns an int[2]: [0] = offset of value in data[]
155 // [1] = offset in headerOffsets. Both are -1 in error
156
157 private int[] findHeaderValue(String name) {
158 int[] result = new int[2];
159 byte[] namebytes = getBytes(name);
160
161 outer: for (int i = 0; i < numHeaders; i++) {
162 int offset = headerOffsets[i];
163 if (offset == -1) {
164 continue;
165 }
166
167 for (int j=0; j<namebytes.length; j++) {
168 if (namebytes[j] != lowerCase(data[offset+j])) {
169 continue outer;
170 }
171 }
172 // next char must be ':'
173 if (data[offset+namebytes.length] != ':') {
174 continue;
175 }
176 result[0] = offset+namebytes.length + 1;
177 result[1] = i;
178 return result;
179 }
180 result[0] = -1;
181 result[1] = -1;
182 return result;
183 }
184
185 /**
186 * Populates the map for header values with the given name.
187 * The offsets are cleared for any that are found, so they don't
188 * get repeatedly searched.
189 */
190 List<String> populateMapEntry(String name) {
191 List<String> l = getOrCreate(name);
192 int[] search = findHeaderValue(name);
193 if (search[0] != -1) {
194 addEntry(l, name, search[0]);
195 // clear the offset
196 headerOffsets[search[1]] = -1;
197 }
198 return l;
199 }
200
201 static final Locale usLocale = Locale.US;
202 static final Charset ascii = StandardCharsets.US_ASCII;
203
204 private byte[] getBytes(String name) {
205 return name.toLowerCase(usLocale).getBytes(ascii);
206 }
207
208 /*
209 * We read buffers in a loop until we detect end of headers
210 * CRLFCRLF. Each byte received is copied into the byte[] data
211 * The position of the first byte of each header (after a CRLF)
212 * is recorded in a separate array showing the location of
213 * each header name.
214 */
215 void initHeaders() throws IOException {
216
217 inHeaderName = true;
218 endOfHeader = true;
219
220 for (int numBuffers = 0; true; numBuffers++) {
221
222 if (numBuffers > 0) {
223 buffer = connection.read();
224 }
225
226 if (buffer == null) {
227 throw new IOException("Error reading headers");
228 }
229
230 if (!buffer.hasRemaining()) {
231 continue;
232 }
233
234 // Position set to first byte
235 int start = buffer.position();
236 byte[] backing = buffer.array();
237 int len = buffer.limit() - start;
238
239 for (int i = 0; i < len; i++) {
240 byte b = backing[i + start];
241 if (inHeaderName) {
242 b = lowerCase(b);
243 }
244 if (b == ':') {
245 inHeaderName = false;
246 }
247 data[count++] = b;
248 checkByte(b);
249 if (firstChar) {
250 recordHeaderOffset(count-1);
251 firstChar = false;
252 }
253 if (endOfHeader && numHeaders == 0) {
254 // empty headers
255 endOfAllHeaders = true;
256 }
257 if (endOfAllHeaders) {
258 int newposition = i + 1 + start;
259 if (newposition <= buffer.limit()) {
260 buffer.position(newposition);
261 residue = buffer;
262 } else {
263 residue = null;
264 }
265 return;
266 }
267
268 if (count == data.length) {
269 resizeData();
270 }
271 }
272 }
273 }
274
275 static final int CR = 13;
276 static final int LF = 10;
277 int crlfCount = 0;
278
279 // results of checkByte()
280 boolean endOfHeader; // just seen LF after CR before
281 boolean endOfAllHeaders; // just seen LF after CRLFCR before
282 boolean firstChar; //
283 boolean inHeaderName; // examining header name
284
285 void checkByte(byte b) throws IOException {
286 if (endOfHeader && b != CR && b != LF)
287 firstChar = true;
288 endOfHeader = false;
289 endOfAllHeaders = false;
290 switch (crlfCount) {
291 case 0:
292 crlfCount = b == CR ? 1 : 0;
293 break;
294 case 1:
295 crlfCount = b == LF ? 2 : 0;
296 endOfHeader = true;
297 inHeaderName = true;
298 break;
299 case 2:
300 crlfCount = b == CR ? 3 : 0;
301 break;
302 case 3:
303 if (b != LF) {
304 throw new IOException("Bad header block termination");
305 }
306 endOfAllHeaders = true;
307 break;
308 }
309 }
310
311 byte lowerCase(byte b) {
312 if (b >= 0x41 && b <= 0x5A)
313 b = (byte)(b + 32);
314 return b;
315 }
316
317 void resizeData() {
318 int oldlen = data.length;
319 int newlen = oldlen * 2;
320 byte[] newdata = new byte[newlen];
321 System.arraycopy(data, 0, newdata, 0, oldlen);
322 data = newdata;
323 }
324
325 final void initOffsets() {
326 headerOffsets = new int[NUM_HEADERS];
327 numHeaders = 0;
328 }
329
330 ByteBuffer getResidue() {
331 return residue;
332 }
333
334 void recordHeaderOffset(int index) {
335 if (numHeaders >= headerOffsets.length) {
336 int oldlen = headerOffsets.length;
337 int newlen = oldlen * 2;
338 int[] new1 = new int[newlen];
339 System.arraycopy(headerOffsets, 0, new1, 0, oldlen);
340 headerOffsets = new1;
341 }
342 headerOffsets[numHeaders++] = index;
343 }
344
345 /**
346 * As entries are read from the byte[] they are placed in here
347 * So we always check this map first
348 */
349 Map<String,List<String>> headers = new HashMap<>();
350
351 @Override
352 public Optional<String> firstValue(String name) {
353 List<String> l = allValues(name);
354 if (l == null || l.isEmpty()) {
355 return Optional.ofNullable(null);
356 } else {
357 return Optional.of(l.get(0));
358 }
359 }
360
361 @Override
362 public List<String> allValues(String name) {
363 name = name.toLowerCase(usLocale);
364 List<String> l = headers.get(name);
365 if (l == null) {
366 l = populateMapEntry(name);
367 }
368 return Collections.unmodifiableList(l);
369 }
370
371 @Override
372 public void makeUnmodifiable() {
373 }
374
375 // Delegates map to HashMap but converts keys to lower case
376
377 static class HeaderMap implements Map<String,List<String>> {
378 Map<String,List<String>> inner;
379
380 HeaderMap(Map<String,List<String>> inner) {
381 this.inner = inner;
382 }
383 @Override
384 public int size() {
385 return inner.size();
386 }
387
388 @Override
389 public boolean isEmpty() {
390 return inner.isEmpty();
391 }
392
393 @Override
394 public boolean containsKey(Object key) {
395 if (!(key instanceof String)) {
396 return false;
397 }
398 String s = ((String)key).toLowerCase(usLocale);
399 return inner.containsKey(s);
400 }
401
402 @Override
403 public boolean containsValue(Object value) {
404 return inner.containsValue(value);
405 }
406
407 @Override
408 public List<String> get(Object key) {
409 String s = ((String)key).toLowerCase(usLocale);
410 return inner.get(s);
411 }
412
413 @Override
414 public List<String> put(String key, List<String> value) {
415 throw new UnsupportedOperationException("Not supported");
416 }
417
418 @Override
419 public List<String> remove(Object key) {
420 throw new UnsupportedOperationException("Not supported");
421 }
422
423 @Override
424 public void putAll(Map<? extends String, ? extends List<String>> m) {
425 throw new UnsupportedOperationException("Not supported");
426 }
427
428 @Override
429 public void clear() {
430 throw new UnsupportedOperationException("Not supported");
431 }
432
433 @Override
434 public Set<String> keySet() {
435 return inner.keySet();
436 }
437
438 @Override
439 public Collection<List<String>> values() {
440 return inner.values();
441 }
442
443 @Override
444 public Set<Entry<String, List<String>>> entrySet() {
445 return inner.entrySet();
446 }
447 }
448
449 @Override
450 public Map<String, List<String>> map() {
451 populateMap(true);
452 return new HeaderMap(headers);
453 }
454
455 Map<String, List<String>> mapInternal() {
456 populateMap(false);
457 return new HeaderMap(headers);
458 }
459
460 private List<String> getOrCreate(String name) {
461 List<String> l = headers.get(name);
462 if (l == null) {
463 l = new LinkedList<>();
464 headers.put(name, l);
465 }
466 return l;
467 }
468
469 @Override
470 public Optional<Long> firstValueAsLong(String name) {
471 List<String> l = allValues(name);
472 if (l == null) {
473 return Optional.ofNullable(null);
474 } else {
475 String v = l.get(0);
476 Long lv = Long.parseLong(v);
477 return Optional.of(lv);
478 }
479 }
480 }
< prev index next >