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

17
18 package okhttp3;
19
20 import java.time.Instant;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.TreeMap;
30 import java.util.TreeSet;
31 import javax.annotation.Nullable;
32 import okhttp3.internal.Util;
33 import okhttp3.internal.http.HttpDate;
34 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
35
36 /**
37  * The header fields of a single HTTP message. Values are uninterpreted strings; use {@code Request}
38  * and {@code Response} for interpreted headers. This class maintains the order of the header fields
39  * within the HTTP message.
40  *
41  * <p>This class tracks header values line-by-line. A field with multiple comma- separated values on
42  * the same line will be treated as a field with a single value by this class. It is the caller's
43  * responsibility to detect and split on commas if their field permits multiple values. This
44  * simplifies use of single-valued fields whose values routinely contain commas, such as cookies or
45  * dates.
46  *
47  * <p>This class trims whitespace from values. It never returns values with leading or trailing
48  * whitespace.
49  *
50  * <p>Instances of this class are immutable. Use {@link Builder} to create instances.
51  */

52 public final class Headers {
53   private final String[] namesAndValues;
54
55   Headers(Builder builder) {
56     this.namesAndValues = builder.namesAndValues.toArray(new String[builder.namesAndValues.size()]);
57   }
58
59   private Headers(String[] namesAndValues) {
60     this.namesAndValues = namesAndValues;
61   }
62
63   /** Returns the last value corresponding to the specified field, or null. */
64   public @Nullable String get(String name) {
65     return get(namesAndValues, name);
66   }
67
68   /**
69    * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if
70    * either the field is absent or cannot be parsed as a date.
71    */

72   public @Nullable Date getDate(String name) {
73     String value = get(name);
74     return value != null ? HttpDate.parse(value) : null;
75   }
76
77   /**
78    * Returns the last value corresponding to the specified field parsed as an HTTP date, or null if
79    * either the field is absent or cannot be parsed as a date.
80    */

81   @IgnoreJRERequirement
82   public @Nullable Instant getInstant(String name) {
83     Date value = getDate(name);
84     return value != null ? value.toInstant() : null;
85   }
86
87   /** Returns the number of field values. */
88   public int size() {
89     return namesAndValues.length / 2;
90   }
91
92   /** Returns the field at {@code position}. */
93   public String name(int index) {
94     return namesAndValues[index * 2];
95   }
96
97   /** Returns the value at {@code index}. */
98   public String value(int index) {
99     return namesAndValues[index * 2 + 1];
100   }
101
102   /** Returns an immutable case-insensitive set of header names. */
103   public Set<String> names() {
104     TreeSet<String> result = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
105     for (int i = 0, size = size(); i < size; i++) {
106       result.add(name(i));
107     }
108     return Collections.unmodifiableSet(result);
109   }
110
111   /** Returns an immutable list of the header values for {@code name}. */
112   public List<String> values(String name) {
113     List<String> result = null;
114     for (int i = 0, size = size(); i < size; i++) {
115       if (name.equalsIgnoreCase(name(i))) {
116         if (result == null) result = new ArrayList<>(2);
117         result.add(value(i));
118       }
119     }
120     return result != null
121         ? Collections.unmodifiableList(result)
122         : Collections.emptyList();
123   }
124
125   /**
126    * Returns the number of bytes required to encode these headers using HTTP/1.1. This is also the
127    * approximate size of HTTP/2 headers before they are compressed with HPACK. This value is
128    * intended to be used as a metric: smaller headers are more efficient to encode and transmit.
129    */

130   public long byteCount() {
131     // Each header name has 2 bytes of overhead for ': ' and every header value has 2 bytes of
132     // overhead for '\r\n'.
133     long result = namesAndValues.length * 2;
134
135     for (int i = 0, size = namesAndValues.length; i < size; i++) {
136       result += namesAndValues[i].length();
137     }
138
139     return result;
140   }
141
142   public Builder newBuilder() {
143     Builder result = new Builder();
144     Collections.addAll(result.namesAndValues, namesAndValues);
145     return result;
146   }
147
148   /**
149    * Returns true if {@code other} is a {@code Headers} object with the same headers, with the same
150    * casing, in the same order. Note that two headers instances may be <i>semantically</i> equal
151    * but not equal according to this method. In particular, none of the following sets of headers
152    * are equal according to this method: <pre>   {@code
153    *
154    *   1. Original
155    *   Content-Type: text/html
156    *   Content-Length: 50
157    *
158    *   2. Different order
159    *   Content-Length: 50
160    *   Content-Type: text/html
161    *
162    *   3. Different case
163    *   content-type: text/html
164    *   content-length: 50
165    *
166    *   4. Different values
167    *   Content-Type: text/html
168    *   Content-Length: 050
169    * }</pre>
170    *
171    * Applications that require semantically equal headers should convert them into a canonical form
172    * before comparing them for equality.
173    */

174   @Override public boolean equals(@Nullable Object other) {
175     return other instanceof Headers
176         && Arrays.equals(((Headers) other).namesAndValues, namesAndValues);
177   }
178
179   @Override public int hashCode() {
180     return Arrays.hashCode(namesAndValues);
181   }
182
183   @Override public String toString() {
184     StringBuilder result = new StringBuilder();
185     for (int i = 0, size = size(); i < size; i++) {
186       result.append(name(i)).append(": ").append(value(i)).append("\n");
187     }
188     return result.toString();
189   }
190
191   public Map<String, List<String>> toMultimap() {
192     Map<String, List<String>> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
193     for (int i = 0, size = size(); i < size; i++) {
194       String name = name(i).toLowerCase(Locale.US);
195       List<String> values = result.get(name);
196       if (values == null) {
197         values = new ArrayList<>(2);
198         result.put(name, values);
199       }
200       values.add(value(i));
201     }
202     return result;
203   }
204
205   private static @Nullable String get(String[] namesAndValues, String name) {
206     for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
207       if (name.equalsIgnoreCase(namesAndValues[i])) {
208         return namesAndValues[i + 1];
209       }
210     }
211     return null;
212   }
213
214   /**
215    * Returns headers for the alternating header names and values. There must be an even number of
216    * arguments, and they must alternate between header names and values.
217    */

