1 /*
2  * Copyright (C) 2012 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package okhttp3.internal;
17
18 import java.io.Closeable;
19 import java.io.IOException;
20 import java.io.InterruptedIOException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.net.IDN;
24 import java.net.InetAddress;
25 import java.net.ServerSocket;
26 import java.net.Socket;
27 import java.net.UnknownHostException;
28 import java.nio.charset.Charset;
29 import java.security.AccessControlException;
30 import java.security.GeneralSecurityException;
31 import java.security.KeyStore;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.Comparator;
36 import java.util.LinkedHashMap;
37 import java.util.List;
38 import java.util.Locale;
39 import java.util.Map;
40 import java.util.TimeZone;
41 import java.util.concurrent.ThreadFactory;
42 import java.util.concurrent.TimeUnit;
43 import java.util.regex.Pattern;
44 import javax.annotation.Nullable;
45 import javax.net.ssl.TrustManager;
46 import javax.net.ssl.TrustManagerFactory;
47 import javax.net.ssl.X509TrustManager;
48 import okhttp3.Headers;
49 import okhttp3.HttpUrl;
50 import okhttp3.RequestBody;
51 import okhttp3.ResponseBody;
52 import okhttp3.internal.http2.Header;
53 import okio.Buffer;
54 import okio.BufferedSource;
55 import okio.ByteString;
56 import okio.Options;
57 import okio.Source;
58
59 import static java.nio.charset.StandardCharsets.UTF_16BE;
60 import static java.nio.charset.StandardCharsets.UTF_16LE;
61 import static java.nio.charset.StandardCharsets.UTF_8;
62
63 /** Junk drawer of utility methods. */
64 public final class Util {
65   public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
66   public static final String[] EMPTY_STRING_ARRAY = new String[0];
67   public static final Headers EMPTY_HEADERS = Headers.of();
68
69   public static final ResponseBody EMPTY_RESPONSE = ResponseBody.create(null, EMPTY_BYTE_ARRAY);
70   public static final RequestBody EMPTY_REQUEST = RequestBody.create(null, EMPTY_BYTE_ARRAY);
71
72   /** Byte order marks. */
73   private static final Options UNICODE_BOMS = Options.of(
74       ByteString.decodeHex("efbbbf"),   // UTF-8
75       ByteString.decodeHex("feff"),     // UTF-16BE
76       ByteString.decodeHex("fffe"),     // UTF-16LE
77       ByteString.decodeHex("0000ffff"), // UTF-32BE
78       ByteString.decodeHex("ffff0000")  // UTF-32LE
79   );
80
81   private static final Charset UTF_32BE = Charset.forName("UTF-32BE");
82   private static final Charset UTF_32LE = Charset.forName("UTF-32LE");
83
84   /** GMT and UTC are equivalent for our purposes. */
85   public static final TimeZone UTC = TimeZone.getTimeZone("GMT");
86
87   public static final Comparator<String> NATURAL_ORDER = String::compareTo;
88
89   private static final Method addSuppressedExceptionMethod;
90
91   static {
92     Method m;
93     try {
94       m = Throwable.class.getDeclaredMethod("addSuppressed", Throwable.class);
95     } catch (Exception e) {
96       m = null;
97     }
98     addSuppressedExceptionMethod = m;
99   }
100
101   public static void addSuppressedIfPossible(Throwable e, Throwable suppressed) {
102     if (addSuppressedExceptionMethod != null) {
103       try {
104         addSuppressedExceptionMethod.invoke(e, suppressed);
105       } catch (InvocationTargetException | IllegalAccessException ignored) {
106       }
107     }
108   }
109
110   /**
111    * Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
112    * of Android's private InetAddress#isNumeric API.
113    *
114    * <p>This matches IPv6 addresses as a hex string containing at least one colon, and possibly
115    * including dots after the first colon. It matches IPv4 addresses as strings containing only
116    * decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
117    * addresses nor hostnames; they will be verified as IP addresses (which is a more strict
118    * verification).
119    */

120   private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile(
121       "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
122
123   private Util() {
124   }
125
126   public static void checkOffsetAndCount(long arrayLength, long offset, long count) {
127     if ((offset | count) < 0 || offset > arrayLength || arrayLength - offset < count) {
128       throw new ArrayIndexOutOfBoundsException();
129     }
130   }
131
132   /**
133    * Closes {@code closeable}, ignoring any checked exceptions. Does nothing if {@code closeable} is
134    * null.
135    */

136   public static void closeQuietly(Closeable closeable) {
137     if (closeable != null) {
138       try {
139         closeable.close();
140       } catch (RuntimeException rethrown) {
141         throw rethrown;
142       } catch (Exception ignored) {
143       }
144     }
145   }
146
147   /**
148    * Closes {@code socket}, ignoring any checked exceptions. Does nothing if {@code socket} is
149    * null.
150    */

151   public static void closeQuietly(Socket socket) {
152     if (socket != null) {
153       try {
154         socket.close();
155       } catch (AssertionError e) {
156         if (!isAndroidGetsocknameError(e)) throw e;
157       } catch (RuntimeException rethrown) {
158         throw rethrown;
159       } catch (Exception ignored) {
160       }
161     }
162   }
163
164   /**
165    * Closes {@code serverSocket}, ignoring any checked exceptions. Does nothing if {@code
166    * serverSocket} is null.
167    */

168   public static void closeQuietly(ServerSocket serverSocket) {
169     if (serverSocket != null) {
170       try {
171         serverSocket.close();
172       } catch (RuntimeException rethrown) {
173         throw rethrown;
174       } catch (Exception ignored) {
175       }
176     }
177   }
178
179   /**
180    * Attempts to exhaust {@code source}, returning true if successful. This is useful when reading a
181    * complete source is helpful, such as when doing so completes a cache body or frees a socket
182    * connection for reuse.
183    */

184   public static boolean discard(Source source, int timeout, TimeUnit timeUnit) {
185     try {
186       return skipAll(source, timeout, timeUnit);
187     } catch (IOException e) {
188       return false;
189     }
190   }
191
192   /**
193    * Reads until {@code in} is exhausted or the deadline has been reached. This is careful to not
194    * extend the deadline if one exists already.
195    */

196   public static boolean skipAll(Source source, int duration, TimeUnit timeUnit) throws IOException {
197     long now = System.nanoTime();
198     long originalDuration = source.timeout().hasDeadline()
199         ? source.timeout().deadlineNanoTime() - now
200         : Long.MAX_VALUE;
201     source.timeout().deadlineNanoTime(now + Math.min(originalDuration, timeUnit.toNanos(duration)));
202     try {
203       Buffer skipBuffer = new Buffer();
204       while (source.read(skipBuffer, 8192) != -1) {
205         skipBuffer.clear();
206       }
207       return true// Success! The source has been exhausted.
208     } catch (InterruptedIOException e) {
209       return false// We ran out of time before exhausting the source.
210     } finally {
211       if (originalDuration == Long.MAX_VALUE) {
212         source.timeout().clearDeadline();
213       } else {
214         source.timeout().deadlineNanoTime(now + originalDuration);
215       }
216     }
217   }
218
219   /** Returns an immutable copy of {@code list}. */
220   public static <T> List<T> immutableList(List<T> list) {
221     return Collections.unmodifiableList(new ArrayList<>(list));
222   }
223
224   /** Returns an immutable copy of {@code map}. */
225   public static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
226     return map.isEmpty()
227         ? Collections.emptyMap()
228         : Collections.unmodifiableMap(new LinkedHashMap<>(map));
229   }
230
231   /** Returns an immutable list containing {@code elements}. */
232   @SafeVarargs
233   public static <T> List<T> immutableList(T... elements) {
234     return Collections.unmodifiableList(Arrays.asList(elements.clone()));
235   }
236
237   public static ThreadFactory threadFactory(String name, boolean daemon) {
238     return runnable -> {
239       Thread result = new Thread(runnable, name);
240       result.setDaemon(daemon);
241       return result;
242     };
243   }
244
245   /**
246    * Returns an array containing only elements found in {@code first} and also in {@code
247    * second}. The returned elements are in the same order as in {@code first}.
248    */

249   public static String[] intersect(
250       Comparator<? super String> comparator, String[] first, String[] second) {
251     List<String> result = new ArrayList<>();
252     for (String a : first) {
253       for (String b : second) {
254         if (comparator.compare(a, b) == 0) {
255           result.add(a);
256           break;
257         }
258       }
259     }
260     return result.toArray(new String[result.size()]);
261   }
262
263   /**
264    * Returns true if there is an element in {@code first} that is also in {@code second}. This
265    * method terminates if any intersection is found. The sizes of both arguments are assumed to be
266    * so small, and the likelihood of an intersection so great, that it is not worth the CPU cost of
267    * sorting or the memory cost of hashing.
268    */

269   public static boolean nonEmptyIntersection(
270       Comparator<String> comparator, String[] first, String[] second) {
271     if (first == null || second == null || first.length == 0 || second.length == 0) {
272       return false;
273     }
274     for (String a : first) {
275       for (String b : second) {
276         if (comparator.compare(a, b) == 0) {
277           return true;
278         }
279       }
280     }
281     return false;
282   }
283
284   public static String hostHeader(HttpUrl url, boolean includeDefaultPort) {
285     String host = url.host().contains(":")
286         ? "[" + url.host() + "]"
287         : url.host();
288     return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme())
289         ? host + ":" + url.port()
290         : host;
291   }
292
293   /**
294    * Returns true if {@code e} is due to a firmware bug fixed after Android 4.2.2.
295    * https://code.google.com/p/android/issues/detail?id=54072
296    */

