summaryrefslogtreecommitdiffstats
path: root/android/src/android/net/http/HttpsConnection.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/src/android/net/http/HttpsConnection.java')
-rw-r--r--android/src/android/net/http/HttpsConnection.java433
1 files changed, 433 insertions, 0 deletions
diff --git a/android/src/android/net/http/HttpsConnection.java b/android/src/android/net/http/HttpsConnection.java
new file mode 100644
index 0000000..a8674de
--- /dev/null
+++ b/android/src/android/net/http/HttpsConnection.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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 android.net.http;
+
+import android.content.Context;
+import android.util.Log;
+import com.android.org.conscrypt.FileClientSessionCache;
+import com.android.org.conscrypt.OpenSSLContextImpl;
+import com.android.org.conscrypt.SSLClientSessionCache;
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpStatus;
+import org.apache.http.ParseException;
+import org.apache.http.ProtocolVersion;
+import org.apache.http.StatusLine;
+import org.apache.http.message.BasicHttpRequest;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.security.KeyManagementException;
+import java.security.cert.X509Certificate;
+import java.util.Locale;
+
+/**
+ * A Connection connecting to a secure http server or tunneling through
+ * a http proxy server to a https server.
+ *
+ * @hide
+ */
+public class HttpsConnection extends Connection {
+
+ /**
+ * SSL socket factory
+ */
+ private static SSLSocketFactory mSslSocketFactory = null;
+
+ static {
+ // This initialization happens in the zygote. It triggers some
+ // lazy initialization that can will benefit later invocations of
+ // initializeEngine().
+ initializeEngine(null);
+ }
+
+ /**
+ * @hide
+ *
+ * @param sessionDir directory to cache SSL sessions
+ */
+ public static void initializeEngine(File sessionDir) {
+ try {
+ SSLClientSessionCache cache = null;
+ if (sessionDir != null) {
+ Log.d("HttpsConnection", "Caching SSL sessions in "
+ + sessionDir + ".");
+ cache = FileClientSessionCache.usingDirectory(sessionDir);
+ }
+
+ OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
+
+ // here, trust managers is a single trust-all manager
+ TrustManager[] trustManagers = new TrustManager[] {
+ new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ public void checkClientTrusted(
+ X509Certificate[] certs, String authType) {
+ }
+
+ public void checkServerTrusted(
+ X509Certificate[] certs, String authType) {
+ }
+ }
+ };
+
+ sslContext.engineInit(null, trustManagers, null);
+ sslContext.engineGetClientSessionContext().setPersistentCache(cache);
+
+ synchronized (HttpsConnection.class) {
+ mSslSocketFactory = sslContext.engineGetSocketFactory();
+ }
+ } catch (KeyManagementException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private synchronized static SSLSocketFactory getSocketFactory() {
+ return mSslSocketFactory;
+ }
+
+ /**
+ * Object to wait on when suspending the SSL connection
+ */
+ private Object mSuspendLock = new Object();
+
+ /**
+ * True if the connection is suspended pending the result of asking the
+ * user about an error.
+ */
+ private boolean mSuspended = false;
+
+ /**
+ * True if the connection attempt should be aborted due to an ssl
+ * error.
+ */
+ private boolean mAborted = false;
+
+ // Used when connecting through a proxy.
+ private HttpHost mProxyHost;
+
+ /**
+ * Contructor for a https connection.
+ */
+ HttpsConnection(Context context, HttpHost host, HttpHost proxy,
+ RequestFeeder requestFeeder) {
+ super(context, host, requestFeeder);
+ mProxyHost = proxy;
+ }
+
+ /**
+ * Sets the server SSL certificate associated with this
+ * connection.
+ * @param certificate The SSL certificate
+ */
+ /* package */ void setCertificate(SslCertificate certificate) {
+ mCertificate = certificate;
+ }
+
+ /**
+ * Opens the connection to a http server or proxy.
+ *
+ * @return the opened low level connection
+ * @throws IOException if the connection fails for any reason.
+ */
+ @Override
+ AndroidHttpClientConnection openConnection(Request req) throws IOException {
+ SSLSocket sslSock = null;
+
+ if (mProxyHost != null) {
+ // If we have a proxy set, we first send a CONNECT request
+ // to the proxy; if the proxy returns 200 OK, we negotiate
+ // a secure connection to the target server via the proxy.
+ // If the request fails, we drop it, but provide the event
+ // handler with the response status and headers. The event
+ // handler is then responsible for cancelling the load or
+ // issueing a new request.
+ AndroidHttpClientConnection proxyConnection = null;
+ Socket proxySock = null;
+ try {
+ proxySock = new Socket
+ (mProxyHost.getHostName(), mProxyHost.getPort());
+
+ proxySock.setSoTimeout(60 * 1000);
+
+ proxyConnection = new AndroidHttpClientConnection();
+ HttpParams params = new BasicHttpParams();
+ HttpConnectionParams.setSocketBufferSize(params, 8192);
+
+ proxyConnection.bind(proxySock, params);
+ } catch(IOException e) {
+ if (proxyConnection != null) {
+ proxyConnection.close();
+ }
+
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to establish a connection to the proxy";
+ }
+
+ throw new IOException(errorMessage);
+ }
+
+ StatusLine statusLine = null;
+ int statusCode = 0;
+ Headers headers = new Headers();
+ try {
+ BasicHttpRequest proxyReq = new BasicHttpRequest
+ ("CONNECT", mHost.toHostString());
+
+ // add all 'proxy' headers from the original request, we also need
+ // to add 'host' header unless we want proxy to answer us with a
+ // 400 Bad Request
+ for (Header h : req.mHttpRequest.getAllHeaders()) {
+ String headerName = h.getName().toLowerCase(Locale.ROOT);
+ if (headerName.startsWith("proxy") || headerName.equals("keep-alive")
+ || headerName.equals("host")) {
+ proxyReq.addHeader(h);
+ }
+ }
+
+ proxyConnection.sendRequestHeader(proxyReq);
+ proxyConnection.flush();
+
+ // it is possible to receive informational status
+ // codes prior to receiving actual headers;
+ // all those status codes are smaller than OK 200
+ // a loop is a standard way of dealing with them
+ do {
+ statusLine = proxyConnection.parseResponseHeader(headers);
+ statusCode = statusLine.getStatusCode();
+ } while (statusCode < HttpStatus.SC_OK);
+ } catch (ParseException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to send a CONNECT request";
+ }
+
+ throw new IOException(errorMessage);
+ } catch (HttpException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to send a CONNECT request";
+ }
+
+ throw new IOException(errorMessage);
+ } catch (IOException e) {
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to send a CONNECT request";
+ }
+
+ throw new IOException(errorMessage);
+ }
+
+ if (statusCode == HttpStatus.SC_OK) {
+ try {
+ sslSock = (SSLSocket) getSocketFactory().createSocket(
+ proxySock, mHost.getHostName(), mHost.getPort(), true);
+ } catch(IOException e) {
+ if (sslSock != null) {
+ sslSock.close();
+ }
+
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage =
+ "failed to create an SSL socket";
+ }
+ throw new IOException(errorMessage);
+ }
+ } else {
+ // if the code is not OK, inform the event handler
+ ProtocolVersion version = statusLine.getProtocolVersion();
+
+ req.mEventHandler.status(version.getMajor(),
+ version.getMinor(),
+ statusCode,
+ statusLine.getReasonPhrase());
+ req.mEventHandler.headers(headers);
+ req.mEventHandler.endData();
+
+ proxyConnection.close();
+
+ // here, we return null to indicate that the original
+ // request needs to be dropped
+ return null;
+ }
+ } else {
+ // if we do not have a proxy, we simply connect to the host
+ try {
+ sslSock = (SSLSocket) getSocketFactory().createSocket(
+ mHost.getHostName(), mHost.getPort());
+ sslSock.setSoTimeout(SOCKET_TIMEOUT);
+ } catch(IOException e) {
+ if (sslSock != null) {
+ sslSock.close();
+ }
+
+ String errorMessage = e.getMessage();
+ if (errorMessage == null) {
+ errorMessage = "failed to create an SSL socket";
+ }
+
+ throw new IOException(errorMessage);
+ }
+ }
+
+ // do handshake and validate server certificates
+ SslError error = CertificateChainValidator.getInstance().
+ doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
+
+ // Inform the user if there is a problem
+ if (error != null) {
+ // handleSslErrorRequest may immediately unsuspend if it wants to
+ // allow the certificate anyway.
+ // So we mark the connection as suspended, call handleSslErrorRequest
+ // then check if we're still suspended and only wait if we actually
+ // need to.
+ synchronized (mSuspendLock) {
+ mSuspended = true;
+ }
+ // don't hold the lock while calling out to the event handler
+ boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
+ if(!canHandle) {
+ throw new IOException("failed to handle "+ error);
+ }
+ synchronized (mSuspendLock) {
+ if (mSuspended) {
+ try {
+ // Put a limit on how long we are waiting; if the timeout
+ // expires (which should never happen unless you choose
+ // to ignore the SSL error dialog for a very long time),
+ // we wake up the thread and abort the request. This is
+ // to prevent us from stalling the network if things go
+ // very bad.
+ mSuspendLock.wait(10 * 60 * 1000);
+ if (mSuspended) {
+ // mSuspended is true if we have not had a chance to
+ // restart the connection yet (ie, the wait timeout
+ // has expired)
+ mSuspended = false;
+ mAborted = true;
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpsConnection.openConnection():" +
+ " SSL timeout expired and request was cancelled!!!");
+ }
+ }
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+ if (mAborted) {
+ // The user decided not to use this unverified connection
+ // so close it immediately.
+ sslSock.close();
+ throw new SSLConnectionClosedByUserException("connection closed by the user");
+ }
+ }
+ }
+
+ // All went well, we have an open, verified connection.
+ AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
+ BasicHttpParams params = new BasicHttpParams();
+ params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
+ conn.bind(sslSock, params);
+
+ return conn;
+ }
+
+ /**
+ * Closes the low level connection.
+ *
+ * If an exception is thrown then it is assumed that the connection will
+ * have been closed (to the extent possible) anyway and the caller does not
+ * need to take any further action.
+ *
+ */
+ @Override
+ void closeConnection() {
+ // if the connection has been suspended due to an SSL error
+ if (mSuspended) {
+ // wake up the network thread
+ restartConnection(false);
+ }
+
+ try {
+ if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
+ mHttpClientConnection.close();
+ }
+ } catch (IOException e) {
+ if (HttpLog.LOGV)
+ HttpLog.v("HttpsConnection.closeConnection():" +
+ " failed closing connection " + mHost);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Restart a secure connection suspended waiting for user interaction.
+ */
+ void restartConnection(boolean proceed) {
+ if (HttpLog.LOGV) {
+ HttpLog.v("HttpsConnection.restartConnection():" +
+ " proceed: " + proceed);
+ }
+
+ synchronized (mSuspendLock) {
+ if (mSuspended) {
+ mSuspended = false;
+ mAborted = !proceed;
+ mSuspendLock.notify();
+ }
+ }
+ }
+
+ @Override
+ String getScheme() {
+ return "https";
+ }
+}
+
+/**
+ * Simple exception we throw if the SSL connection is closed by the user.
+ *
+ * {@hide}
+ */
+class SSLConnectionClosedByUserException extends SSLException {
+
+ public SSLConnectionClosedByUserException(String reason) {
+ super(reason);
+ }
+}