1package com.squareup.okhttp.internal.http;
2
3import com.squareup.okhttp.Headers;
4import com.squareup.okhttp.Request;
5import com.squareup.okhttp.Response;
6import com.squareup.okhttp.internal.Platform;
7import java.util.ArrayList;
8import java.util.Collections;
9import java.util.Comparator;
10import java.util.List;
11import java.util.Map;
12import java.util.TreeMap;
13
14/** Headers and utilities for internal use by OkHttp. */
15public final class OkHeaders {
16  private static final Comparator<String> FIELD_NAME_COMPARATOR = new Comparator<String>() {
17    // @FindBugsSuppressWarnings("ES_COMPARING_PARAMETER_STRING_WITH_EQ")
18    @Override public int compare(String a, String b) {
19      if (a == b) {
20        return 0;
21      } else if (a == null) {
22        return -1;
23      } else if (b == null) {
24        return 1;
25      } else {
26        return String.CASE_INSENSITIVE_ORDER.compare(a, b);
27      }
28    }
29  };
30
31  static final String PREFIX = Platform.get().getPrefix();
32
33  /**
34   * Synthetic response header: the local time when the request was sent.
35   */
36  public static final String SENT_MILLIS = PREFIX + "-Sent-Millis";
37
38  /**
39   * Synthetic response header: the local time when the response was received.
40   */
41  public static final String RECEIVED_MILLIS = PREFIX + "-Received-Millis";
42
43  /**
44   * Synthetic response header: the response source and status code like
45   * "CONDITIONAL_CACHE 304".
46   */
47  public static final String RESPONSE_SOURCE = PREFIX + "-Response-Source";
48
49  /**
50   * Synthetic response header: the selected
51   * {@link com.squareup.okhttp.Protocol protocol} ("spdy/3.1", "http/1.1", etc).
52   */
53  public static final String SELECTED_PROTOCOL = PREFIX + "-Selected-Protocol";
54
55  private OkHeaders() {
56  }
57
58  public static long contentLength(Request request) {
59    return contentLength(request.headers());
60  }
61
62  public static long contentLength(Response response) {
63    return contentLength(response.headers());
64  }
65
66  public static long contentLength(Headers headers) {
67    return stringToLong(headers.get("Content-Length"));
68  }
69
70  private static long stringToLong(String s) {
71    if (s == null) return -1;
72    try {
73      return Long.parseLong(s);
74    } catch (NumberFormatException e) {
75      return -1;
76    }
77  }
78
79  /**
80   * Returns an immutable map containing each field to its list of values.
81   *
82   * @param valueForNullKey the request line for requests, or the status line
83   *     for responses. If non-null, this value is mapped to the null key.
84   */
85  public static Map<String, List<String>> toMultimap(Headers headers, String valueForNullKey) {
86    Map<String, List<String>> result = new TreeMap<String, List<String>>(FIELD_NAME_COMPARATOR);
87    for (int i = 0; i < headers.size(); i++) {
88      String fieldName = headers.name(i);
89      String value = headers.value(i);
90
91      List<String> allValues = new ArrayList<String>();
92      List<String> otherValues = result.get(fieldName);
93      if (otherValues != null) {
94        allValues.addAll(otherValues);
95      }
96      allValues.add(value);
97      result.put(fieldName, Collections.unmodifiableList(allValues));
98    }
99    if (valueForNullKey != null) {
100      result.put(null, Collections.unmodifiableList(Collections.singletonList(valueForNullKey)));
101    }
102    return Collections.unmodifiableMap(result);
103  }
104
105  public static void addCookies(Request.Builder builder, Map<String, List<String>> cookieHeaders) {
106    for (Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) {
107      String key = entry.getKey();
108      if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
109          && !entry.getValue().isEmpty()) {
110        builder.addHeader(key, buildCookieHeader(entry.getValue()));
111      }
112    }
113  }
114
115  /**
116   * Send all cookies in one big header, as recommended by
117   * <a href="http://tools.ietf.org/html/rfc6265#section-4.2.1">RFC 6265</a>.
118   */
119  private static String buildCookieHeader(List<String> cookies) {
120    if (cookies.size() == 1) return cookies.get(0);
121    StringBuilder sb = new StringBuilder();
122    for (int i = 0; i < cookies.size(); i++) {
123      if (i > 0) sb.append("; ");
124      sb.append(cookies.get(i));
125    }
126    return sb.toString();
127  }
128}
129