All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.squareup.okhttp.internal.huc.JavaApiConverter Maven / Gradle / Ivy

There is a newer version: 2.7.5
Show newest version
/*
 * Copyright (C) 2014 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.okhttp.internal.huc;

import com.squareup.okhttp.Handshake;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.http.OkHeaders;
import com.squareup.okhttp.internal.http.StatusLine;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.CacheResponse;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.SecureCacheResponse;
import java.net.URI;
import java.net.URLConnection;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocketFactory;
import okio.BufferedSource;
import okio.Okio;

/**
 * Helper methods that convert between Java and OkHttp representations.
 */
public final class JavaApiConverter {

  private JavaApiConverter() {
  }

  /**
   * Creates an OkHttp {@link Response} using the supplied {@link URI} and {@link URLConnection}
   * to supply the data. The URLConnection is assumed to already be connected.
   */
  public static Response createOkResponse(URI uri, URLConnection urlConnection) throws IOException {
    HttpURLConnection httpUrlConnection = (HttpURLConnection) urlConnection;

    Response.Builder okResponseBuilder = new Response.Builder();

    // Request: Create one from the URL connection.
    // A connected HttpURLConnection does not permit access to request headers.
    Map> requestHeaders = null;
    Request okRequest = createOkRequest(uri, httpUrlConnection.getRequestMethod(), requestHeaders);
    okResponseBuilder.request(okRequest);

    // Status line
    StatusLine statusLine = StatusLine.parse(extractStatusLine(httpUrlConnection));
    okResponseBuilder.protocol(statusLine.protocol);
    okResponseBuilder.code(statusLine.code);
    okResponseBuilder.message(statusLine.message);

    // Response headers
    Headers okHeaders = extractOkResponseHeaders(httpUrlConnection);
    okResponseBuilder.headers(okHeaders);

    // Response body
    ResponseBody okBody = createOkBody(okHeaders, urlConnection.getInputStream());
    okResponseBuilder.body(okBody);

    // Handle SSL handshake information as needed.
    if (httpUrlConnection instanceof HttpsURLConnection) {
      HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) httpUrlConnection;

      Certificate[] peerCertificates;
      try {
        peerCertificates = httpsUrlConnection.getServerCertificates();
      } catch (SSLPeerUnverifiedException e) {
        peerCertificates = null;
      }

      Certificate[] localCertificates = httpsUrlConnection.getLocalCertificates();

      Handshake handshake = Handshake.get(
          httpsUrlConnection.getCipherSuite(), nullSafeImmutableList(peerCertificates),
          nullSafeImmutableList(localCertificates));
      okResponseBuilder.handshake(handshake);
    }