297   public static boolean isAndroidGetsocknameError(AssertionError e) {
298     return e.getCause() != null && e.getMessage() != null
299         && e.getMessage().contains("getsockname failed");
300   }
301
302   public static int indexOf(Comparator<String> comparator, String[] array, String value) {
303     for (int i = 0, size = array.length; i < size; i++) {
304       if (comparator.compare(array[i], value) == 0) return i;
305     }
306     return -1;
307   }
308
309   public static String[] concat(String[] array, String value) {
310     String[] result = new String[array.length + 1];
311     System.arraycopy(array, 0, result, 0, array.length);
312     result[result.length - 1] = value;
313     return result;
314   }
315
316   /**
317    * Increments {@code pos} until {@code input[pos]} is not ASCII whitespace. Stops at {@code
318    * limit}.
319    */

320   public static int skipLeadingAsciiWhitespace(String input, int pos, int limit) {
321     for (int i = pos; i < limit; i++) {
322       switch (input.charAt(i)) {
323         case '\t':
324         case '\n':
325         case '\f':
326         case '\r':
327         case ' ':
328           continue;
329         default:
330           return i;
331       }
332     }
333     return limit;
334   }
335
336   /**
337    * Decrements {@code limit} until {@code input[limit - 1]} is not ASCII whitespace. Stops at
338    * {@code pos}.
339    */

