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