1 /* 2 * Copyright (C) 2014 Square, Inc. 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 com.squareup.okhttp.internal.huc; 17 18 import com.squareup.okhttp.Handshake; 19 import com.squareup.okhttp.Headers; 20 import com.squareup.okhttp.MediaType; 21 import com.squareup.okhttp.Request; 22 import com.squareup.okhttp.RequestBody; 23 import com.squareup.okhttp.Response; 24 import com.squareup.okhttp.ResponseBody; 25 import com.squareup.okhttp.internal.Internal; 26 import com.squareup.okhttp.internal.Util; 27 import com.squareup.okhttp.internal.http.CacheRequest; 28 import com.squareup.okhttp.internal.http.HttpMethod; 29 import com.squareup.okhttp.internal.http.OkHeaders; 30 import com.squareup.okhttp.internal.http.StatusLine; 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.OutputStream; 34 import java.net.CacheResponse; 35 import java.net.HttpURLConnection; 36 import java.net.ProtocolException; 37 import java.net.SecureCacheResponse; 38 import java.net.URI; 39 import java.net.URLConnection; 40 import java.security.Principal; 41 import java.security.cert.Certificate; 42 import java.util.Collections; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Set; 46 import javax.net.ssl.HostnameVerifier; 47 import javax.net.ssl.HttpsURLConnection; 48 import javax.net.ssl.SSLPeerUnverifiedException; 49 import javax.net.ssl.SSLSocketFactory; 50 import okio.BufferedSource; 51 import okio.Okio; 52 import okio.Sink; 53 54 /** 55 * Helper methods that convert between Java and OkHttp representations. 56 */ 57 public final class JavaApiConverter { 58 private static final RequestBody EMPTY_REQUEST_BODY = RequestBody.create(null, new byte[0]); 59 60 private JavaApiConverter() { 61 } 62 63 /** 64 * Creates an OkHttp {@link Response} using the supplied {@link URI} and {@link URLConnection} 65 * to supply the data. The URLConnection is assumed to already be connected. If this method 66 * returns {@code null} the response is uncacheable. 67 */ 68 public static Response createOkResponseForCachePut(URI uri, URLConnection urlConnection) 69 throws IOException { 70 71 HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection; 72 73 Response.Builder okResponseBuilder = new Response.Builder(); 74 75 // Request: Create one from the URL connection. 76 Headers responseHeaders = createHeaders(urlConnection.getHeaderFields()); 77 // Some request headers are needed for Vary caching. 78 Headers varyHeaders = varyHeaders(urlConnection, responseHeaders); 79 if (varyHeaders == null) { 80 return null; 81 } 82 83 // OkHttp's Call API requires a placeholder body; the real body will be streamed separately. 84 String requestMethod = httpUrlConnection.getRequestMethod(); 85 RequestBody placeholderBody = HttpMethod.requiresRequestBody(requestMethod) 86 ? EMPTY_REQUEST_BODY 87 : null; 88 89 Request okRequest = new Request.Builder() 90 .url(uri.toString()) 91 .method(requestMethod, placeholderBody) 92 .headers(varyHeaders) 93 .build(); 94 okResponseBuilder.request(okRequest); 95 96 // Status line 97 StatusLine statusLine = StatusLine.parse(extractStatusLine(httpUrlConnection)); 98 okResponseBuilder.protocol(statusLine.protocol); 99 okResponseBuilder.code(statusLine.code); 100 okResponseBuilder.message(statusLine.message); 101 102 // A network response is required for the Cache to find any Vary headers it needs. 103 Response networkResponse = okResponseBuilder.build(); 104 okResponseBuilder.networkResponse(networkResponse); 105 106 // Response headers 107 Headers okHeaders = extractOkResponseHeaders(httpUrlConnection); 108 okResponseBuilder.headers(okHeaders); 109 110 // Response body 111 ResponseBody okBody = createOkBody(urlConnection); 112 okResponseBuilder.body(okBody); 113 114 // Handle SSL handshake information as needed. 115 if (httpUrlConnection instanceof HttpsURLConnection) { 116 HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) httpUrlConnection; 117 118 Certificate[] peerCertificates; 119 try { 120 peerCertificates = httpsUrlConnection.getServerCertificates(); 121 } catch (SSLPeerUnverifiedException e) { 122 peerCertificates = null; 123 } 124 125 Certificate[] localCertificates = httpsUrlConnection.getLocalCertificates(); 126 127 Handshake handshake = Handshake.get( 128 httpsUrlConnection.getCipherSuite(), nullSafeImmutableList(peerCertificates), 129 nullSafeImmutableList(localCertificates)); 130 okResponseBuilder.handshake(handshake); 131 } 132 133 return okResponseBuilder.build(); 134 } 135 136 /** 137 * Returns headers for the header names and values in the {@link Map}. 138 */ 139 private static Headers createHeaders(Map<String, List<String>> headers) { 140 Headers.Builder builder = new Headers.Builder(); 141 for (Map.Entry<String, List<String>> header : headers.entrySet()) { 142 if (header.getKey() == null || header.getValue() == null) { 143 continue; 144 } 145 String name = header.getKey().trim(); 146 for (String value : header.getValue()) { 147 String trimmedValue = value.trim(); 148 Internal.instance.addLenient(builder, name, trimmedValue); 149 } 150 } 151 return builder.build(); 152 } 153 154 private static Headers varyHeaders(URLConnection urlConnection, Headers responseHeaders) { 155 if (OkHeaders.hasVaryAll(responseHeaders)) { 156 // "*" means that this will be treated as uncacheable anyway. 157 return null; 158 } 159 Set<String> varyFields = OkHeaders.varyFields(responseHeaders); 160 if (varyFields.isEmpty()) { 161 return new Headers.Builder().build(); 162 } 163 164 // This probably indicates another HTTP stack is trying to use the shared ResponseCache. 165 // We cannot guarantee this case will work properly because we cannot reliably extract *all* 166 // the request header values, and we can't get multiple Vary request header values. 167 // We also can't be sure about the Accept-Encoding behavior of other stacks. 168 if (!(urlConnection instanceof CacheHttpURLConnection 169 || urlConnection instanceof CacheHttpsURLConnection)) { 170 return null; 171 } 172 173 // This is the case we expect: The URLConnection is from a call to 174 // JavaApiConverter.createJavaUrlConnection() and we have access to the user's request headers. 175 Map<String, List<String>> requestProperties = urlConnection.getRequestProperties(); 176 Headers.Builder result = new Headers.Builder(); 177 for (String fieldName : varyFields) { 178 List<String> fieldValues = requestProperties.get(fieldName); 179 if (fieldValues == null) { 180 if (fieldName.equals("Accept-Encoding")) { 181 // Accept-Encoding is special. If OkHttp sees Accept-Encoding is unset it will add 182 // "gzip". We don't have access to the request that was actually made so we must do the 183 // same. 184 result.add("Accept-Encoding", "gzip"); 185 } 186 } else { 187 for (String fieldValue : fieldValues) { 188 Internal.instance.addLenient(result, fieldName, fieldValue); 189 } 190 } 191 } 192 return result.build(); 193 } 194 195 /** 196 * Creates an OkHttp {@link Response} using the supplied {@link Request} and {@link CacheResponse} 197 * to supply the data. 198 */ 199 static Response createOkResponseForCacheGet(Request request, CacheResponse javaResponse) 200 throws IOException { 201 202 // Build a cache request for the response to use. 203 Headers responseHeaders = createHeaders(javaResponse.getHeaders()); 204 Headers varyHeaders; 205 if (OkHeaders.hasVaryAll(responseHeaders)) { 206 // "*" means that this will be treated as uncacheable anyway. 207 varyHeaders = new Headers.Builder().build(); 208 } else { 209 varyHeaders = OkHeaders.varyHeaders(request.headers(), responseHeaders); 210 } 211 212 Request cacheRequest = new Request.Builder() 213 .url(request.httpUrl()) 214 .method(request.method(), null) 215 .headers(varyHeaders) 216 .build(); 217 218 Response.Builder okResponseBuilder = new Response.Builder(); 219 220 // Request: Use the cacheRequest we built. 221 okResponseBuilder.request(cacheRequest); 222 223 // Status line: Java has this as one of the headers. 224 StatusLine statusLine = StatusLine.parse(extractStatusLine(javaResponse)); 225 okResponseBuilder.protocol(statusLine.protocol); 226 okResponseBuilder.code(statusLine.code); 227 okResponseBuilder.message(statusLine.message); 228 229 // Response headers 230 Headers okHeaders = extractOkHeaders(javaResponse); 231 okResponseBuilder.headers(okHeaders); 232 233 // Response body 234 ResponseBody okBody = createOkBody(okHeaders, javaResponse); 235 okResponseBuilder.body(okBody); 236 237 // Handle SSL handshake information as needed. 238 if (javaResponse instanceof SecureCacheResponse) { 239 SecureCacheResponse javaSecureCacheResponse = (SecureCacheResponse) javaResponse; 240 241 // Handshake doesn't support null lists. 242 List<Certificate> peerCertificates; 243 try { 244 peerCertificates = javaSecureCacheResponse.getServerCertificateChain(); 245 } catch (SSLPeerUnverifiedException e) { 246 peerCertificates = Collections.emptyList(); 247 } 248 List<Certificate> localCertificates = javaSecureCacheResponse.getLocalCertificateChain(); 249 if (localCertificates == null) { 250 localCertificates = Collections.emptyList(); 251 } 252 Handshake handshake = Handshake.get( 253 javaSecureCacheResponse.getCipherSuite(), peerCertificates, localCertificates); 254 okResponseBuilder.handshake(handshake); 255 } 256 257 return okResponseBuilder.build(); 258 } 259 260 /** 261 * Creates an OkHttp {@link Request} from the supplied information. 262 * 263 * <p>This method allows a {@code null} value for {@code requestHeaders} for situations 264 * where a connection is already connected and access to the headers has been lost. 265 * See {@link java.net.HttpURLConnection#getRequestProperties()} for details. 266 */ 267 public static Request createOkRequest( 268 URI uri, String requestMethod, Map<String, List<String>> requestHeaders) { 269 // OkHttp's Call API requires a placeholder body; the real body will be streamed separately. 270 RequestBody placeholderBody = HttpMethod.requiresRequestBody(requestMethod) 271 ? EMPTY_REQUEST_BODY 272 : null; 273 274 Request.Builder builder = new Request.Builder() 275 .url(uri.toString()) 276 .method(requestMethod, placeholderBody); 277 278 if (requestHeaders != null) { 279 Headers headers = extractOkHeaders(requestHeaders); 280 builder.headers(headers); 281 } 282 return builder.build(); 283 } 284 285 /** 286 * Creates a {@link java.net.CacheResponse} of the correct (sub)type using information 287 * gathered from the supplied {@link Response}. 288 */ 289 public static CacheResponse createJavaCacheResponse(final Response response) { 290 final Headers headers = response.headers(); 291 final ResponseBody body = response.body(); 292 if (response.request().isHttps()) { 293 final Handshake handshake = response.handshake(); 294 return new SecureCacheResponse() { 295 @Override 296 public String getCipherSuite() { 297 return handshake != null ? handshake.cipherSuite() : null; 298 } 299 300 @Override 301 public List<Certificate> getLocalCertificateChain() { 302 if (handshake == null) return null; 303 // Java requires null, not an empty list here. 304 List<Certificate> certificates = handshake.localCertificates(); 305 return certificates.size() > 0 ? certificates : null; 306 } 307 308 @Override 309 public List<Certificate> getServerCertificateChain() throws SSLPeerUnverifiedException { 310 if (handshake == null) return null; 311 // Java requires null, not an empty list here. 312 List<Certificate> certificates = handshake.peerCertificates(); 313 return certificates.size() > 0 ? certificates : null; 314 } 315 316 @Override 317 public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { 318 if (handshake == null) return null; 319 return handshake.peerPrincipal(); 320 } 321 322 @Override 323 public Principal getLocalPrincipal() { 324 if (handshake == null) return null; 325 return handshake.localPrincipal(); 326 } 327 328 @Override 329 public Map<String, List<String>> getHeaders() throws IOException { 330 // Java requires that the entry with a null key be the status line. 331 return OkHeaders.toMultimap(headers, StatusLine.get(response).toString()); 332 } 333 334 @Override 335 public InputStream getBody() throws IOException { 336 if (body == null) return null; 337 return body.byteStream(); 338 } 339 }; 340 } else { 341 return new CacheResponse() { 342 @Override 343 public Map<String, List<String>> getHeaders() throws IOException { 344 // Java requires that the entry with a null key be the status line. 345 return OkHeaders.toMultimap(headers, StatusLine.get(response).toString()); 346 } 347 348 @Override 349 public InputStream getBody() throws IOException { 350 if (body == null) return null; 351 return body.byteStream(); 352 } 353 }; 354 } 355 } 356 357 public static java.net.CacheRequest createJavaCacheRequest(final CacheRequest okCacheRequest) { 358 return new java.net.CacheRequest() { 359 @Override 360 public void abort() { 361 okCacheRequest.abort(); 362 } 363 @Override 364 public OutputStream getBody() throws IOException { 365 Sink body = okCacheRequest.body(); 366 if (body == null) { 367 return null; 368 } 369 return Okio.buffer(body).outputStream(); 370 } 371 }; 372 } 373 374 /** 375 * Creates an {@link java.net.HttpURLConnection} of the correct subclass from the supplied OkHttp 376 * {@link Response}. 377 */ 378 static HttpURLConnection createJavaUrlConnectionForCachePut(Response okResponse) { 379 Request request = okResponse.request(); 380 // Create an object of the correct class in case the ResponseCache uses instanceof. 381 if (request.isHttps()) { 382 return new CacheHttpsURLConnection(new CacheHttpURLConnection(okResponse)); 383 } else { 384 return new CacheHttpURLConnection(okResponse); 385 } 386 } 387 388 /** 389 * Extracts an immutable request header map from the supplied {@link com.squareup.okhttp.Headers}. 390 */ 391 static Map<String, List<String>> extractJavaHeaders(Request request) { 392 return OkHeaders.toMultimap(request.headers(), null); 393 } 394 395 /** 396 * Extracts OkHttp headers from the supplied {@link java.net.CacheResponse}. Only real headers are 397 * extracted. See {@link #extractStatusLine(java.net.CacheResponse)}. 398 */ 399 private static Headers extractOkHeaders(CacheResponse javaResponse) throws IOException { 400 Map<String, List<String>> javaResponseHeaders = javaResponse.getHeaders(); 401 return extractOkHeaders(javaResponseHeaders); 402 } 403 404 /** 405 * Extracts OkHttp headers from the supplied {@link java.net.HttpURLConnection}. Only real headers 406 * are extracted. See {@link #extractStatusLine(java.net.HttpURLConnection)}. 407 */ 408 private static Headers extractOkResponseHeaders(HttpURLConnection httpUrlConnection) { 409 Map<String, List<String>> javaResponseHeaders = httpUrlConnection.getHeaderFields(); 410 return extractOkHeaders(javaResponseHeaders); 411 } 412 413 /** 414 * Extracts OkHttp headers from the supplied {@link Map}. Only real headers are 415 * extracted. Any entry (one with a {@code null} key) is discarded. 416 */ 417 // @VisibleForTesting 418 static Headers extractOkHeaders(Map<String, List<String>> javaHeaders) { 419 Headers.Builder okHeadersBuilder = new Headers.Builder(); 420 for (Map.Entry<String, List<String>> javaHeader : javaHeaders.entrySet()) { 421 String name = javaHeader.getKey(); 422 if (name == null) { 423 // The Java API uses the null key to store the status line in responses. 424 // Earlier versions of OkHttp would use the null key to store the "request line" in 425 // requests. e.g. "GET / HTTP 1.1". Although this is no longer the case it must be 426 // explicitly ignored because Headers.Builder does not support null keys. 427 continue; 428 } 429 for (String value : javaHeader.getValue()) { 430 Internal.instance.addLenient(okHeadersBuilder, name, value); 431 } 432 } 433 return okHeadersBuilder.build(); 434 } 435 436 /** 437 * Extracts the status line from the supplied Java API {@link java.net.HttpURLConnection}. 438 * As per the spec, the status line is held as the header with the null key. Returns {@code null} 439 * if there is no status line. 440 */ 441 private static String extractStatusLine(HttpURLConnection httpUrlConnection) { 442 // Java specifies that this will be be response header with a null key. 443 return httpUrlConnection.getHeaderField(null); 444 } 445 446 /** 447 * Extracts the status line from the supplied Java API {@link java.net.CacheResponse}. 448 * As per the spec, the status line is held as the header with the null key. Throws a 449 * {@link ProtocolException} if there is no status line. 450 */ 451 private static String extractStatusLine(CacheResponse javaResponse) throws IOException { 452 Map<String, List<String>> javaResponseHeaders = javaResponse.getHeaders(); 453 return extractStatusLine(javaResponseHeaders); 454 } 455 456 // VisibleForTesting 457 static String extractStatusLine(Map<String, List<String>> javaResponseHeaders) 458 throws ProtocolException { 459 List<String> values = javaResponseHeaders.get(null); 460 if (values == null || values.size() == 0) { 461 // The status line is missing. This suggests a badly behaving cache. 462 throw new ProtocolException( 463 "CacheResponse is missing a \'null\' header containing the status line. Headers=" 464 + javaResponseHeaders); 465 } 466 return values.get(0); 467 } 468 469 /** 470 * Creates an OkHttp Response.Body containing the supplied information. 471 */ 472 private static ResponseBody createOkBody(final Headers okHeaders, 473 final CacheResponse cacheResponse) { 474 return new ResponseBody() { 475 private BufferedSource body; 476 477 @Override 478 public MediaType contentType() { 479 String contentTypeHeader = okHeaders.get("Content-Type"); 480 return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader); 481 } 482 483 @Override 484 public long contentLength() { 485 return OkHeaders.contentLength(okHeaders); 486 } 487 @Override public BufferedSource source() throws IOException { 488 if (body == null) { 489 InputStream is = cacheResponse.getBody(); 490 body = Okio.buffer(Okio.source(is)); 491 } 492 return body; 493 } 494 }; 495 } 496 497 /** 498 * Creates an OkHttp Response.Body containing the supplied information. 499 */ 500 private static ResponseBody createOkBody(final URLConnection urlConnection) { 501 if (!urlConnection.getDoInput()) { 502 return null; 503 } 504 return new ResponseBody() { 505 private BufferedSource body; 506 507 @Override public MediaType contentType() { 508 String contentTypeHeader = urlConnection.getContentType(); 509 return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader); 510 } 511 @Override public long contentLength() { 512 String s = urlConnection.getHeaderField("Content-Length"); 513 return stringToLong(s); 514 } 515 @Override public BufferedSource source() throws IOException { 516 if (body == null) { 517 InputStream is = urlConnection.getInputStream(); 518 body = Okio.buffer(Okio.source(is)); 519 } 520 return body; 521 } 522 }; 523 } 524 525 /** 526 * An {@link java.net.HttpURLConnection} that represents an HTTP request at the point where 527 * the request has been made, and the response headers have been received, but the body content, 528 * if present, has not been read yet. This intended to provide enough information for 529 * {@link java.net.ResponseCache} subclasses and no more. 530 * 531 * <p>Much of the method implementations are overrides to delegate to the OkHttp request and 532 * response, or to deny access to information as a real HttpURLConnection would after connection. 533 */ 534 private static final class CacheHttpURLConnection extends HttpURLConnection { 535 536 private final Request request; 537 private final Response response; 538 539 public CacheHttpURLConnection(Response response) { 540 super(response.request().url()); 541 this.request = response.request(); 542 this.response = response; 543 544 // Configure URLConnection inherited fields. 545 this.connected = true; 546 this.doOutput = request.body() != null; 547 this.doInput = true; 548 this.useCaches = true; 549 550 // Configure HttpUrlConnection inherited fields. 551 this.method = request.method(); 552 } 553 554 // HTTP connection lifecycle methods 555 556 @Override 557 public void connect() throws IOException { 558 throw throwRequestModificationException(); 559 } 560 561 @Override 562 public void disconnect() { 563 throw throwRequestModificationException(); 564 } 565 566 // HTTP Request methods 567 568 @Override 569 public void setRequestProperty(String key, String value) { 570 throw throwRequestModificationException(); 571 } 572 573 @Override 574 public void addRequestProperty(String key, String value) { 575 throw throwRequestModificationException(); 576 } 577 578 @Override 579 public String getRequestProperty(String key) { 580 return request.header(key); 581 } 582 583 @Override 584 public Map<String, List<String>> getRequestProperties() { 585 // The RI and OkHttp's HttpURLConnectionImpl fail this call after connect() as required by the 586 // spec. There seems no good reason why this should fail while getRequestProperty() is ok. 587 // We don't fail here, because we need all request header values for caching Vary responses 588 // correctly. 589 return OkHeaders.toMultimap(request.headers(), null); 590 } 591 592 @Override 593 public void setFixedLengthStreamingMode(int contentLength) { 594 throw throwRequestModificationException(); 595 } 596 597 @Override 598 public void setFixedLengthStreamingMode(long contentLength) { 599 throw throwRequestModificationException(); 600 } 601 602 @Override 603 public void setChunkedStreamingMode(int chunklen) { 604 throw throwRequestModificationException(); 605 } 606 607 @Override 608 public void setInstanceFollowRedirects(boolean followRedirects) { 609 throw throwRequestModificationException(); 610 } 611 612 @Override 613 public boolean getInstanceFollowRedirects() { 614 // Return the platform default. 615 return super.getInstanceFollowRedirects(); 616 } 617 618 @Override 619 public void setRequestMethod(String method) throws ProtocolException { 620 throw throwRequestModificationException(); 621 } 622 623 @Override 624 public String getRequestMethod() { 625 return request.method(); 626 } 627 628 // HTTP Response methods 629 630 @Override 631 public String getHeaderFieldKey(int position) { 632 // Deal with index 0 meaning "status line" 633 if (position < 0) { 634 throw new IllegalArgumentException("Invalid header index: " + position); 635 } 636 if (position == 0) { 637 return null; 638 } 639 return response.headers().name(position - 1); 640 } 641 642 @Override 643 public String getHeaderField(int position) { 644 // Deal with index 0 meaning "status line" 645 if (position < 0) { 646 throw new IllegalArgumentException("Invalid header index: " + position); 647 } 648 if (position == 0) { 649 return StatusLine.get(response).toString(); 650 } 651 return response.headers().value(position - 1); 652 } 653 654 @Override 655 public String getHeaderField(String fieldName) { 656 return fieldName == null 657 ? StatusLine.get(response).toString() 658 : response.headers().get(fieldName); 659 } 660 661 @Override 662 public Map<String, List<String>> getHeaderFields() { 663 return OkHeaders.toMultimap(response.headers(), StatusLine.get(response).toString()); 664 } 665 666 @Override 667 public int getResponseCode() throws IOException { 668 return response.code(); 669 } 670 671 @Override 672 public String getResponseMessage() throws IOException { 673 return response.message(); 674 } 675 676 @Override 677 public InputStream getErrorStream() { 678 return null; 679 } 680 681 // HTTP miscellaneous methods 682 683 @Override 684 public boolean usingProxy() { 685 // It's safe to return false here, even if a proxy is in use. The problem is we don't 686 // necessarily know if we're going to use a proxy by the time we ask the cache for a response. 687 return false; 688 } 689 690 // URLConnection methods 691 692 @Override 693 public void setConnectTimeout(int timeout) { 694 throw throwRequestModificationException(); 695 } 696 697 @Override 698 public int getConnectTimeout() { 699 // Impossible to say. 700 return 0; 701 } 702 703 @Override 704 public void setReadTimeout(int timeout) { 705 throw throwRequestModificationException(); 706 } 707 708 @Override 709 public int getReadTimeout() { 710 // Impossible to say. 711 return 0; 712 } 713 714 @Override 715 public Object getContent() throws IOException { 716 throw throwResponseBodyAccessException(); 717 } 718 719 @Override 720 public Object getContent(Class[] classes) throws IOException { 721 throw throwResponseBodyAccessException(); 722 } 723 724 @Override 725 public InputStream getInputStream() throws IOException { 726 throw throwResponseBodyAccessException(); 727 } 728 729 @Override 730 public OutputStream getOutputStream() throws IOException { 731 throw throwRequestModificationException(); 732 } 733 734 @Override 735 public void setDoInput(boolean doInput) { 736 throw throwRequestModificationException(); 737 } 738 739 @Override 740 public boolean getDoInput() { 741 return doInput; 742 } 743 744 @Override 745 public void setDoOutput(boolean doOutput) { 746 throw throwRequestModificationException(); 747 } 748 749 @Override 750 public boolean getDoOutput() { 751 return doOutput; 752 } 753 754 @Override 755 public void setAllowUserInteraction(boolean allowUserInteraction) { 756 throw throwRequestModificationException(); 757 } 758 759 @Override 760 public boolean getAllowUserInteraction() { 761 return false; 762 } 763 764 @Override 765 public void setUseCaches(boolean useCaches) { 766 throw throwRequestModificationException(); 767 } 768 769 @Override 770 public boolean getUseCaches() { 771 return super.getUseCaches(); 772 } 773 774 @Override 775 public void setIfModifiedSince(long ifModifiedSince) { 776 throw throwRequestModificationException(); 777 } 778 779 @Override 780 public long getIfModifiedSince() { 781 return stringToLong(request.headers().get("If-Modified-Since")); 782 } 783 784 @Override 785 public boolean getDefaultUseCaches() { 786 return super.getDefaultUseCaches(); 787 } 788 789 @Override 790 public void setDefaultUseCaches(boolean defaultUseCaches) { 791 super.setDefaultUseCaches(defaultUseCaches); 792 } 793 } 794 795 /** An HttpsURLConnection to offer to the cache. */ 796 private static final class CacheHttpsURLConnection extends DelegatingHttpsURLConnection { 797 private final CacheHttpURLConnection delegate; 798 799 public CacheHttpsURLConnection(CacheHttpURLConnection delegate) { 800 super(delegate); 801 this.delegate = delegate; 802 } 803 804 @Override protected Handshake handshake() { 805 return delegate.response.handshake(); 806 } 807 808 @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { 809 throw throwRequestModificationException(); 810 } 811 812 @Override public HostnameVerifier getHostnameVerifier() { 813 throw throwRequestSslAccessException(); 814 } 815 816 @Override public void setSSLSocketFactory(SSLSocketFactory socketFactory) { 817 throw throwRequestModificationException(); 818 } 819 820 @Override public SSLSocketFactory getSSLSocketFactory() { 821 throw throwRequestSslAccessException(); 822 } 823 824 // ANDROID-BEGIN 825 // @Override public long getContentLengthLong() { 826 // return delegate.getContentLengthLong(); 827 // } 828 // ANDROID-END 829 830 @Override public void setFixedLengthStreamingMode(long contentLength) { 831 delegate.setFixedLengthStreamingMode(contentLength); 832 } 833 834 // ANDROID-BEGIN 835 // @Override public long getHeaderFieldLong(String field, long defaultValue) { 836 // return delegate.getHeaderFieldLong(field, defaultValue); 837 // } 838 // ANDROID-END 839 } 840 841 private static RuntimeException throwRequestModificationException() { 842 throw new UnsupportedOperationException("ResponseCache cannot modify the request."); 843 } 844 845 private static RuntimeException throwRequestHeaderAccessException() { 846 throw new UnsupportedOperationException("ResponseCache cannot access request headers"); 847 } 848 849 private static RuntimeException throwRequestSslAccessException() { 850 throw new UnsupportedOperationException("ResponseCache cannot access SSL internals"); 851 } 852 853 private static RuntimeException throwResponseBodyAccessException() { 854 throw new UnsupportedOperationException("ResponseCache cannot access the response body."); 855 } 856 857 private static <T> List<T> nullSafeImmutableList(T[] elements) { 858 return elements == null ? Collections.<T>emptyList() : Util.immutableList(elements); 859 } 860 861 private static long stringToLong(String s) { 862 if (s == null) return -1; 863 try { 864 return Long.parseLong(s); 865 } catch (NumberFormatException e) { 866 return -1; 867 } 868 } 869 } 870