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 }