1a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath/*
2a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * Copyright (C) 2007 The Android Open Source Project
3a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath *
4a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * Licensed under the Apache License, Version 2.0 (the "License");
5a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * you may not use this file except in compliance with the License.
6a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * You may obtain a copy of the License at
7a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath *
8a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath *      http://www.apache.org/licenses/LICENSE-2.0
9a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath *
10a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * Unless required by applicable law or agreed to in writing, software
11a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * distributed under the License is distributed on an "AS IS" BASIS,
12a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * See the License for the specific language governing permissions and
14a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * limitations under the License.
15a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath */
16a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
17a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathpackage android.net.http;
18a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
19a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport android.content.Context;
20a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport android.util.Log;
21a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport com.android.org.conscrypt.FileClientSessionCache;
22a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport com.android.org.conscrypt.OpenSSLContextImpl;
23a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport com.android.org.conscrypt.SSLClientSessionCache;
24a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.Header;
25a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.HttpException;
26a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.HttpHost;
27a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.HttpStatus;
28a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.ParseException;
29a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.ProtocolVersion;
30a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.StatusLine;
31a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.message.BasicHttpRequest;
32a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.params.BasicHttpParams;
33a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.params.HttpConnectionParams;
34a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport org.apache.http.params.HttpParams;
35a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
36a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport javax.net.ssl.SSLException;
37a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport javax.net.ssl.SSLSocket;
38a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport javax.net.ssl.SSLSocketFactory;
39a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport javax.net.ssl.TrustManager;
40a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport javax.net.ssl.X509TrustManager;
41a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport java.io.File;
42a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport java.io.IOException;
43a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport java.net.Socket;
44a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport java.security.KeyManagementException;
45a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport java.security.cert.X509Certificate;
46a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathimport java.util.Locale;
47a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
48a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath/**
49a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * A Connection connecting to a secure http server or tunneling through
50a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * a http proxy server to a https server.
51a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath */
52a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathpublic class HttpsConnection extends Connection {
53a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
54a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
55a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * SSL socket factory
56a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
57a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    private static SSLSocketFactory mSslSocketFactory = null;
58a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
59a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    static {
60a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // This initialization happens in the zygote. It triggers some
61a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // lazy initialization that can will benefit later invocations of
62a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // initializeEngine().
63a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        initializeEngine(null);
64a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
65a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
66a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
67a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * @param sessionDir directory to cache SSL sessions
68a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
69a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    public static void initializeEngine(File sessionDir) {
70a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        try {
71a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            SSLClientSessionCache cache = null;
72a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            if (sessionDir != null) {
73a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                Log.d("HttpsConnection", "Caching SSL sessions in "
74a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        + sessionDir + ".");
75a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                cache = FileClientSessionCache.usingDirectory(sessionDir);
76a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
77a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
78a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            OpenSSLContextImpl sslContext = OpenSSLContextImpl.getPreferred();
79a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
80a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // here, trust managers is a single trust-all manager
81a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            TrustManager[] trustManagers = new TrustManager[] {
82a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                new X509TrustManager() {
83a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    public X509Certificate[] getAcceptedIssuers() {
84a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        return null;
85a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
86a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
87a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    public void checkClientTrusted(
88a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        X509Certificate[] certs, String authType) {
89a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
90a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
91a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    public void checkServerTrusted(
92a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        X509Certificate[] certs, String authType) {
93a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
94a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
95a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            };
96a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
97a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            sslContext.engineInit(null, trustManagers, null);
98a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            sslContext.engineGetClientSessionContext().setPersistentCache(cache);
99a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
100a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            synchronized (HttpsConnection.class) {
101a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                mSslSocketFactory = sslContext.engineGetSocketFactory();
102a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
103a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        } catch (KeyManagementException e) {
104a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            throw new RuntimeException(e);
105a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        } catch (IOException e) {
106a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            throw new RuntimeException(e);
107a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
108a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
109a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
110a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    private synchronized static SSLSocketFactory getSocketFactory() {
111a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        return mSslSocketFactory;
112a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
113a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
114a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
115a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * Object to wait on when suspending the SSL connection
116a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
117a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    private Object mSuspendLock = new Object();
118a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
119a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
120a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * True if the connection is suspended pending the result of asking the
121a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * user about an error.
122a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
123a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    private boolean mSuspended = false;
124a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
125a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
126a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * True if the connection attempt should be aborted due to an ssl
127a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * error.
128a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
129a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    private boolean mAborted = false;
130a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
131a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    // Used when connecting through a proxy.
132a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    private HttpHost mProxyHost;
133a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
134a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
135a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * Contructor for a https connection.
136a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
137a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    HttpsConnection(Context context, HttpHost host, HttpHost proxy,
138a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    RequestFeeder requestFeeder) {
139a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        super(context, host, requestFeeder);
140a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        mProxyHost = proxy;
141a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
142a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
143a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
144a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * Sets the server SSL certificate associated with this
145a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * connection.
146a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * @param certificate The SSL certificate
147a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
148a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /* package */ void setCertificate(SslCertificate certificate) {
149a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        mCertificate = certificate;
150a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
151a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
152a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
153a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * Opens the connection to a http server or proxy.
154a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     *
155a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * @return the opened low level connection
156a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * @throws IOException if the connection fails for any reason.
157a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
158a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    @Override
159a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    AndroidHttpClientConnection openConnection(Request req) throws IOException {
160a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        SSLSocket sslSock = null;
161a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
162a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        if (mProxyHost != null) {
163a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // If we have a proxy set, we first send a CONNECT request
164a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // to the proxy; if the proxy returns 200 OK, we negotiate
165a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // a secure connection to the target server via the proxy.
166a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // If the request fails, we drop it, but provide the event
167a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // handler with the response status and headers. The event
168a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // handler is then responsible for cancelling the load or
169a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // issueing a new request.
170a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            AndroidHttpClientConnection proxyConnection = null;
171a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            Socket proxySock = null;
172a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            try {
173a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxySock = new Socket
174a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    (mProxyHost.getHostName(), mProxyHost.getPort());
175a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
176a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxySock.setSoTimeout(60 * 1000);
177a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
178a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxyConnection = new AndroidHttpClientConnection();
179a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                HttpParams params = new BasicHttpParams();
180a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                HttpConnectionParams.setSocketBufferSize(params, 8192);
181a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
182a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxyConnection.bind(proxySock, params);
183a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            } catch(IOException e) {
184a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (proxyConnection != null) {
185a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    proxyConnection.close();
186a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
187a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
188a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                String errorMessage = e.getMessage();
189a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (errorMessage == null) {
190a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    errorMessage =
191a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        "failed to establish a connection to the proxy";
192a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
193a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
194a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                throw new IOException(errorMessage);
195a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
196a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
197a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            StatusLine statusLine = null;
198a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            int statusCode = 0;
199a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            Headers headers = new Headers();
200a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            try {
201a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                BasicHttpRequest proxyReq = new BasicHttpRequest
202a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    ("CONNECT", mHost.toHostString());
203a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
204a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // add all 'proxy' headers from the original request, we also need
205a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // to add 'host' header unless we want proxy to answer us with a
206a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // 400 Bad Request
207a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                for (Header h : req.mHttpRequest.getAllHeaders()) {
208a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    String headerName = h.getName().toLowerCase(Locale.ROOT);
209a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    if (headerName.startsWith("proxy") || headerName.equals("keep-alive")
210a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            || headerName.equals("host")) {
211a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        proxyReq.addHeader(h);
212a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
213a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
214a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
215a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxyConnection.sendRequestHeader(proxyReq);
216a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxyConnection.flush();
217a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
218a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // it is possible to receive informational status
219a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // codes prior to receiving actual headers;
220a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // all those status codes are smaller than OK 200
221a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // a loop is a standard way of dealing with them
222a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                do {
223a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    statusLine = proxyConnection.parseResponseHeader(headers);
224a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    statusCode = statusLine.getStatusCode();
225a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                } while (statusCode < HttpStatus.SC_OK);
226a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            } catch (ParseException e) {
227a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                String errorMessage = e.getMessage();
228a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (errorMessage == null) {
229a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    errorMessage =
230a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        "failed to send a CONNECT request";
231a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
232a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
233a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                throw new IOException(errorMessage);
234a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            } catch (HttpException e) {
235a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                String errorMessage = e.getMessage();
236a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (errorMessage == null) {
237a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    errorMessage =
238a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        "failed to send a CONNECT request";
239a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
240a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
241a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                throw new IOException(errorMessage);
242a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            } catch (IOException e) {
243a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                String errorMessage = e.getMessage();
244a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (errorMessage == null) {
245a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    errorMessage =
246a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        "failed to send a CONNECT request";
247a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
248a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
249a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                throw new IOException(errorMessage);
250a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
251a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
252a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            if (statusCode == HttpStatus.SC_OK) {
253a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                try {
254a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    sslSock = (SSLSocket) getSocketFactory().createSocket(
255a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            proxySock, mHost.getHostName(), mHost.getPort(), true);
256a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                } catch(IOException e) {
257a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    if (sslSock != null) {
258a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        sslSock.close();
259a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
260a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
261a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    String errorMessage = e.getMessage();
262a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    if (errorMessage == null) {
263a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        errorMessage =
264a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            "failed to create an SSL socket";
265a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
266a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    throw new IOException(errorMessage);
267a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
268a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            } else {
269a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // if the code is not OK, inform the event handler
270a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                ProtocolVersion version = statusLine.getProtocolVersion();
271a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
272a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                req.mEventHandler.status(version.getMajor(),
273a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                                         version.getMinor(),
274a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                                         statusCode,
275a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                                         statusLine.getReasonPhrase());
276a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                req.mEventHandler.headers(headers);
277a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                req.mEventHandler.endData();
278a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
279a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                proxyConnection.close();
280a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
281a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // here, we return null to indicate that the original
282a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                // request needs to be dropped
283a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                return null;
284a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
285a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        } else {
286a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // if we do not have a proxy, we simply connect to the host
287a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            try {
288a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                sslSock = (SSLSocket) getSocketFactory().createSocket(
289a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        mHost.getHostName(), mHost.getPort());
290a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                sslSock.setSoTimeout(SOCKET_TIMEOUT);
291a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            } catch(IOException e) {
292a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (sslSock != null) {
293a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    sslSock.close();
294a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
295a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
296a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                String errorMessage = e.getMessage();
297a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (errorMessage == null) {
298a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    errorMessage = "failed to create an SSL socket";
299a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
300a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
301a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                throw new IOException(errorMessage);
302a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
303a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
304a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
305a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // do handshake and validate server certificates
306a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        SslError error = CertificateChainValidator.getInstance().
307a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());
308a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
309a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // Inform the user if there is a problem
310a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        if (error != null) {
311a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // handleSslErrorRequest may immediately unsuspend if it wants to
312a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // allow the certificate anyway.
313a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // So we mark the connection as suspended, call handleSslErrorRequest
314a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // then check if we're still suspended and only wait if we actually
315a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // need to.
316a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            synchronized (mSuspendLock) {
317a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                mSuspended = true;
318a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
319a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // don't hold the lock while calling out to the event handler
320a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
321a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            if(!canHandle) {
322a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                throw new IOException("failed to handle "+ error);
323a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
324a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            synchronized (mSuspendLock) {
325a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (mSuspended) {
326a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    try {
327a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // Put a limit on how long we are waiting; if the timeout
328a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // expires (which should never happen unless you choose
329a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // to ignore the SSL error dialog for a very long time),
330a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // we wake up the thread and abort the request. This is
331a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // to prevent us from stalling the network if things go
332a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // very bad.
333a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        mSuspendLock.wait(10 * 60 * 1000);
334a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        if (mSuspended) {
335a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            // mSuspended is true if we have not had a chance to
336a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            // restart the connection yet (ie, the wait timeout
337a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            // has expired)
338a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            mSuspended = false;
339a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            mAborted = true;
340a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            if (HttpLog.LOGV) {
341a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                                HttpLog.v("HttpsConnection.openConnection():" +
342a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                                          " SSL timeout expired and request was cancelled!!!");
343a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                            }
344a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        }
345a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    } catch (InterruptedException e) {
346a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                        // ignore
347a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    }
348a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
349a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                if (mAborted) {
350a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    // The user decided not to use this unverified connection
351a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    // so close it immediately.
352a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    sslSock.close();
353a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                    throw new SSLConnectionClosedByUserException("connection closed by the user");
354a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                }
355a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
356a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
357a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
358a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // All went well, we have an open, verified connection.
359a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
360a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        BasicHttpParams params = new BasicHttpParams();
361a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
362a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        conn.bind(sslSock, params);
363a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
364a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        return conn;
365a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
366a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
367a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
368a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * Closes the low level connection.
369a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     *
370a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * If an exception is thrown then it is assumed that the connection will
371a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * have been closed (to the extent possible) anyway and the caller does not
372a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * need to take any further action.
373a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     *
374a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
375a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    @Override
376a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    void closeConnection() {
377a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        // if the connection has been suspended due to an SSL error
378a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        if (mSuspended) {
379a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            // wake up the network thread
380a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            restartConnection(false);
381a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
382a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
383a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        try {
384a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) {
385a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                mHttpClientConnection.close();
386a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
387a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        } catch (IOException e) {
388a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            if (HttpLog.LOGV)
389a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                HttpLog.v("HttpsConnection.closeConnection():" +
390a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                          " failed closing connection " + mHost);
391a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            e.printStackTrace();
392a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
393a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
394a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
395a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    /**
396a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     * Restart a secure connection suspended waiting for user interaction.
397a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath     */
398a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    void restartConnection(boolean proceed) {
399a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        if (HttpLog.LOGV) {
400a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            HttpLog.v("HttpsConnection.restartConnection():" +
401a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                      " proceed: " + proceed);
402a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
403a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
404a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        synchronized (mSuspendLock) {
405a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            if (mSuspended) {
406a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                mSuspended = false;
407a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                mAborted = !proceed;
408a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath                mSuspendLock.notify();
409a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath            }
410a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        }
411a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
412a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
413a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    @Override
414a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    String getScheme() {
415a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        return "https";
416a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
417a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath}
418a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
419a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath/**
420a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath * Simple exception we throw if the SSL connection is closed by the user.
421a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath *
422a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath */
423a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamathclass SSLConnectionClosedByUserException extends SSLException {
424a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath
425a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    public SSLConnectionClosedByUserException(String reason) {
426a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath        super(reason);
427a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath    }
428a8b46a3d3b6ed1488df10740653829283572903bNarayan Kamath}
429