    return okResponseBuilder.build();
  }

  /**
   * Creates an OkHttp {@link Response} using the supplied {@link Request} and {@link CacheResponse}
   * to supply the data.
   */
  static Response createOkResponse(Request request, CacheResponse javaResponse)
      throws IOException {
    Response.Builder okResponseBuilder = new Response.Builder();

    // Request: Use the one provided.
    okResponseBuilder.request(request);

    // Status line: Java has this as one of the headers.
    StatusLine statusLine = StatusLine.parse(extractStatusLine(javaResponse));
    okResponseBuilder.protocol(statusLine.protocol);
    okResponseBuilder.code(statusLine.code);
    okResponseBuilder.message(statusLine.message);

    // Response headers
    Headers okHeaders = extractOkHeaders(javaResponse);
    okResponseBuilder.headers(okHeaders);

    // Response body
    ResponseBody okBody = createOkBody(okHeaders, javaResponse.getBody());
    okResponseBuilder.body(okBody);

    // Handle SSL handshake information as needed.
    if (javaResponse instanceof SecureCacheResponse) {
      SecureCacheResponse javaSecureCacheResponse = (SecureCacheResponse) javaResponse;

      // Handshake doesn't support null lists.
      List peerCertificates;
      try {
        peerCertificates = javaSecureCacheResponse.getServerCertificateChain();
      } catch (SSLPeerUnverifiedException e) {
        peerCertificates = Collections.emptyList();
      }
      List localCertificates = javaSecureCacheResponse.getLocalCertificateChain();
      if (localCertificates == null) {
        localCertificates = Collections.emptyList();
      }
      Handshake handshake = Handshake.get(
          javaSecureCacheResponse.getCipherSuite(), peerCertificates, localCertificates);
      okResponseBuilder.handshake(handshake);
    }

    return okResponseBuilder.build();
  }

  /**
   * Creates an OkHttp {@link Request} from the supplied information.
   *
   * 

This method allows a {@code null} value for {@code requestHeaders} for situations * where a connection is already connected and access to the headers has been lost. * See {@link java.net.HttpURLConnection#getRequestProperties()} for details. */ public static Request createOkRequest( URI uri, String requestMethod, Map> requestHeaders) { Request.Builder builder = new Request.Builder() .url(uri.toString()) .method(requestMethod, null); if (requestHeaders != null) { Headers headers = extractOkHeaders(requestHeaders); builder.headers(headers); } return builder.build(); } /** * Creates a {@link java.net.CacheResponse} of the correct (sub)type using information * gathered from the supplied {@link Response}. */ public static CacheResponse createJavaCacheResponse(final Response response) { final Headers headers = response.headers(); final ResponseBody body = response.body(); if (response.request().isHttps()) { final Handshake handshake = response.handshake(); return new SecureCacheResponse() { @Override public String getCipherSuite() { return handshake != null ? handshake.cipherSuite() : null; } @Override public List getLocalCertificateChain() { if (handshake == null) return null; // Java requires null, not an empty list here. List certificates = handshake.localCertificates(); return certificates.size() > 0 ? certificates : null; } @Override public List getServerCertificateChain() throws SSLPeerUnverifiedException { if (handshake == null) return null; // Java requires null, not an empty list here. List certificates = handshake.peerCertificates(); return certificates.size() > 0 ? certificates : null; } @Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { if (handshake == null) return null; return handshake.peerPrincipal(); } @Override public Principal getLocalPrincipal() { if (handshake == null) return null; return handshake.localPrincipal(); } @Override public Map> getHeaders() throws IOException { // Java requires that the entry with a null key be the status line. return OkHeaders.toMultimap(headers, StatusLine.get(response).toString()); } @Override public InputStream getBody() throws IOException { if (body == null) return null; return body.byteStream(); } }; } else { return new CacheResponse() { @Override public Map> getHeaders() throws IOException { // Java requires that the entry with a null key be the status line. return OkHeaders.toMultimap(headers, StatusLine.get(response).toString()); } @Override public InputStream getBody() throws IOException { if (body == null) return null; return body.byteStream(); } }; } } /** * Creates an {@link java.net.HttpURLConnection} of the correct subclass from the supplied OkHttp * {@link Response}. */ static HttpURLConnection createJavaUrlConnection(Response okResponse) { Request request = okResponse.request(); // Create an object of the correct class in case the ResponseCache uses instanceof. if (request.isHttps()) { return new CacheHttpsURLConnection(new CacheHttpURLConnection(okResponse)); } else { return new CacheHttpURLConnection(okResponse); } } /** * Extracts an immutable request header map from the supplied {@link com.squareup.okhttp.Headers}. */ static Map> extractJavaHeaders(Request request) { return OkHeaders.toMultimap(request.headers(), null); } /** * Extracts OkHttp headers from the supplied {@link java.net.CacheResponse}. Only real headers are * extracted. See {@link #extractStatusLine(java.net.CacheResponse)}. */ private static Headers extractOkHeaders(CacheResponse javaResponse) throws IOException { Map> javaResponseHeaders = javaResponse.getHeaders(); return extractOkHeaders(javaResponseHeaders); } /** * Extracts OkHttp headers from the supplied {@link java.net.HttpURLConnection}. Only real headers * are extracted. See {@link #extractStatusLine(java.net.HttpURLConnection)}. */ private static Headers extractOkResponseHeaders(HttpURLConnection httpUrlConnection) { Map> javaResponseHeaders = httpUrlConnection.getHeaderFields(); return extractOkHeaders(javaResponseHeaders); } /** * Extracts OkHttp headers from the supplied {@link Map}. Only real headers are * extracted. Any entry (one with a {@code null} key) is discarded. */ // @VisibleForTesting static Headers extractOkHeaders(Map> javaHeaders) { Headers.Builder okHeadersBuilder = new Headers.Builder(); for (Map.Entry> javaHeader : javaHeaders.entrySet()) { String name = javaHeader.getKey(); if (name == null) { // The Java API uses the null key to store the status line in responses. // Earlier versions of OkHttp would use the null key to store the "request line" in // requests. e.g. "GET / HTTP 1.1". Although this is no longer the case it must be // explicitly ignored because Headers.Builder does not support null keys. continue; } for (String value : javaHeader.getValue()) { okHeadersBuilder.add(name, value); } } return okHeadersBuilder.build(); } /** * Extracts the status line from the supplied Java API {@link java.net.HttpURLConnection}. * As per the spec, the status line is held as the header with the null key. Returns {@code null} * if there is no status line. */ private static String extractStatusLine(HttpURLConnection httpUrlConnection) { // Java specifies that this will be be response header with a null key. return httpUrlConnection.getHeaderField(null); } /** * Extracts the status line from the supplied Java API {@link java.net.CacheResponse}. * As per the spec, the status line is held as the header with the null key. Returns {@code null} * if there is no status line. */ private static String extractStatusLine(CacheResponse javaResponse) throws IOException { Map> javaResponseHeaders = javaResponse.getHeaders(); return extractStatusLine(javaResponseHeaders); } // VisibleForTesting static String extractStatusLine(Map> javaResponseHeaders) { List values = javaResponseHeaders.get(null); if (values == null || values.size() == 0) { return null; } return values.get(0); } /** * Creates an OkHttp Response.Body containing the supplied information. */ private static ResponseBody createOkBody(final Headers okHeaders, InputStream body) { final BufferedSource source = Okio.buffer(Okio.source(body)); return new ResponseBody() { @Override public MediaType contentType() { String contentTypeHeader = okHeaders.get("Content-Type"); return contentTypeHeader == null ? null : MediaType.parse(contentTypeHeader); } @Override public long contentLength() { return OkHeaders.contentLength(okHeaders); } @Override public BufferedSource source() { return source; } }; } /** * An {@link java.net.HttpURLConnection} that represents an HTTP request at the point where * the request has been made, and the response headers have been received, but the body content, * if present, has not been read yet. This intended to provide enough information for * {@link java.net.ResponseCache} subclasses and no more. * *

Much of the method implementations are overrides to delegate to the OkHttp request and * response, or to deny access to information as a real HttpURLConnection would after connection. */ private static final class CacheHttpURLConnection extends HttpURLConnection { private final Request request; private final Response response; public CacheHttpURLConnection(Response response) { super(response.request().url()); this.request = response.request(); this.response = response; // Configure URLConnection inherited fields. this.connected = true; this.doOutput = response.body() == null; // Configure HttpUrlConnection inherited fields. this.method = request.method(); } // HTTP connection lifecycle methods @Override public void connect() throws IOException { throw throwRequestModificationException(); } @Override public void disconnect() { throw throwRequestModificationException(); } // HTTP Request methods @Override public void setRequestProperty(String key, String value) { throw throwRequestModificationException(); } @Override public void addRequestProperty(String key, String value) { throw throwRequestModificationException(); } @Override public String getRequestProperty(String key) { return request.header(key); } @Override public Map> getRequestProperties() { // This is to preserve RI and compatibility with OkHttp's HttpURLConnectionImpl. There seems // no good reason why this should fail while getRequestProperty() is ok. throw throwRequestHeaderAccessException(); } @Override public void setFixedLengthStreamingMode(int contentLength) { throw throwRequestModificationException(); } @Override public void setFixedLengthStreamingMode(long contentLength) { throw throwRequestModificationException(); } @Override public void setChunkedStreamingMode(int chunklen) { throw throwRequestModificationException(); } @Override public void setInstanceFollowRedirects(boolean followRedirects) { throw throwRequestModificationException(); } @Override public boolean getInstanceFollowRedirects() { // Return the platform default. return super.getInstanceFollowRedirects(); } @Override public void setRequestMethod(String method) throws ProtocolException { throw throwRequestModificationException(); } @Override public String getRequestMethod() { return request.method(); } // HTTP Response methods @Override public String getHeaderFieldKey(int position) { // Deal with index 0 meaning "status line" if (position < 0) { throw new IllegalArgumentException("Invalid header index: " + position); } if (position == 0) { return null; } return response.headers().name(position - 1); } @Override public String getHeaderField(int position) { // Deal with index 0 meaning "status line" if (position < 0) { throw new IllegalArgumentException("Invalid header index: " + position); } if (position == 0) { return StatusLine.get(response).toString(); } return response.headers().value(position - 1); } @Override public String getHeaderField(String fieldName) { return fieldName == null ? StatusLine.get(response).toString() : response.headers().get(fieldName); } @Override public Map> getHeaderFields() { return OkHeaders.toMultimap(response.headers(), StatusLine.get(response).toString()); } @Override public int getResponseCode() throws IOException { return response.code(); } @Override public String getResponseMessage() throws IOException { return response.message(); } @Override public InputStream getErrorStream() { return null; } // HTTP miscellaneous methods @Override public boolean usingProxy() { // It's safe to return false here, even if a proxy is in use. The problem is we don't // necessarily know if we're going to use a proxy by the time we ask the cache for a response. return false; } // URLConnection methods @Override public void setConnectTimeout(int timeout) { throw throwRequestModificationException(); } @Override public int getConnectTimeout() { // Impossible to say. return 0; } @Override public void setReadTimeout(int timeout) { throw throwRequestModificationException(); } @Override public int getReadTimeout() { // Impossible to say. return 0; } @Override public Object getContent() throws IOException { throw throwResponseBodyAccessException(); } @Override public Object getContent(Class[] classes) throws IOException { throw throwResponseBodyAccessException(); } @Override public InputStream getInputStream() throws IOException { throw throwResponseBodyAccessException(); } @Override public OutputStream getOutputStream() throws IOException { throw throwRequestModificationException(); } @Override public void setDoInput(boolean doInput) { throw throwRequestModificationException(); } @Override public boolean getDoInput() { return true; } @Override public void setDoOutput(boolean doOutput) { throw throwRequestModificationException(); } @Override public boolean getDoOutput() { return request.body() != null; } @Override public void setAllowUserInteraction(boolean allowUserInteraction) { throw throwRequestModificationException(); } @Override public boolean getAllowUserInteraction() { return false; } @Override public void setUseCaches(boolean useCaches) { throw throwRequestModificationException(); } @Override public boolean getUseCaches() { return super.getUseCaches(); } @Override public void setIfModifiedSince(long ifModifiedSince) { throw throwRequestModificationException(); } @Override public long getIfModifiedSince() { return 0; } @Override public boolean getDefaultUseCaches() { return super.getDefaultUseCaches(); } @Override public void setDefaultUseCaches(boolean defaultUseCaches) { super.setDefaultUseCaches(defaultUseCaches); } } /** An HttpsURLConnection to offer to the cache. */ private static final class CacheHttpsURLConnection extends DelegatingHttpsURLConnection { private final CacheHttpURLConnection delegate; public CacheHttpsURLConnection(CacheHttpURLConnection delegate) { super(delegate); this.delegate = delegate; } @Override protected Handshake handshake() { return delegate.response.handshake(); } @Override public void setHostnameVerifier(HostnameVerifier hostnameVerifier) { throw throwRequestModificationException(); } @Override public HostnameVerifier getHostnameVerifier() { throw throwRequestSslAccessException(); } @Override public void setSSLSocketFactory(SSLSocketFactory socketFactory) { throw throwRequestModificationException(); } @Override public SSLSocketFactory getSSLSocketFactory() { throw throwRequestSslAccessException(); } @Override public long getContentLengthLong() { return delegate.getContentLengthLong(); } @Override public void setFixedLengthStreamingMode(long contentLength) { delegate.setFixedLengthStreamingMode(contentLength); } @Override public long getHeaderFieldLong(String field, long defaultValue) { return delegate.getHeaderFieldLong(field, defaultValue); } } private static RuntimeException throwRequestModificationException() { throw new UnsupportedOperationException("ResponseCache cannot modify the request."); } private static RuntimeException throwRequestHeaderAccessException() { throw new UnsupportedOperationException("ResponseCache cannot access request headers"); } private static RuntimeException throwRequestSslAccessException() { throw new UnsupportedOperationException("ResponseCache cannot access SSL internals"); } private static RuntimeException throwResponseBodyAccessException() { throw new UnsupportedOperationException("ResponseCache cannot access the response body."); } private static List nullSafeImmutableList(T[] elements) { return elements == null ? Collections.emptyList() : Util.immutableList(elements); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy