1 /*
   2  * Copyright (c) 2005, 2013, 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 com.sun.net.httpserver;
  27 
  28 import java.util.*;
  29 
  30 /**
  31  * HTTP request and response headers are represented by this class which implements
  32  * the interface
  33  * {@link java.util.Map}{@literal <}{@link java.lang.String}, {@link java.util.List}
  34  * {@literal <}{@link java.lang.String}{@literal >>}.
  35  * The keys are case-insensitive Strings representing the header names and
  36  * the value associated with each key is
  37  * a {@link List}{@literal <}{@link String}{@literal >} with one
  38  * element for each occurrence of the header name in the request or response.
  39  * <p>
  40  * For example, if a response header instance contains
  41  * one key "HeaderName" with two values "value1 and value2"
  42  * then this object is output as two header lines:
  43  * <blockquote><pre>
  44  * HeaderName: value1
  45  * HeaderName: value2
  46  * </pre></blockquote>
  47  * <p>
  48  * All the normal {@link java.util.Map} methods are provided, but the following
  49  * additional convenience methods are most likely to be used:
  50  * <ul>
  51  * <li>{@link #getFirst(String)} returns a single valued header or the first value of
  52  * a multi-valued header.</li>
  53  * <li>{@link #add(String,String)} adds the given header value to the list for the given key</li>
  54  * <li>{@link #set(String,String)} sets the given header field to the single value given
  55  * overwriting any existing values in the value list.
  56  * </ul><p>
  57  * All methods in this class accept <code>null</code> values for keys and values. However, null
  58  * keys will never will be present in HTTP request headers, and will not be output/sent in response headers.
  59  * Null values can be represented as either a null entry for the key (i.e. the list is null) or
  60  * where the key has a list, but one (or more) of the list's values is null. Null values are output
  61  * as a header line containing the key but no associated value.
  62  * @since 1.6
  63  */
  64 public class Headers implements Map<String,List<String>> {
  65 
  66         HashMap<String,List<String>> map;
  67 
  68         public Headers () {map = new HashMap<String,List<String>>(32);}
  69 
  70         /* Normalize the key by converting to following form.
  71          * First char upper case, rest lower case.
  72          * key is presumed to be ASCII
  73          */
  74         private String normalize (String key) {
  75             if (key == null) {
  76                 return null;
  77             }
  78             int len = key.length();
  79             if (len == 0) {
  80                 return key;
  81             }
  82             char[] b = key.toCharArray();
  83             if (b[0] >= 'a' && b[0] <= 'z') {
  84                 b[0] = (char)(b[0] - ('a' - 'A'));
  85             } else if (b[0] == '\r' || b[0] == '\n')
  86                 throw new IllegalArgumentException("illegal character in key");
  87 
  88             for (int i=1; i<len; i++) {
  89                 if (b[i] >= 'A' && b[i] <= 'Z') {
  90                     b[i] = (char) (b[i] + ('a' - 'A'));
  91                 } else if (b[i] == '\r' || b[i] == '\n')
  92                     throw new IllegalArgumentException("illegal character in key");
  93             }
  94             return new String(b);
  95         }
  96 
  97         public int size() {return map.size();}
  98 
  99         public boolean isEmpty() {return map.isEmpty();}
 100 
 101         public boolean containsKey(Object key) {
 102             if (key == null) {
 103                 return false;
 104             }
 105             if (!(key instanceof String)) {
 106                 return false;
 107             }
 108             return map.containsKey (normalize((String)key));
 109         }
 110 
 111         public boolean containsValue(Object value) {
 112             return map.containsValue(value);
 113         }
 114 
 115         public List<String> get(Object key) {
 116             return map.get(normalize((String)key));
 117         }
 118 
 119         /**
 120          * returns the first value from the List of String values
 121          * for the given key (if at least one exists).
 122          * @param key the key to search for
 123          * @return the first string value associated with the key
 124          */
 125         public String getFirst (String key) {
 126             List<String> l = map.get(normalize(key));
 127             if (l == null) {
 128                 return null;
 129             }
 130             return l.get(0);
 131         }
 132 
 133         public List<String> put(String key, List<String> value) {
 134             for (String v : value)
 135                 checkValue(v);
 136             return map.put (normalize(key), value);
 137         }
 138 
 139         /**
 140          * adds the given value to the list of headers
 141          * for the given key. If the mapping does not
 142          * already exist, then it is created
 143          * @param key the header name
 144          * @param value the header value to add to the header
 145          */
 146         public void add (String key, String value) {
 147             checkValue(value);
 148             String k = normalize(key);
 149             List<String> l = map.get(k);
 150             if (l == null) {
 151                 l = new LinkedList<String>();
 152                 map.put(k,l);
 153             }
 154             l.add (value);
 155         }
 156 
 157         private static void checkValue(String value) {
 158             int len = value.length();
 159             for (int i=0; i<len; i++) {
 160                 char c = value.charAt(i);
 161                 if (c == '\r') {
 162                     // is allowed if it is followed by \n and a whitespace char
 163                     if (i >= len - 2) {
 164                         throw new IllegalArgumentException("Illegal CR found in header");
 165                     }
 166                     char c1 = value.charAt(i+1);
 167                     char c2 = value.charAt(i+2);
 168                     if (c1 != '\n') {
 169                         throw new IllegalArgumentException("Illegal char found after CR in header");
 170                     }
 171                     if (c2 != ' ' && c2 != '\t') {
 172                         throw new IllegalArgumentException("No whitespace found after CRLF in header");
 173                     }
 174                     i+=2;
 175                 } else if (c == '\n') {
 176                     throw new IllegalArgumentException("Illegal LF found in header");
 177                 }
 178             }
 179         }
 180 
 181         /**
 182          * sets the given value as the sole header value
 183          * for the given key. If the mapping does not
 184          * already exist, then it is created
 185          * @param key the header name
 186          * @param value the header value to set.
 187          */
 188         public void set (String key, String value) {
 189             LinkedList<String> l = new LinkedList<String>();
 190             l.add (value);
 191             put (key, l);
 192         }
 193 
 194 
 195         public List<String> remove(Object key) {
 196             return map.remove(normalize((String)key));
 197         }
 198 
 199         public void putAll(Map<? extends String,? extends List<String>> t)  {
 200             map.putAll (t);
 201         }
 202 
 203         public void clear() {map.clear();}
 204 
 205         public Set<String> keySet() {return map.keySet();}
 206 
 207         public Collection<List<String>> values() {return map.values();}
 208 
 209         public Set<Map.Entry<String, List<String>>> entrySet() {
 210             return map.entrySet();
 211         }
 212 
 213         public boolean equals(Object o) {return map.equals(o);}
 214 
 215         public int hashCode() {return map.hashCode();}
 216     }