| /* | |
| * 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)); | |
| } | |
| } |