340   public static int skipTrailingAsciiWhitespace(String input, int pos, int limit) {
341     for (int i = limit - 1; i >= pos; i--) {
342       switch (input.charAt(i)) {
343         case '\t':
344         case '\n':
345         case '\f':
346         case '\r':
347         case ' ':
348           continue;
349         default:
350           return i + 1;
351       }
352     }
353     return pos;
354   }
355
356   /** Equivalent to {@code string.substring(pos, limit).trim()}. */
357   public static String trimSubstring(String string, int pos, int limit) {
358     int start = skipLeadingAsciiWhitespace(string, pos, limit);
359     int end = skipTrailingAsciiWhitespace(string, start, limit);
360     return string.substring(start, end);
361   }
362
363   /**
364    * Returns the index of the first character in {@code input} that contains a character in {@code
365    * delimiters}. Returns limit if there is no such character.
366    */

367   public static int delimiterOffset(String input, int pos, int limit, String delimiters) {
368     for (int i = pos; i < limit; i++) {
369       if (delimiters.indexOf(input.charAt(i)) != -1) return i;
370     }
371     return limit;
372   }
373
374   /**
375    * Returns the index of the first character in {@code input} that is {@code delimiter}. Returns
376    * limit if there is no such character.
377    */

378   public static int delimiterOffset(String input, int pos, int limit, char delimiter) {
379     for (int i = pos; i < limit; i++) {
380       if (input.charAt(i) == delimiter) return i;
381     }
382     return limit;
383   }
384
385   /**
386    * If {@code host} is an IP address, this returns the IP address in canonical form.
387    *
388    * <p>Otherwise this performs IDN ToASCII encoding and canonicalize the result to lowercase. For
389    * example this converts {@code ☃.net} to {@code xn--n3h.net}, and {@code WwW.GoOgLe.cOm} to
390    * {@code www.google.com}. {@code null} will be returned if the host cannot be ToASCII encoded or
391    * if the result contains unsupported ASCII characters.
392    */

