1
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
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
64 public @Nullable String get(String name) {
65 return get(namesAndValues, name);
66 }
67
68
72 public @Nullable Date getDate(String name) {
73 String value = get(name);
74 return value != null ? HttpDate.parse(value) : null;
75 }
76
77
81 @IgnoreJRERequirement
82 public @Nullable Instant getInstant(String name) {
83 Date value = getDate(name);
84 return value != null ? value.toInstant() : null;
85 }
86
87
88 public int size() {
89 return namesAndValues.length / 2;
90 }
91
92
93 public String name(int index) {
94 return namesAndValues[index * 2];
95 }
96
97
98 public String value(int index) {
99 return namesAndValues[index * 2 + 1];
100 }
101
102
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
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
130 public long byteCount() {
131
132
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
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
218 public static Headers of(String... namesAndValues) {
219 if (namesAndValues == null) throw new NullPointerException("namesAndValues == null");
220 if (namesAndValues.length % 2 != 0) {
221 throw new IllegalArgumentException("Expected alternating header names and values");
222 }
223
224
225 namesAndValues = namesAndValues.clone();
226 for (int i = 0; i < namesAndValues.length; i++) {
227 if (namesAndValues[i] == null) throw new IllegalArgumentException("Headers cannot be null");
228 namesAndValues[i] = namesAndValues[i].trim();
229 }
230
231
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
245 public static Headers of(Map<String, String> headers) {
246 if (headers == null) throw new NullPointerException("headers == null");
247
248
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 == null) throw 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 == null) throw 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
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
303
304 return addLenient("", line.substring(1));
305 } else {
306 return addLenient("", line);
307 }
308 }
309
310
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
322 public Builder add(String name, String value) {
323 checkName(name);
324 checkValue(value, name);
325 return addLenient(name, value);
326 }
327
328
332 public Builder addUnsafeNonAscii(String name, String value) {
333 checkName(name);
334 return addLenient(name, value);
335 }
336
337
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
352 public Builder add(String name, Date value) {
353 if (value == null) throw new NullPointerException("value for name " + name + " == null");
354 add(name, HttpDate.format(value));
355 return this;
356 }
357
358
362 @IgnoreJRERequirement
363 public Builder add(String name, Instant value) {
364 if (value == null) throw new NullPointerException("value for name " + name + " == null");
365 return add(name, new Date(value.toEpochMilli()));
366 }
367
368
372 public Builder set(String name, Date value) {
373 if (value == null) throw new NullPointerException("value for name " + name + " == null");
374 set(name, HttpDate.format(value));
375 return this;
376 }
377
378
382 @IgnoreJRERequirement
383 public Builder set(String name, Instant value) {
384 if (value == null) throw new NullPointerException("value for name " + name + " == null");
385 return set(name, new Date(value.toEpochMilli()));
386 }
387
388
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);
402 namesAndValues.remove(i);
403 i -= 2;
404 }
405 }
406 return this;
407 }
408
409
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
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