218   public static Headers of(String... namesAndValues) {
219     if (namesAndValues == nullthrow new NullPointerException("namesAndValues == null");
220     if (namesAndValues.length % 2 != 0) {
221       throw new IllegalArgumentException("Expected alternating header names and values");
222     }
223
224     // Make a defensive copy and clean it up.
225     namesAndValues = namesAndValues.clone();
226     for (int i = 0; i < namesAndValues.length; i++) {
227       if (namesAndValues[i] == nullthrow new IllegalArgumentException("Headers cannot be null");
228       namesAndValues[i] = namesAndValues[i].trim();
229     }
230
231     // Check for malformed headers.
232     for (int i = 0; i < namesAndValues.length; i += 2) {
233       String name = namesAndValues[i];
234       String value = namesAndValues[i + 1];
235       checkName(name);
236       checkValue(value, name);
237     }
238
239     return new Headers(namesAndValues);
240   }
241
242   /**
243    * Returns headers for the header names and values in the {@link Map}.
244    */

245   public static Headers of(Map<String, String> headers) {
246     if (headers == nullthrow new NullPointerException("headers == null");
247
248     // Make a defensive copy and clean it up.
249     String[] namesAndValues = new String[headers.size() * 2];
250     int i = 0;
251     for (Map.Entry<String, String> header : headers.entrySet()) {
252       if (header.getKey() == null || header.getValue() == null) {
253         throw new IllegalArgumentException("Headers cannot be null");
254       }
255       String name = header.getKey().trim();
256       String value = header.getValue().trim();
257       checkName(name);
258       checkValue(value, name);
259       namesAndValues[i] = name;
260       namesAndValues[i + 1] = value;
261       i += 2;
262     }
263
264     return new Headers(namesAndValues);
265   }
266
267   static void checkName(String name) {
268     if (name == nullthrow new NullPointerException("name == null");
269     if (name.isEmpty()) throw new IllegalArgumentException("name is empty");
270     for (int i = 0, length = name.length(); i < length; i++) {
271       char c = name.charAt(i);
272       if (c <= '\u0020' || c >= '\u007f') {
273         throw new IllegalArgumentException(Util.format(
274             "Unexpected char %#04x at %d in header name: %s", (int) c, i, name));
275       }
276     }
277   }
278
279   static void checkValue(String value, String name) {
280     if (value == nullthrow new NullPointerException("value for name " + name + " == null");
281     for (int i = 0, length = value.length(); i < length; i++) {
282       char c = value.charAt(i);
283       if ((c <= '\u001f' && c != '\t') || c >= '\u007f') {
284         throw new IllegalArgumentException(Util.format(
285             "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value));
286       }
287     }
288   }
289
290   public static final class Builder {
291     final List<String> namesAndValues = new ArrayList<>(20);
292
293     /**
294      * Add a header line without any validation. Only appropriate for headers from the remote peer
295      * or cache.
296      */

297     Builder addLenient(String line) {
298       int index = line.indexOf(":", 1);
299       if (index != -1) {
300         return addLenient(line.substring(0, index), line.substring(index + 1));
301       } else if (line.startsWith(":")) {
302         // Work around empty header names and header names that start with a
303         // colon (created by old broken SPDY versions of the response cache).
304         return addLenient("", line.substring(1)); // Empty header name.
305       } else {
306         return addLenient("", line); // No header name.
307       }
308     }
309
310     /** Add an header line containing a field name, a literal colon, and a value. */
311     public Builder add(String line) {
312       int index = line.indexOf(":");
313       if (index == -1) {
314         throw new IllegalArgumentException("Unexpected header: " + line);
315       }
316       return add(line.substring(0, index).trim(), line.substring(index + 1));
317     }
318
319     /**
320      * Add a header with the specified name and value. Does validation of header names and values.
321      */

322     public Builder add(String name, String value) {
323       checkName(name);
324       checkValue(value, name);
325       return addLenient(name, value);
326     }
327
328     /**
329      * Add a header with the specified name and value. Does validation of header names, allowing
330      * non-ASCII values.
331      */

332     public Builder addUnsafeNonAscii(String name, String value) {
333       checkName(name);
334       return addLenient(name, value);
335     }
336
337     /**
338      * Adds all headers from an existing collection.
339      */

340     public Builder addAll(Headers headers) {
341       for (int i = 0, size = headers.size(); i < size; i++) {
342         addLenient(headers.name(i), headers.value(i));
343       }
344
345       return this;
346     }
347
348     /**
349      * Add a header with the specified name and formatted date. Does validation of header names and
350      * value.
351      */

352     public Builder add(String name, Date value) {
353       if (value == nullthrow new NullPointerException("value for name " + name + " == null");
354       add(name, HttpDate.format(value));
355       return this;
356     }
357
358     /**
359      * Add a header with the specified name and formatted instant. Does validation of header names
360      * and value.
361      */

362     @IgnoreJRERequirement
363     public Builder add(String name, Instant value) {
364       if (value == nullthrow new NullPointerException("value for name " + name + " == null");
365       return add(name, new Date(value.toEpochMilli()));
366     }
367
368     /**
369      * Set a field with the specified date. If the field is not found, it is added. If the field is
370      * found, the existing values are replaced.
371      */

372     public Builder set(String name, Date value) {
373       if (value == nullthrow new NullPointerException("value for name " + name + " == null");
374       set(name, HttpDate.format(value));
375       return this;
376     }
377
378     /**
379      * Set a field with the specified instant. If the field is not found, it is added. If the field
380      * is found, the existing values are replaced.
381      */

382     @IgnoreJRERequirement
383     public Builder set(String name, Instant value) {
384       if (value == nullthrow new NullPointerException("value for name " + name + " == null");
385       return set(name, new Date(value.toEpochMilli()));
386     }
387
388     /**
389      * Add a field with the specified value without any validation. Only appropriate for headers
390      * from the remote peer or cache.
391      */

392     Builder addLenient(String name, String value) {
393       namesAndValues.add(name);
394       namesAndValues.add(value.trim());
395       return this;
396     }
397
398     public Builder removeAll(String name) {
399       for (int i = 0; i < namesAndValues.size(); i += 2) {
400         if (name.equalsIgnoreCase(namesAndValues.get(i))) {
401           namesAndValues.remove(i); // name
402           namesAndValues.remove(i); // value
403           i -= 2;
404         }
405       }
406       return this;
407     }
408
409     /**
410      * Set a field with the specified value. If the field is not found, it is added. If the field is
411      * found, the existing values are replaced.
412      */

413     public Builder set(String name, String value) {
414       checkName(name);
415       checkValue(value, name);
416       removeAll(name);
417       addLenient(name, value);
418       return this;
419     }
420
421     /** Equivalent to {@code build().get(name)}, but potentially faster. */
422     public @Nullable String get(String name) {
423       for (int i = namesAndValues.size() - 2; i >= 0; i -= 2) {
424         if (name.equalsIgnoreCase(namesAndValues.get(i))) {
425           return namesAndValues.get(i + 1);
426         }
427       }
428       return null;
429     }
430
431     public Headers build() {
432       return new Headers(this);
433     }
434   }
435 }
436