blob: 279203d1d5ba34ab65a2e2a031c996189d5b5ea7 [file]
/*
* Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.net.httpserver;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import sun.net.httpserver.UnmodifiableHeaders;
import sun.net.httpserver.Utils;
/**
* HTTP request and response headers are represented by this class which
* implements the interface
* {@link java.util.Map}{@literal <}{@link java.lang.String},
* {@link java.util.List} {@literal <}{@link java.lang.String}{@literal >>}.
* The keys are case-insensitive Strings representing the header names and
* the value associated with each key is
* a {@link List}{@literal <}{@link String}{@literal >} with one
* element for each occurrence of the header name in the request or response.
*
* <p> For example, if a response header instance contains
* one key "HeaderName" with two values "value1 and value2"
* then this object is output as two header lines:
*
* <blockquote><pre>
* HeaderName: value1
* HeaderName: value2
* </pre></blockquote>
*
* All the normal {@link java.util.Map} methods are provided, but the
* following additional convenience methods are most likely to be used:
*
* <ul>
* <li>{@link #getFirst(String)} returns a single valued header or the first
* value of a multi-valued header.
* <li>{@link #add(String,String)} adds the given header value to the list
* for the given key.
* <li>{@link #set(String,String)} sets the given header field to the single
* value given overwriting any existing values in the value list.
* </ul>
*
* <p> An instance of {@code Headers} is either <i>mutable</i> or <i>immutable</i>.
* A <i>mutable headers</i> allows to add, remove, or modify header names and
* values, e.g. the instance returned by {@link HttpExchange#getResponseHeaders()}.
* An <i>immutable headers</i> disallows any modification to header names or
* values, e.g. the instance returned by {@link HttpExchange#getRequestHeaders()}.
* The mutator methods for an immutable headers instance unconditionally throw
* {@code UnsupportedOperationException}.
*
* <p> All methods in this class reject {@code null} values for keys and values.
* {@code null} keys will never be present in HTTP request or response headers.
* @since 1.6
*/
public class Headers implements Map<String,List<String>> {
HashMap<String,List<String>> map;
/**
* Creates an empty instance of {@code Headers}.
*/
public Headers() {map = new HashMap<>(32);}
/**
* Creates a mutable {@code Headers} from the given {@code headers} with
* the same header names and values.
*
* @param headers a map of header names and values
* @throws NullPointerException if {@code headers} or any of its names or
* values are null, or if any value contains
* null.
* @since 18
*/
public Headers(Map<String,List<String>> headers) {
Objects.requireNonNull(headers);
var h = headers.entrySet().stream()
.collect(Collectors.toUnmodifiableMap(
Entry::getKey, e -> new LinkedList<>(e.getValue())));
map = new HashMap<>(32);
this.putAll(h);
}
/**
* Normalize the key by converting to following form.
* First {@code char} upper case, rest lower case.
* key is presumed to be {@code ASCII}.
*/
private String normalize(String key) {
Objects.requireNonNull(key);
int len = key.length();
if (len == 0) {
return key;
}
char[] b = key.toCharArray();
if (b[0] >= 'a' && b[0] <= 'z') {
b[0] = (char)(b[0] - ('a' - 'A'));
} else if (b[0] == '\r' || b[0] == '\n')
throw new IllegalArgumentException("illegal character in key");
for (int i=1; i<len; i++) {
if (b[i] >= 'A' && b[i] <= 'Z') {
b[i] = (char) (b[i] + ('a' - 'A'));
} else if (b[i] == '\r' || b[i] == '\n')
throw new IllegalArgumentException("illegal character in key");
}
return new String(b);
}
@Override
public int size() {return map.size();}
@Override
public boolean isEmpty() {return map.isEmpty();}
@Override
public boolean containsKey(Object key) {
Objects.requireNonNull(key);
return key instanceof String k && map.containsKey(normalize(k));
}
@Override
public boolean containsValue(Object value) {
Objects.requireNonNull(value);
return map.containsValue(value);
}
@Override
public List<String> get(Object key) {
return map.get(normalize((String)key));
}
/**
* Returns the first value from the {@link List} of {@code String} values
* for the given {@code key}, or {@code null} if no mapping for the
* {@code key} exists.
*
* @param key the key to search for
* @return the first {@code String} value associated with the key,
* or {@code null} if no mapping for the key exists
*/
public String getFirst(String key) {
List<String> l = map.get(normalize(key));
if (l == null || l.size() == 0) { // no mapping exists
return null;
}
return l.get(0);
}
@Override
public List<String> put(String key, List<String> value) {
// checkHeader is called in this class to fail fast
// It also must be called in sendResponseHeaders because
// Headers instances internal state can be modified
// external to these methods.
Utils.checkHeader(key, false);
for (String v : value)
Utils.checkHeader(v, true);
return map.put(normalize(key), value);
}
/**
* Adds the given {@code value} to the list of headers for the given
* {@code key}. If the mapping does not already exist, then it is created.
*
* @param key the header name
* @param value the value to add to the header
*/
public void add(String key, String value) {
Utils.checkHeader(key, false);
Utils.checkHeader(value, true);
String k = normalize(key);
List<String> l = map.get(k);
if (l == null) {
l = new LinkedList<>();
map.put(k,l);
}
l.add(value);
}
/**
* Sets the given {@code value} as the sole header value for the given
* {@code key}. If the mapping does not already exist, then it is created.
*
* @param key the header name
* @param value the header value to set
*/
public void set(String key, String value) {
LinkedList<String> l = new LinkedList<>();
l.add(value);
put(key, l);
}
@Override
public List<String> remove(Object key) {
return map.remove(normalize((String)key));
}
@Override
public void putAll(Map<? extends String,? extends List<String>> t) {
t.forEach(this::put);
}
@Override
public void clear() {map.clear();}
@Override
public Set<String> keySet() {return map.keySet();}
@Override
public Collection<List<String>> values() {return map.values();}
@Override
public Set<Map.Entry<String, List<String>>> entrySet() {
return map.entrySet();
}
@Override
public void replaceAll(BiFunction<? super String, ? super List<String>, ? extends List<String>> function) {
var f = function.andThen(values -> {
Objects.requireNonNull(values);
values.forEach(value -> Utils.checkHeader(value, true));
return values;
});
Map.super.replaceAll(f);
}
@Override
public boolean equals(Object o) { return map.equals(o); }
@Override
public int hashCode() {return map.hashCode();}
@Override
public String toString() {
final var sb = new StringBuilder(Headers.class.getSimpleName());
sb.append(" { ");
sb.append(map.toString());
sb.append(" }");
return sb.toString();
}
/**
* Returns an immutable {@code Headers} with the given name value pairs as
* its set of headers.
*
* <p> The supplied {@code String} instances must alternate as header names
* and header values. To add several values to the same name, the same name
* must be supplied with each new value. If the supplied {@code headers} is
* empty, then an empty {@code Headers} is returned.
*
* @param headers the list of name value pairs
* @return an immutable headers with the given name value pairs
* @throws NullPointerException if {@code headers} or any of its
* elements are null.
* @throws IllegalArgumentException if the number of supplied strings is odd.
* @since 18
*/
public static Headers of(String... headers) {
Objects.requireNonNull(headers);
if (headers.length == 0) {
return new UnmodifiableHeaders(new Headers());
}
if (headers.length % 2 != 0) {
throw new IllegalArgumentException("wrong number, %d, of elements"
.formatted(headers.length));
}
Arrays.stream(headers).forEach(Objects::requireNonNull);
var h = new Headers();
for (int i = 0; i < headers.length; i += 2) {
String name = headers[i];
String value = headers[i + 1];
h.add(name, value);
}
return new UnmodifiableHeaders(h);
}
/**
* Returns an immutable {@code Headers} from the given {@code headers} with
* the same header names and values.
*
* @param headers a map of header names and values
* @return an immutable headers
* @throws NullPointerException if {@code headers} or any of its names or
* values are null, or if any value contains
* null.
* @since 18
*/
public static Headers of(Map<String,List<String>> headers) {
return new UnmodifiableHeaders(new Headers(headers));
}
}