393   public static String canonicalizeHost(String host) {
394     // If the input contains a :, it’s an IPv6 address.
395     if (host.contains(":")) {
396       // If the input is encased in square braces "[...]", drop 'em.
397       InetAddress inetAddress = host.startsWith("[") && host.endsWith("]")
398           ? decodeIpv6(host, 1, host.length() - 1)
399           : decodeIpv6(host, 0, host.length());
400       if (inetAddress == nullreturn null;
401       byte[] address = inetAddress.getAddress();
402       if (address.length == 16) return inet6AddressToAscii(address);
403       if (address.length == 4) return inetAddress.getHostAddress(); // An IPv4-mapped IPv6 address.
404       throw new AssertionError("Invalid IPv6 address: '" + host + "'");
405     }
406
407     try {
408       String result = IDN.toASCII(host).toLowerCase(Locale.US);
409       if (result.isEmpty()) return null;
410
411       // Confirm that the IDN ToASCII result doesn't contain any illegal characters.
412       if (containsInvalidHostnameAsciiCodes(result)) {
413         return null;
414       }
415       // TODO: implement all label limits.
416       return result;
417     } catch (IllegalArgumentException e) {
418       return null;
419     }
420   }
421
422   private static boolean containsInvalidHostnameAsciiCodes(String hostnameAscii) {
423     for (int i = 0; i < hostnameAscii.length(); i++) {
424       char c = hostnameAscii.charAt(i);
425       // The WHATWG Host parsing rules accepts some character codes which are invalid by
426       // definition for OkHttp's host header checks (and the WHATWG Host syntax definition). Here
427       // we rule out characters that would cause problems in host headers.
428       if (c <= '\u001f' || c >= '\u007f') {
429         return true;
430       }
431       // Check for the characters mentioned in the WHATWG Host parsing spec:
432       // U+0000, U+0009, U+000A, U+000D, U+0020, "#""%""/"":""?""@""[""\", and "]"
433       // (excluding the characters covered above).
434       if (" #%/:?@[\\]".indexOf(c) != -1) {
435         return true;
436       }
437     }
438     return false;
439   }
440
441   /**
442    * Returns the index of the first character in {@code input} that is either a control character
443    * (like {@code \u0000 or \n}) or a non-ASCII character. Returns -1 if {@code input} has no such
444    * characters.
445    */

446   public static int indexOfControlOrNonAscii(String input) {
447     for (int i = 0, length = input.length(); i < length; i++) {
448       char c = input.charAt(i);
449       if (c <= '\u001f' || c >= '\u007f') {
450         return i;
451       }
452     }
453     return -1;
454   }
455
456   /** Returns true if {@code host} is not a host name and might be an IP address. */
457   public static boolean verifyAsIpAddress(String host) {
458     return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
459   }
460
461   /** Returns a {@link Locale#US} formatted {@link String}. */
462   public static String format(String format, Object... args) {
463     return String.format(Locale.US, format, args);
464   }
465
466   public static Charset bomAwareCharset(BufferedSource source, Charset charset) throws IOException {
467     switch (source.select(UNICODE_BOMS)) {
468       case 0: return UTF_8;
469       case 1: return UTF_16BE;
470       case 2: return UTF_16LE;
471       case 3: return UTF_32BE;
472       case 4: return UTF_32LE;
473       case -1: return charset;
474       defaultthrow new AssertionError();
475     }
476   }
477
478   public static int checkDuration(String name, long duration, TimeUnit unit) {
479     if (duration < 0) throw new IllegalArgumentException(name + " < 0");
480     if (unit == nullthrow new NullPointerException("unit == null");
481     long millis = unit.toMillis(duration);
482     if (millis > Integer.MAX_VALUE) throw new IllegalArgumentException(name + " too large.");
483     if (millis == 0 && duration > 0) throw new IllegalArgumentException(name + " too small.");
484     return (int) millis;
485   }
486
487   public static int decodeHexDigit(char c) {
488     if (c >= '0' && c <= '9') return c - '0';
489     if (c >= 'a' && c <= 'f') return c - 'a' + 10;
490     if (c >= 'A' && c <= 'F') return c - 'A' + 10;
491     return -1;
492   }
493
494   /** Decodes an IPv6 address like 1111:2222:3333:4444:5555:6666:7777:8888 or ::1. */
495   private static @Nullable InetAddress decodeIpv6(String input, int pos, int limit) {
496     byte[] address = new byte[16];
497     int b = 0;
498     int compress = -1;
499     int groupOffset = -1;
500
501     for (int i = pos; i < limit; ) {
502       if (b == address.length) return null// Too many groups.
503
504       // Read a delimiter.
505       if (i + 2 <= limit && input.regionMatches(i, "::", 0, 2)) {
506         // Compression "::" delimiter, which is anywhere in the input, including its prefix.
507         if (compress != -1) return null// Multiple "::" delimiters.
508         i += 2;
509         b += 2;
510         compress = b;
511         if (i == limit) break;
512       } else if (b != 0) {
513         // Group separator ":" delimiter.
514         if (input.regionMatches(i, ":", 0, 1)) {
515           i++;
516         } else if (input.regionMatches(i, ".", 0, 1)) {
517           // If we see a '.', rewind to the beginning of the previous group and parse as IPv4.
518           if (!decodeIpv4Suffix(input, groupOffset, limit, address, b - 2)) return null;
519           b += 2; // We rewound two bytes and then added four.
520           break;
521         } else {
522           return null// Wrong delimiter.
523         }
524       }
525
526       // Read a group, one to four hex digits.
527       int value = 0;
528       groupOffset = i;
529       for (; i < limit; i++) {
530         char c = input.charAt(i);
531         int hexDigit = decodeHexDigit(c);
532         if (hexDigit == -1) break;
533         value = (value << 4) + hexDigit;
534       }
535       int groupLength = i - groupOffset;
536       if (groupLength == 0 || groupLength > 4) return null// Group is the wrong size.
537
538       // We've successfully read a group. Assign its value to our byte array.
539       address[b++] = (byte) ((value >>> 8) & 0xff);
540       address[b++] = (byte) (value & 0xff);
541     }
542
543     // All done. If compression happened, we need to move bytes to the right place in the
544     // address. Here's a sample:
545     //
546     //      input: "1111:2222:3333::7777:8888"
547     //     before: { 11, 11, 22, 22, 33, 33, 00, 00, 77, 77, 88, 88, 00, 00, 00, 00  }
548     //   compress: 6
549     //          b: 10
550     //      after: { 11, 11, 22, 22, 33, 33, 00, 00, 00, 00, 00, 00, 77, 77, 88, 88 }
551     //
552     if (b != address.length) {
553       if (compress == -1) return null// Address didn't have compression or enough groups.
554       System.arraycopy(address, compress, address, address.length - (b - compress), b - compress);
555       Arrays.fill(address, compress, compress + (address.length - b), (byte) 0);
556     }
557
558     try {
559       return InetAddress.getByAddress(address);
560     } catch (UnknownHostException e) {
561       throw new AssertionError();
562     }
563   }
564
565   /** Decodes an IPv4 address suffix of an IPv6 address, like 1111::5555:6666:192.168.0.1. */
566   private static boolean decodeIpv4Suffix(
567       String input, int pos, int limit, byte[] address, int addressOffset) {
568     int b = addressOffset;
569
570     for (int i = pos; i < limit; ) {
571       if (b == address.length) return false// Too many groups.
572
573       // Read a delimiter.
574       if (b != addressOffset) {
575         if (input.charAt(i) != '.') return false// Wrong delimiter.
576         i++;
577       }
578
579       // Read 1 or more decimal digits for a value in 0..255.
580       int value = 0;
581       int groupOffset = i;
582       for (; i < limit; i++) {
583         char c = input.charAt(i);
584         if (c < '0' || c > '9') break;
585         if (value == 0 && groupOffset != i) return false// Reject unnecessary leading '0's.
586         value = (value * 10) + c - '0';
587         if (value > 255) return false// Value out of range.
588       }
589       int groupLength = i - groupOffset;
590       if (groupLength == 0) return false// No digits.
591
592       // We've successfully read a byte.
593       address[b++] = (byte) value;
594     }
595
596     if (b != addressOffset + 4) return false// Too few groups. We wanted exactly four.
597     return true// Success.
598   }
599
600   /** Encodes an IPv6 address in canonical form according to RFC 5952. */
601   private static String inet6AddressToAscii(byte[] address) {
602     // Go through the address looking for the longest run of 0s. Each group is 2-bytes.
603     // A run must be longer than one group (section 4.2.2).
604     // If there are multiple equal runs, the first one must be used (section 4.2.3).
605     int longestRunOffset = -1;
606     int longestRunLength = 0;
607     for (int i = 0; i < address.length; i += 2) {
608       int currentRunOffset = i;
609       while (i < 16 && address[i] == 0 && address[i + 1] == 0) {
610         i += 2;
611       }
612       int currentRunLength = i - currentRunOffset;
613       if (currentRunLength > longestRunLength && currentRunLength >= 4) {
614         longestRunOffset = currentRunOffset;
615         longestRunLength = currentRunLength;
616       }
617     }
618
619     // Emit each 2-byte group in hex, separated by ':'. The longest run of zeroes is "::".
620     Buffer result = new Buffer();
621     for (int i = 0; i < address.length; ) {
622       if (i == longestRunOffset) {
623         result.writeByte(':');
624         i += longestRunLength;
625         if (i == 16) result.writeByte(':');
626       } else {
627         if (i > 0) result.writeByte(':');
628         int group = (address[i] & 0xff) << 8 | address[i + 1] & 0xff;
629         result.writeHexadecimalUnsignedLong(group);
630         i += 2;
631       }
632     }
633     return result.readUtf8();
634   }
635
636   public static X509TrustManager platformTrustManager() {
637     try {
638       TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
639           TrustManagerFactory.getDefaultAlgorithm());
640       trustManagerFactory.init((KeyStore) null);
641       TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
642       if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
643         throw new IllegalStateException("Unexpected default trust managers:"
644             + Arrays.toString(trustManagers));
645       }
646       return (X509TrustManager) trustManagers[0];
647     } catch (GeneralSecurityException e) {
648       throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up.
649     }
650   }
651
652   public static Headers toHeaders(List<Header> headerBlock) {
653     Headers.Builder builder = new Headers.Builder();
654     for (Header header : headerBlock) {
655       Internal.instance.addLenient(builder, header.name.utf8(), header.value.utf8());
656     }
657     return builder.build();
658   }
659
660   public static List<Header> toHeaderBlock(Headers headers) {
661     List<Header> result = new ArrayList<>();
662     for (int i = 0; i < headers.size(); i++) {
663       result.add(new Header(headers.name(i), headers.value(i)));
664     }
665     return result;
666   }
667
668   /**
669    * Returns the system property, or defaultValue if the system property is null or
670    * cannot be read (e.g. because of security policy restrictions).
671    */

672   public static String getSystemProperty(String key, @Nullable String defaultValue) {
673     String value;
674     try {
675       value = System.getProperty(key);
676     } catch (AccessControlException ex) {
677       return defaultValue;
678     }
679     return value != null ? value : defaultValue;
680   }
681
682   /** Returns true if an HTTP request for {@code a} and {@code b} can reuse a connection. */
683   public static boolean sameConnection(HttpUrl a, HttpUrl b) {
684     return a.host().equals(b.host())
685         && a.port() == b.port()
686         && a.scheme().equals(b.scheme());
687   }
688 }
689