libcore.net.http.HttpsURLConnectionImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of httpresponsecache Show documentation
Show all versions of httpresponsecache Show documentation
An HTTP Response Cache for java.net.URL
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 libcore.net.http;
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.Proxy;
import java.net.SecureCacheResponse;
import java.net.URL;
import java.security.Permission;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import com.integralblue.httpresponsecache.compat.URLs;
final class HttpsURLConnectionImpl extends HttpsURLConnection {
/** HttpUrlConnectionDelegate allows reuse of HttpURLConnectionImpl */
private final HttpUrlConnectionDelegate delegate;
protected HttpsURLConnectionImpl(URL url, int port) {
super(url);
delegate = new HttpUrlConnectionDelegate(url, port);
}
protected HttpsURLConnectionImpl(URL url, int port, Proxy proxy) {
super(url);
delegate = new HttpUrlConnectionDelegate(url, port, proxy);
}
private void checkConnected() {
if (delegate.getSSLSocket() == null) {
throw new IllegalStateException("Connection has not yet been established");
}
}
HttpEngine getHttpEngine() {
return delegate.getHttpEngine();
}
@Override
public String getCipherSuite() {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getCipherSuite();
}
checkConnected();
return delegate.getSSLSocket().getSession().getCipherSuite();
}
@Override
public Certificate[] getLocalCertificates() {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
List result = cacheResponse.getLocalCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
checkConnected();
return delegate.getSSLSocket().getSession().getLocalCertificates();
}
@Override
public Certificate[] getServerCertificates() throws SSLPeerUnverifiedException {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
List result = cacheResponse.getServerCertificateChain();
return result != null ? result.toArray(new Certificate[result.size()]) : null;
}
checkConnected();
return delegate.getSSLSocket().getSession().getPeerCertificates();
}
@Override
public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getPeerPrincipal();
}
checkConnected();
return delegate.getSSLSocket().getSession().getPeerPrincipal();
}
@Override
public Principal getLocalPrincipal() {
SecureCacheResponse cacheResponse = delegate.getCacheResponse();
if (cacheResponse != null) {
return cacheResponse.getLocalPrincipal();
}
checkConnected();
return delegate.getSSLSocket().getSession().getLocalPrincipal();
}
@Override
public void disconnect() {
delegate.disconnect();
}
@Override
public InputStream getErrorStream() {
return delegate.getErrorStream();
}
@Override
public String getRequestMethod() {
return delegate.getRequestMethod();
}
@Override
public int getResponseCode() throws IOException {
return delegate.getResponseCode();
}
@Override
public String getResponseMessage() throws IOException {
return delegate.getResponseMessage();
}
@Override
public void setRequestMethod(String method) throws ProtocolException {
delegate.setRequestMethod(method);
}
@Override
public boolean usingProxy() {
return delegate.usingProxy();
}
@Override
public boolean getInstanceFollowRedirects() {
return delegate.getInstanceFollowRedirects();
}
@Override
public void setInstanceFollowRedirects(boolean followRedirects) {
delegate.setInstanceFollowRedirects(followRedirects);
}
@Override
public void connect() throws IOException {
connected = true;
delegate.connect();
}
@Override
public boolean getAllowUserInteraction() {
return delegate.getAllowUserInteraction();
}
@Override
public Object getContent() throws IOException {
return delegate.getContent();
}
@SuppressWarnings("unchecked") // Spec does not generify
@Override
public Object getContent(Class[] types) throws IOException {
return delegate.getContent(types);
}
@Override
public String getContentEncoding() {
return delegate.getContentEncoding();
}
@Override
public int getContentLength() {
return delegate.getContentLength();
}
@Override
public String getContentType() {
return delegate.getContentType();
}
@Override
public long getDate() {
return delegate.getDate();
}
@Override
public boolean getDefaultUseCaches() {
return delegate.getDefaultUseCaches();
}
@Override
public boolean getDoInput() {
return delegate.getDoInput();
}
@Override
public boolean getDoOutput() {
return delegate.getDoOutput();
}
@Override
public long getExpiration() {
return delegate.getExpiration();
}
@Override
public String getHeaderField(int pos) {
return delegate.getHeaderField(pos);
}
@Override
public Map> getHeaderFields() {
return delegate.getHeaderFields();
}
@Override
public Map> getRequestProperties() {
return delegate.getRequestProperties();
}
@Override
public void addRequestProperty(String field, String newValue) {
delegate.addRequestProperty(field, newValue);
}
@Override
public String getHeaderField(String key) {
return delegate.getHeaderField(key);
}
@Override
public long getHeaderFieldDate(String field, long defaultValue) {
return delegate.getHeaderFieldDate(field, defaultValue);
}
@Override
public int getHeaderFieldInt(String field, int defaultValue) {
return delegate.getHeaderFieldInt(field, defaultValue);
}
@Override
public String getHeaderFieldKey(int posn) {
return delegate.getHeaderFieldKey(posn);
}
@Override
public long getIfModifiedSince() {
return delegate.getIfModifiedSince();
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public long getLastModified() {
return delegate.getLastModified();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public Permission getPermission() throws IOException {
return delegate.getPermission();
}
@Override
public String getRequestProperty(String field) {
return delegate.getRequestProperty(field);
}
@Override
public URL getURL() {
return delegate.getURL();
}
@Override
public boolean getUseCaches() {
return delegate.getUseCaches();
}
@Override
public void setAllowUserInteraction(boolean newValue) {
delegate.setAllowUserInteraction(newValue);
}
@Override
public void setDefaultUseCaches(boolean newValue) {
delegate.setDefaultUseCaches(newValue);
}
@Override
public void setDoInput(boolean newValue) {
delegate.setDoInput(newValue);
}
@Override
public void setDoOutput(boolean newValue) {
delegate.setDoOutput(newValue);
}
@Override
public void setIfModifiedSince(long newValue) {
delegate.setIfModifiedSince(newValue);
}
@Override
public void setRequestProperty(String field, String newValue) {
delegate.setRequestProperty(field, newValue);
}
@Override
public void setUseCaches(boolean newValue) {
delegate.setUseCaches(newValue);
}
@Override
public void setConnectTimeout(int setConnectTimeout) {
delegate.setConnectTimeout(setConnectTimeout);
}
@Override
public int getConnectTimeout() {
return delegate.getConnectTimeout();
}
@Override
public void setReadTimeout(int timeoutMillis) {
delegate.setReadTimeout(timeoutMillis);
}
@Override
public int getReadTimeout() {
return delegate.getReadTimeout();
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public void setFixedLengthStreamingMode(int contentLength) {
delegate.setFixedLengthStreamingMode(contentLength);
}
@Override
public void setChunkedStreamingMode(int chunkLength) {
delegate.setChunkedStreamingMode(chunkLength);
}
private final class HttpUrlConnectionDelegate extends HttpURLConnectionImpl {
private HttpUrlConnectionDelegate(URL url, int port) {
super(url, port);
}
private HttpUrlConnectionDelegate(URL url, int port, Proxy proxy) {
super(url, port, proxy);
}
@Override protected HttpEngine newHttpEngine(String method, RawHeaders requestHeaders,
HttpConnection connection, RetryableOutputStream requestBody) throws IOException {
return new HttpsEngine(this, method, requestHeaders, connection, requestBody,
HttpsURLConnectionImpl.this);
}
public SecureCacheResponse getCacheResponse() {
HttpsEngine engine = (HttpsEngine) httpEngine;
return engine != null ? (SecureCacheResponse) engine.getCacheResponse() : null;
}
public SSLSocket getSSLSocket() {
HttpsEngine engine = (HttpsEngine) httpEngine;
return engine != null ? engine.sslSocket : null;
}
}
private static class HttpsEngine extends HttpEngine {
/**
* Local stash of HttpsEngine.connection.sslSocket for answering
* queries such as getCipherSuite even after
* httpsEngine.Connection has been recycled. It's presence is also
* used to tell if the HttpsURLConnection is considered connected,
* as opposed to the connected field of URLConnection or the a
* non-null connect in HttpURLConnectionImpl
*/
private SSLSocket sslSocket;
private final HttpsURLConnectionImpl enclosing;
/**
* @param policy the HttpURLConnectionImpl with connection configuration
* @param enclosing the HttpsURLConnection with HTTPS features
*/
private HttpsEngine(HttpURLConnectionImpl policy, String method, RawHeaders requestHeaders,
HttpConnection connection, RetryableOutputStream requestBody,
HttpsURLConnectionImpl enclosing) throws IOException {
super(policy, method, requestHeaders, connection, requestBody);
this.sslSocket = connection != null ? connection.getSecureSocketIfConnected() : null;
this.enclosing = enclosing;
}
@Override protected void connect() throws IOException {
// first try an SSL connection with compression and
// various TLS extensions enabled, if it fails (and its
// not unheard of that it will) fallback to a more
// barebones connections
boolean connectionReused;
try {
connectionReused = makeSslConnection(true);
} catch (IOException e) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry, we didn't have an abrupt server initiated exception.
if (e instanceof SSLHandshakeException
&& e.getCause() instanceof CertificateException) {
throw e;
}
release(false);
connectionReused = makeSslConnection(false);
}
if (!connectionReused) {
sslSocket = connection.verifySecureSocketHostname(enclosing.getHostnameVerifier());
}
}
/**
* Attempt to make an https connection. Returns true if a
* connection was reused, false otherwise.
*
* @param tlsTolerant If true, assume server can handle common
* TLS extensions and SSL deflate compression. If false, use
* an SSL3 only fallback mode without compression.
*/
private boolean makeSslConnection(boolean tlsTolerant) throws IOException {
// make an SSL Tunnel on the first message pair of each SSL + proxy connection
if (connection == null) {
connection = openSocketConnection();
if (connection.getAddress().getProxy() != null) {
makeTunnel(policy, connection, getRequestHeaders());
}
}
// if super.makeConnection returned a connection from the
// pool, sslSocket needs to be initialized here. If it is
// a new connection, it will be initialized by
// getSecureSocket below.
sslSocket = connection.getSecureSocketIfConnected();
// we already have an SSL connection,
if (sslSocket != null) {
return true;
}
connection.setupSecureSocket(enclosing.getSSLSocketFactory(), tlsTolerant);
return false;
}
/**
* To make an HTTPS connection over an HTTP proxy, send an unencrypted
* CONNECT request to create the proxy connection. This may need to be
* retried if the proxy requires authorization.
*/
private void makeTunnel(HttpURLConnectionImpl policy, HttpConnection connection,
RequestHeaders requestHeaders) throws IOException {
RawHeaders rawRequestHeaders = requestHeaders.getHeaders();
while (true) {
HttpEngine connect = new ProxyConnectEngine(policy, rawRequestHeaders, connection);
connect.sendRequest();
connect.readResponse();
int responseCode = connect.getResponseCode();
switch (connect.getResponseCode()) {
case HTTP_OK:
return;
case HTTP_PROXY_AUTH:
rawRequestHeaders = new RawHeaders(rawRequestHeaders);
boolean credentialsFound = policy.processAuthHeader(HTTP_PROXY_AUTH,
connect.getResponseHeaders(), rawRequestHeaders);
if (credentialsFound) {
continue;
} else {
throw new IOException("Failed to authenticate with proxy");
}
default:
throw new IOException("Unexpected response code for CONNECT: " + responseCode);
}
}
}
@Override protected boolean acceptCacheResponseType(CacheResponse cacheResponse) {
return cacheResponse instanceof SecureCacheResponse;
}
@Override protected boolean includeAuthorityInRequestLine() {
// Even if there is a proxy, it isn't involved. Always request just the file.
return false;
}
// Android's HttpsUrlConnection has a getSslSocketFactory method, while Java's does not.
// So in Android, this is @Override, but here, it cannot be.
protected SSLSocketFactory getSslSocketFactory() {
return enclosing.getSSLSocketFactory();
}
@Override protected HttpURLConnection getHttpConnectionToCache() {
return enclosing;
}
}
private static class ProxyConnectEngine extends HttpEngine {
public ProxyConnectEngine(HttpURLConnectionImpl policy, RawHeaders requestHeaders,
HttpConnection connection) throws IOException {
super(policy, HttpEngine.CONNECT, requestHeaders, connection, null);
}
/**
* If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), send
* only the minimum set of headers. This avoids sending potentially
* sensitive data like HTTP cookies to the proxy unencrypted.
*/
@Override protected RawHeaders getNetworkRequestHeaders() throws IOException {
RequestHeaders privateHeaders = getRequestHeaders();
URL url = policy.getURL();
RawHeaders result = new RawHeaders();
result.setStatusLine("CONNECT " + url.getHost() + ":" + URLs.getEffectivePort(url)
+ " HTTP/1.1");
// Always set Host and User-Agent.
String host = privateHeaders.getHost();
if (host == null) {
host = getOriginAddress(url);
}
result.set("Host", host);
String userAgent = privateHeaders.getUserAgent();
if (userAgent == null) {
userAgent = getDefaultUserAgent();
}
result.set("User-Agent", userAgent);
// Copy over the Proxy-Authorization header if it exists.
String proxyAuthorization = privateHeaders.getProxyAuthorization();
if (proxyAuthorization != null) {
result.set("Proxy-Authorization", proxyAuthorization);
}
// Always set the Proxy-Connection to Keep-Alive for the benefit of
// HTTP/1.0 proxies like Squid.
result.set("Proxy-Connection", "Keep-Alive");
return result;
}
@Override protected boolean requiresTunnel() {
return true;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy