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