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

com.squareup.okhttp.internal.http.RawHeaders Maven / Gradle / Ivy

There is a newer version: 2.7.5
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.squareup.okhttp.internal.http;

import com.squareup.okhttp.internal.Util;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;

/**
 * The HTTP status and unparsed header fields of a single HTTP message. Values
 * are represented as uninterpreted strings; use {@link RequestHeaders} and
 * {@link ResponseHeaders} for interpreted headers. This class maintains the
 * order of the header fields within the HTTP message.
 *
 * 

This class tracks fields 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. */ public final class RawHeaders { private static final Comparator FIELD_NAME_COMPARATOR = new Comparator() { // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ") @Override public int compare(String a, String b) { if (a == b) { return 0; } else if (a == null) { return -1; } else if (b == null) { return 1; } else { return String.CASE_INSENSITIVE_ORDER.compare(a, b); } } }; private final List namesAndValues = new ArrayList(20); private String requestLine; private String statusLine; private int httpMinorVersion = 1; private int responseCode = -1; private String responseMessage; public RawHeaders() { } public RawHeaders(RawHeaders copyFrom) { namesAndValues.addAll(copyFrom.namesAndValues); requestLine = copyFrom.requestLine; statusLine = copyFrom.statusLine; httpMinorVersion = copyFrom.httpMinorVersion; responseCode = copyFrom.responseCode; responseMessage = copyFrom.responseMessage; } /** Sets the request line (like "GET / HTTP/1.1"). */ public void setRequestLine(String requestLine) { requestLine = requestLine.trim(); this.requestLine = requestLine; } /** Sets the response status line (like "HTTP/1.0 200 OK"). */ public void setStatusLine(String statusLine) throws IOException { // H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 if (this.responseMessage != null) { throw new IllegalStateException("statusLine is already set"); } // We allow empty message without leading white space since some servers // do not send the white space when the message is empty. boolean hasMessage = statusLine.length() > 13; if (!statusLine.startsWith("HTTP/1.") || statusLine.length() < 12 || statusLine.charAt(8) != ' ' || (hasMessage && statusLine.charAt(12) != ' ')) { throw new ProtocolException("Unexpected status line: " + statusLine); } int httpMinorVersion = statusLine.charAt(7) - '0'; if (httpMinorVersion < 0 || httpMinorVersion > 9) { throw new ProtocolException("Unexpected status line: " + statusLine); } int responseCode; try { responseCode = Integer.parseInt(statusLine.substring(9, 12)); } catch (NumberFormatException e) { throw new ProtocolException("Unexpected status line: " + statusLine); } this.responseMessage = hasMessage ? statusLine.substring(13) : ""; this.responseCode = responseCode; this.statusLine = statusLine; this.httpMinorVersion = httpMinorVersion; } public void computeResponseStatusLineFromSpdyHeaders() throws IOException { String status = null; String version = null; for (int i = 0; i < namesAndValues.size(); i += 2) { String name = namesAndValues.get(i); if (":status".equals(name)) { status = namesAndValues.get(i + 1); } else if (":version".equals(name)) { version = namesAndValues.get(i + 1); } } if (status == null || version == null) { throw new ProtocolException("Expected ':status' and ':version' headers not present"); } setStatusLine(version + " " + status); } /** * @param method like "GET", "POST", "HEAD", etc. * @param path like "/foo/bar.html" * @param version like "HTTP/1.1" * @param host like "www.android.com:1234" * @param scheme like "https" */ public void addSpdyRequestHeaders(String method, String path, String version, String host, String scheme) { // TODO: populate the statusLine for the client's benefit? add(":method", method); add(":scheme", scheme); add(":path", path); add(":version", version); add(":host", host); } public String getStatusLine() { return statusLine; } /** * Returns the status line's HTTP minor version. This returns 0 for HTTP/1.0 * and 1 for HTTP/1.1. This returns 1 if the HTTP version is unknown. */ public int getHttpMinorVersion() { return httpMinorVersion != -1 ? httpMinorVersion : 1; } /** Returns the HTTP status code or -1 if it is unknown. */ public int getResponseCode() { return responseCode; } /** Returns the HTTP status message or null if it is unknown. */ public String getResponseMessage() { return responseMessage; } /** * Add an HTTP header line containing a field name, a literal colon, and a * value. */ public void addLine(String line) { int index = line.indexOf(":"); if (index == -1) { addLenient("", line); } else { addLenient(line.substring(0, index), line.substring(index + 1)); } } /** Add a field with the specified value. */ public void add(String fieldName, String value) { if (fieldName == null) throw new IllegalArgumentException("fieldname == null"); if (value == null) throw new IllegalArgumentException("value == null"); if (fieldName.length() == 0 || fieldName.indexOf('\0') != -1 || value.indexOf('\0') != -1) { throw new IllegalArgumentException("Unexpected header: " + fieldName + ": " + value); } addLenient(fieldName, value); } /** * Add a field with the specified value without any validation. Only * appropriate for headers from the remote peer. */ private void addLenient(String fieldName, String value) { namesAndValues.add(fieldName); namesAndValues.add(value.trim()); } public void removeAll(String fieldName) { for (int i = 0; i < namesAndValues.size(); i += 2) { if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) { namesAndValues.remove(i); // field name namesAndValues.remove(i); // value } } } public void addAll(String fieldName, List headerFields) { for (String value : headerFields) { add(fieldName, value); } } /** * 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 void set(String fieldName, String value) { removeAll(fieldName); add(fieldName, value); } /** Returns the number of field values. */ public int length() { return namesAndValues.size() / 2; } /** Returns the field at {@code position} or null if that is out of range. */ public String getFieldName(int index) { int fieldNameIndex = index * 2; if (fieldNameIndex < 0 || fieldNameIndex >= namesAndValues.size()) { return null; } return namesAndValues.get(fieldNameIndex); } /** Returns the value at {@code index} or null if that is out of range. */ public String getValue(int index) { int valueIndex = index * 2 + 1; if (valueIndex < 0 || valueIndex >= namesAndValues.size()) { return null; } return namesAndValues.get(valueIndex); } /** Returns the last value corresponding to the specified field, or null. */ public String get(String fieldName) { for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) { if (fieldName.equalsIgnoreCase(namesAndValues.get(i))) { return namesAndValues.get(i + 1); } } return null; } /** @param fieldNames a case-insensitive set of HTTP header field names. */ public RawHeaders getAll(Set fieldNames) { RawHeaders result = new RawHeaders(); for (int i = 0; i < namesAndValues.size(); i += 2) { String fieldName = namesAndValues.get(i); if (fieldNames.contains(fieldName)) { result.add(fieldName, namesAndValues.get(i + 1)); } } return result; } /** Returns bytes of a request header for sending on an HTTP transport. */ public byte[] toBytes() throws UnsupportedEncodingException { StringBuilder result = new StringBuilder(256); result.append(requestLine).append("\r\n"); for (int i = 0; i < namesAndValues.size(); i += 2) { result.append(namesAndValues.get(i)) .append(": ") .append(namesAndValues.get(i + 1)) .append("\r\n"); } result.append("\r\n"); return result.toString().getBytes("ISO-8859-1"); } /** Parses bytes of a response header from an HTTP transport. */ public static RawHeaders fromBytes(InputStream in) throws IOException { RawHeaders headers; do { headers = new RawHeaders(); headers.setStatusLine(Util.readAsciiLine(in)); readHeaders(in, headers); } while (headers.getResponseCode() == HttpEngine.HTTP_CONTINUE); return headers; } /** Reads headers or trailers into {@code out}. */ public static void readHeaders(InputStream in, RawHeaders out) throws IOException { // parse the result headers until the first blank line String line; while ((line = Util.readAsciiLine(in)).length() != 0) { out.addLine(line); } } /** * Returns an immutable map containing each field to its list of values. The * status line is mapped to null. */ public Map> toMultimap(boolean response) { Map> result = new TreeMap>(FIELD_NAME_COMPARATOR); for (int i = 0; i < namesAndValues.size(); i += 2) { String fieldName = namesAndValues.get(i); String value = namesAndValues.get(i + 1); List allValues = new ArrayList(); List otherValues = result.get(fieldName); if (otherValues != null) { allValues.addAll(otherValues); } allValues.add(value); result.put(fieldName, Collections.unmodifiableList(allValues)); } if (response && statusLine != null) { result.put(null, Collections.unmodifiableList(Collections.singletonList(statusLine))); } else if (requestLine != null) { result.put(null, Collections.unmodifiableList(Collections.singletonList(requestLine))); } return Collections.unmodifiableMap(result); } /** * Creates a new instance from the given map of fields to values. If * present, the null field's last element will be used to set the status * line. */ public static RawHeaders fromMultimap(Map> map, boolean response) throws IOException { if (!response) throw new UnsupportedOperationException(); RawHeaders result = new RawHeaders(); for (Entry> entry : map.entrySet()) { String fieldName = entry.getKey(); List values = entry.getValue(); if (fieldName != null) { for (String value : values) { result.addLenient(fieldName, value); } } else if (!values.isEmpty()) { result.setStatusLine(values.get(values.size() - 1)); } } return result; } /** * Returns a list of alternating names and values. Names are all lower case. * No names are repeated. If any name has multiple values, they are * concatenated using "\0" as a delimiter. */ public List toNameValueBlock() { Set names = new HashSet(); List result = new ArrayList(); for (int i = 0; i < namesAndValues.size(); i += 2) { String name = namesAndValues.get(i).toLowerCase(Locale.US); String value = namesAndValues.get(i + 1); // Drop headers that are forbidden when layering HTTP over SPDY. if (name.equals("connection") || name.equals("host") || name.equals("keep-alive") || name.equals("proxy-connection") || name.equals("transfer-encoding")) { continue; } // If we haven't seen this name before, add the pair to the end of the list... if (names.add(name)) { result.add(name); result.add(value); continue; } // ...otherwise concatenate the existing values and this value. for (int j = 0; j < result.size(); j += 2) { if (name.equals(result.get(j))) { result.set(j + 1, result.get(j + 1) + "\0" + value); break; } } } return result; } public static RawHeaders fromNameValueBlock(List nameValueBlock) { if (nameValueBlock.size() % 2 != 0) { throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock); } RawHeaders result = new RawHeaders(); for (int i = 0; i < nameValueBlock.size(); i += 2) { String name = nameValueBlock.get(i); String values = nameValueBlock.get(i + 1); for (int start = 0; start < values.length(); ) { int end = values.indexOf('\0', start); if (end == -1) { end = values.length(); } result.namesAndValues.add(name); result.namesAndValues.add(values.substring(start, end)); start = end + 1; } } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy