All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.facebook.presto.jdbc.internal.okhttp3.Headers Maven / Gradle / Ivy

There is a newer version: 0.289
Show newest version
/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.facebook.presto.jdbc.internal.okhttp3;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import com.facebook.presto.jdbc.internal.javax.annotation.Nullable;
import com.facebook.presto.jdbc.internal.okhttp3.internal.Util;
import com.facebook.presto.jdbc.internal.okhttp3.internal.http.HttpDate;

/**
 * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request}
 * and {@code Response} for interpreted headers. This class maintains the order of the header fields
 * within the HTTP message.
 *
 * 

This class tracks header values line-by-line. A field with multiple comma- separated values on * the same line will be treated as a field with a single value by this class. It is the caller's * responsibility to detect and split on commas if their field permits multiple values. This * simplifies use of single-valued fields whose values routinely contain commas, such as cookies or * dates. * *

This class trims whitespace from values. It never returns values with leading or trailing * whitespace. * *

Instances of this class are immutable. Use {@link Builder} to create instances. */ public final class Headers { private final String[] namesAndValues; Headers(Builder builder) { this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]); } private Headers(String[] namesAndValues) { this.namesAndValues = namesAndValues; } /** Returns the last value corresponding to the specified field, or null. */ public @Nullable String get(String name) { return get(namesAndValues, name); } /** * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if * either the field is absent or cannot be parsed as a date. */ public @Nullable Date getDate(String name) { String value = get(name); return value != null ? HttpDate.parse(value) : null; } /** Returns the number of field values. */ public int size() { return namesAndValues.length / 2; } /** Returns the field at {@code position}. */ public String name(int index) { return namesAndValues[index * 2]; } /** Returns the value at {@code index}. */ public String value(int index) { return namesAndValues[index * 2 + 1]; } /** Returns an immutable case-insensitive set of header names. */ public Set names() { TreeSet result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); for (int i = 0, size = size(); i < size; i++) { result.add(name(i)); } return Collections.unmodifiableSet(result); } /** Returns an immutable list of the header values for {@code name}. */ public List values(String name) { List result = null; for (int i = 0, size = size(); i < size; i++) { if (name.equalsIgnoreCase(name(i))) { if (result == null) result = new ArrayList<>(2); result.add(value(i)); } } return result != null ? Collections.unmodifiableList(result) : Collections.emptyList(); } /** * Returns the number of bytes required to encode these headers using HTTP/1.1. This is also the * approximate size of HTTP/2 headers before they are compressed with HPACK. This value is * intended to be used as a metric: smaller headers are more efficient to encode and transmit. */ public long byteCount() { // Each header name has 2 bytes of overhead for ': ' and every header value has 2 bytes of // overhead for '\r\n'. long result = namesAndValues.length * 2; for (int i = 0, size = namesAndValues.length; i < size; i++) { result += namesAndValues[i].length(); } return result; } public Builder newBuilder() { Builder result = new Builder(); Collections.addAll(result.namesAndValues, namesAndValues); return result; } /** * Returns true if {@code other} is a {@code Headers} object with the same headers, with the same * casing, in the same order. Note that two headers instances may be semantically equal * but not equal according to this method. In particular, none of the following sets of headers * are equal according to this method:

   {@code
   *
   *   1. Original
   *   Content-Type: text/html
   *   Content-Length: 50
   *
   *   2. Different order
   *   Content-Length: 50
   *   Content-Type: text/html
   *
   *   3. Different case
   *   content-type: text/html
   *   content-length: 50
   *
   *   4. Different values
   *   Content-Type: text/html
   *   Content-Length: 050
   * }
* * Applications that require semantically equal headers should convert them into a canonical form * before comparing them for equality. */ @Override public boolean equals(@Nullable Object other) { return other instanceof Headers && Arrays.equals(((Headers) other).namesAndValues, namesAndValues); } @Override public int hashCode() { return Arrays.hashCode(namesAndValues); } @Override public String toString() { StringBuilder result = new StringBuilder(); for (int i = 0, size = size(); i < size; i++) { result.append(name(i)).append(": ").append(value(i)).append("\n"); } return result.toString(); } public Map> toMultimap() { Map> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); for (int i = 0, size = size(); i < size; i++) { String name = name(i).toLowerCase(Locale.US); List values = result.get(name); if (values == null) { values = new ArrayList<>(2); result.put(name, values); } values.add(value(i)); } return result; } private static String get(String[] namesAndValues, String name) { for (int i = namesAndValues.length - 2; i >= 0; i -= 2) { if (name.equalsIgnoreCase(namesAndValues[i])) { return namesAndValues[i + 1]; } } return null; } /** * Returns headers for the alternating header names and values. There must be an even number of * arguments, and they must alternate between header names and values. */ public static Headers of(String... namesAndValues) { if (namesAndValues == null) throw new NullPointerException("namesAndValues == null"); if (namesAndValues.length % 2 != 0) { throw new IllegalArgumentException("Expected alternating header names and values"); } // Make a defensive copy and clean it up. namesAndValues = namesAndValues.clone(); for (int i = 0; i < namesAndValues.length; i++) { if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null"); namesAndValues[i] = namesAndValues[i].trim(); } // Check for malformed headers. for (int i = 0; i < namesAndValues.length; i += 2) { String name = namesAndValues[i]; String value = namesAndValues[i + 1]; if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) { throw new IllegalArgumentException("Unexpected header: " + name + ": " + value); } } return new Headers(namesAndValues); } /** * Returns headers for the header names and values in the {@link Map}. */ public static Headers of(Map headers) { if (headers == null) throw new NullPointerException("headers == null"); // Make a defensive copy and clean it up. String[] namesAndValues = new String[headers.size() * 2]; int i = 0; for (Map.Entry header : headers.entrySet()) { if (header.getKey() == null || header.getValue() == null) { throw new IllegalArgumentException("Headers cannot be null"); } String name = header.getKey().trim(); String value = header.getValue().trim(); if (name.length() == 0 || name.indexOf('\0') != -1 || value.indexOf('\0') != -1) { throw new IllegalArgumentException("Unexpected header: " + name + ": " + value); } namesAndValues[i] = name; namesAndValues[i + 1] = value; i += 2; } return new Headers(namesAndValues); } public static final class Builder { final List namesAndValues = new ArrayList<>(20); /** * Add a header line without any validation. Only appropriate for headers from the remote peer * or cache. */ Builder addLenient(String line) { int index = line.indexOf(":", 1); if (index != -1) { return addLenient(line.substring(0, index), line.substring(index + 1)); } else if (line.startsWith(":")) { // Work around empty header names and header names that start with a // colon (created by old broken SPDY versions of the response cache). return addLenient("", line.substring(1)); // Empty header name. } else { return addLenient("", line); // No header name. } } /** Add an header line containing a field name, a literal colon, and a value. */ public Builder add(String line) { int index = line.indexOf(":"); if (index == -1) { throw new IllegalArgumentException("Unexpected header: " + line); } return add(line.substring(0, index).trim(), line.substring(index + 1)); } /** Add a field with the specified value. */ public Builder add(String name, String value) { checkNameAndValue(name, value); return addLenient(name, value); } /** * Add a field with the specified value without any validation. Only appropriate for headers * from the remote peer or cache. */ Builder addLenient(String name, String value) { namesAndValues.add(name); namesAndValues.add(value.trim()); return this; } public Builder removeAll(String name) { for (int i = 0; i < namesAndValues.size(); i += 2) { if (name.equalsIgnoreCase(namesAndValues.get(i))) { namesAndValues.remove(i); // name namesAndValues.remove(i); // value i -= 2; } } return this; } /** * Set a field with the specified value. If the field is not found, it is added. If the field is * found, the existing values are replaced. */ public Builder set(String name, String value) { checkNameAndValue(name, value); removeAll(name); addLenient(name, value); return this; } private void checkNameAndValue(String name, String value) { if (name == null) throw new NullPointerException("name == null"); if (name.isEmpty()) throw new IllegalArgumentException("name is empty"); for (int i = 0, length = name.length(); i < length; i++) { char c = name.charAt(i); if (c <= '\u0020' || c >= '\u007f') { throw new IllegalArgumentException(Util.format( "Unexpected char %#04x at %d in header name: %s", (int) c, i, name)); } } if (value == null) throw new NullPointerException("value for name " + name + " == null"); for (int i = 0, length = value.length(); i < length; i++) { char c = value.charAt(i); if ((c <= '\u001f' && c != '\t') || c >= '\u007f') { throw new IllegalArgumentException(Util.format( "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value)); } } } /** Equivalent to {@code build().get(name)}, but potentially faster. */ public String get(String name) { for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { if (name.equalsIgnoreCase(namesAndValues.get(i))) { return namesAndValues.get(i + 1); } } return null; } public Headers build() { return new Headers(this); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy