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 HttpHeaders { 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 while (search[0] != -1) { 194 addEntry(l, name, search[0]); 195 // clear the offset 196 headerOffsets[search[1]] = -1; 197 search = findHeaderValue(name); 198 } 199 return l; 200 } 201 202 static final Locale usLocale = Locale.US; 203 static final Charset ascii = StandardCharsets.US_ASCII; 204 205 private byte[] getBytes(String name) { 206 return name.toLowerCase(usLocale).getBytes(ascii); 207 } 208 209 /* 210 * We read buffers in a loop until we detect end of headers 211 * CRLFCRLF. Each byte received is copied into the byte[] data 212 * The position of the first byte of each header (after a CRLF) 213 * is recorded in a separate array showing the location of 214 * each header name. 215 */ 216 void initHeaders() throws IOException { 217 218 inHeaderName = true; 219 endOfHeader = true; 220 221 for (int numBuffers = 0; true; numBuffers++) { 222 223 if (numBuffers > 0) { 224 buffer = connection.read(); 225 } 226 227 if (buffer == null) { 228 throw new IOException("Error reading headers"); 229 } 230 231 if (!buffer.hasRemaining()) { 232 continue; 233 } 234 235 // Position set to first byte 236 int start = buffer.position(); 237 byte[] backing = buffer.array(); 238 int len = buffer.limit() - start; 239 240 for (int i = 0; i < len; i++) { 241 byte b = backing[i + start]; 242 if (inHeaderName) { 243 b = lowerCase(b); 244 } 245 if (b == ':') { 246 inHeaderName = false; 247 } 248 data[count++] = b; 249 checkByte(b); 250 if (firstChar) { 251 recordHeaderOffset(count-1); 252 firstChar = false; 253 } 254 if (endOfHeader && numHeaders == 0) { 255 // empty headers 256 endOfAllHeaders = true; 257 } 258 if (endOfAllHeaders) { 259 int newposition = i + 1 + start; 260 if (newposition <= buffer.limit()) { 261 buffer.position(newposition); 262 residue = buffer; 263 } else { 264 residue = null; 265 } 266 return; 267 } 268 269 if (count == data.length) { 270 resizeData(); 271 } 272 } 273 } 274 } 275 276 static final int CR = 13; 277 static final int LF = 10; 278 int crlfCount = 0; 279 280 // results of checkByte() 281 boolean endOfHeader; // just seen LF after CR before 282 boolean endOfAllHeaders; // just seen LF after CRLFCR before 283 boolean firstChar; // 284 boolean inHeaderName; // examining header name 285 286 void checkByte(byte b) throws IOException { 287 if (endOfHeader && b != CR && b != LF) 288 firstChar = true; 289 endOfHeader = false; 290 endOfAllHeaders = false; 291 switch (crlfCount) { 292 case 0: 293 crlfCount = b == CR ? 1 : 0; 294 break; 295 case 1: 296 crlfCount = b == LF ? 2 : 0; 297 endOfHeader = true; 298 inHeaderName = true; 299 break; 300 case 2: 301 crlfCount = b == CR ? 3 : 0; 302 break; 303 case 3: 304 if (b != LF) { 305 throw new IOException("Bad header block termination"); 306 } 307 endOfAllHeaders = true; 308 break; 309 } 310 } 311 312 byte lowerCase(byte b) { 313 if (b >= 0x41 && b <= 0x5A) 314 b = (byte)(b + 32); 315 return b; 316 } 317 318 void resizeData() { 319 int oldlen = data.length; 320 int newlen = oldlen * 2; 321 byte[] newdata = new byte[newlen]; 322 System.arraycopy(data, 0, newdata, 0, oldlen); 323 data = newdata; 324 } 325 326 final void initOffsets() { 327 headerOffsets = new int[NUM_HEADERS]; 328 numHeaders = 0; 329 } 330 331 ByteBuffer getResidue() { 332 return residue; 333 } 334 335 void recordHeaderOffset(int index) { 336 if (numHeaders >= headerOffsets.length) { 337 int oldlen = headerOffsets.length; 338 int newlen = oldlen * 2; 339 int[] new1 = new int[newlen]; 340 System.arraycopy(headerOffsets, 0, new1, 0, oldlen); 341 headerOffsets = new1; 342 } 343 headerOffsets[numHeaders++] = index; 344 } 345 346 /** 347 * As entries are read from the byte[] they are placed in here 348 * So we always check this map first 349 */ 350 Map<String,List<String>> headers = new HashMap<>(); 351 352 @Override 353 public Optional<String> firstValue(String name) { 354 List<String> l = allValues(name); 355 if (l == null || l.isEmpty()) { 356 return Optional.ofNullable(null); 357 } else { 358 return Optional.of(l.get(0)); 359 } 360 } 361 362 @Override 363 public List<String> allValues(String name) { 364 name = name.toLowerCase(usLocale); 365 List<String> l = headers.get(name); 366 if (l == null) { 367 l = populateMapEntry(name); 368 } 369 return Collections.unmodifiableList(l); 370 } 371 372 // Delegates map to HashMap but converts keys to lower case 373 374 static class HeaderMap implements Map<String,List<String>> { 375 Map<String,List<String>> inner; 376 377 HeaderMap(Map<String,List<String>> inner) { 378 this.inner = inner; 379 } 380 @Override 381 public int size() { 382 return inner.size(); 383 } 384 385 @Override 386 public boolean isEmpty() { 387 return inner.isEmpty(); 388 } 389 390 @Override 391 public boolean containsKey(Object key) { 392 if (!(key instanceof String)) { 393 return false; 394 } 395 String s = ((String)key).toLowerCase(usLocale); 396 return inner.containsKey(s); 397 } 398 399 @Override 400 public boolean containsValue(Object value) { 401 return inner.containsValue(value); 402 } 403 404 @Override 405 public List<String> get(Object key) { 406 String s = ((String)key).toLowerCase(usLocale); 407 return inner.get(s); 408 } 409 410 @Override 411 public List<String> put(String key, List<String> value) { 412 throw new UnsupportedOperationException("Not supported"); 413 } 414 415 @Override 416 public List<String> remove(Object key) { 417 throw new UnsupportedOperationException("Not supported"); 418 } 419 420 @Override 421 public void putAll(Map<? extends String, ? extends List<String>> m) { 422 throw new UnsupportedOperationException("Not supported"); 423 } 424 425 @Override 426 public void clear() { 427 throw new UnsupportedOperationException("Not supported"); 428 } 429 430 @Override 431 public Set<String> keySet() { 432 return inner.keySet(); 433 } 434 435 @Override 436 public Collection<List<String>> values() { 437 return inner.values(); 438 } 439 440 @Override 441 public Set<Entry<String, List<String>>> entrySet() { 442 return inner.entrySet(); 443 } 444 } 445 446 @Override 447 public Map<String, List<String>> map() { 448 populateMap(true); 449 return new HeaderMap(headers); 450 } 451 452 Map<String, List<String>> mapInternal() { 453 populateMap(false); 454 return new HeaderMap(headers); 455 } 456 457 private List<String> getOrCreate(String name) { 458 List<String> l = headers.get(name); 459 if (l == null) { 460 l = new LinkedList<>(); 461 headers.put(name, l); 462 } 463 return l; 464 } 465 466 @Override 467 public Optional<Long> firstValueAsLong(String name) { 468 List<String> l = allValues(name); 469 if (l == null) { 470 return Optional.ofNullable(null); 471 } else { 472 String v = l.get(0); 473 Long lv = Long.parseLong(v); 474 return Optional.of(lv); 475 } 476 } 477 }