MailTransport.java revision 51c653646d14d841fbe527aee9fab7a1886338f8
196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/*
296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project
396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License.
696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at
796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project *
1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and
1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License.
1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */
1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
1796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpackage com.android.email.mail.transport;
1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
19b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blankimport android.content.Context;
20b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank
2151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdonimport com.android.email.DebugUtils;
2231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging;
232193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.CertificateValidationException;
242193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException;
257d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.HostAuth;
263a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankimport com.android.emailcommon.utility.SSLUtils;
27560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils;
2896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
2996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedInputStream;
3096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedOutputStream;
3196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException;
3296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream;
3396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.OutputStream;
34f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onukiimport java.net.InetAddress;
3596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.InetSocketAddress;
3696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.Socket;
3796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketAddress;
3896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketException;
3996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
40fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HostnameVerifier;
41fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HttpsURLConnection;
4296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport javax.net.ssl.SSLException;
43fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLPeerUnverifiedException;
44fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSession;
45fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSocket;
4696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
47b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blankpublic class MailTransport {
488d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
4996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    // TODO protected eventually
5096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000;
5196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000;
5296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
53fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    private static final HostnameVerifier HOSTNAME_VERIFIER =
54fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            HttpsURLConnection.getDefaultHostnameVerifier();
55fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
567d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    private final String mDebugLabel;
577d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    private final Context mContext;
5817d3a29c9d8f7a27c463239f190bdcc4e0804527Jerry Xie    protected final HostAuth mHostAuth;
5996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
6096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private Socket mSocket;
6196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private InputStream mIn;
6296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    private OutputStream mOut;
6396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
647d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank    public MailTransport(Context context, String debugLabel, HostAuth hostAuth) {
6596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        super();
667d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        mContext = context;
6796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mDebugLabel = debugLabel;
687d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        mHostAuth = hostAuth;
6996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
708d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
717d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank   /**
72ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * Returns a new transport, using the current transport as a model. The new transport is
73ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * configured identically (as if {@link #setSecurity(int, boolean)}, {@link #setPort(int)}
74ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy     * and {@link #setHost(String)} were invoked), but not opened or connected in any way.
7596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
76ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy    @Override
77b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank    public MailTransport clone() {
787d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return new MailTransport(mContext, mDebugLabel, mHostAuth);
79a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy    }
80a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy
8196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public String getHost() {
827d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return mHostAuth.mAddress;
8396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public int getPort() {
867d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return mHostAuth.mPort;
8796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
8896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
8996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean canTrySslSecurity() {
907d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return (mHostAuth.mFlags & HostAuth.FLAG_SSL) != 0;
9196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
928d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
9396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean canTryTlsSecurity() {
947d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return (mHostAuth.mFlags & HostAuth.FLAG_TLS) != 0;
9596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
968d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
97e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler    public boolean canTrustAllCertificates() {
987d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank        return (mHostAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0;
99e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler    }
100e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler
10196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to open a connection using the Uri supplied for connection parameters.  Will attempt
10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * an SSL connection if indicated.
10496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
10596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void open() throws MessagingException, CertificateValidationException {
10651c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
107560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "*** " + mDebugLabel + " open " +
10896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                    getHost() + ":" + String.valueOf(getPort()));
10996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (canTrySslSecurity()) {
1147d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank                mSocket = SSLUtils.getSSLSocketFactory(
1157d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank                        mContext, mHostAuth, canTrustAllCertificates()).createSocket();
11696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket = new Socket();
11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
120fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            // After the socket connects to an SSL server, confirm that the hostname is as expected
121fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            if (canTrySslSecurity() && !canTrustAllCertificates()) {
122fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                verifyHostname(mSocket, getHost());
123fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            }
12496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
12596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
1269a2686afa2e02c62a8e5f9c42c82bd2da70b96afYu Ping Hu            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
12796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (SSLException e) {
12851c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
129560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, e.toString());
13096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
13196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new CertificateValidationException(e.getMessage(), e);
13296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
13351c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
134560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ioe.toString());
13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
137532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook        } catch (IllegalArgumentException iae) {
13851c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
139560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, iae.toString());
140532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook            }
141532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook            throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString());
14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
14496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
14596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Attempts to reopen a TLS connection using the Uri supplied for connection parameters.
14796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *
148fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * NOTE: No explicit hostname verification is required here, because it's handled automatically
149fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * by the call to createSocket().
150fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
15196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * TODO should we explicitly close the old socket?  This seems funky to abandon it.
15296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void reopenTls() throws MessagingException {
15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
1557d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank            mSocket = SSLUtils.getSSLSocketFactory(mContext, mHostAuth, canTrustAllCertificates())
156c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki                    .createSocket(mSocket, getHost(), getPort(), true);
15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
15996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
16096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
161e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler        } catch (SSLException e) {
16251c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
163560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, e.toString());
164e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            }
165e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler            throw new CertificateValidationException(e.getMessage(), e);
16696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (IOException ioe) {
16751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon            if (DebugUtils.DEBUG) {
168560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ioe.toString());
16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            throw new MessagingException(MessagingException.IOERROR, ioe.toString());
17196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
173fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
174fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    /**
175fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
176fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * service but is not in the public API.
177fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
178fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * Verify the hostname of the certificate used by the other end of a
179fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * connected socket.  You MUST call this if you did not supply a hostname
180fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * to SSLCertificateSocketFactory.createSocket().  It is harmless to call this method
181fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * redundantly if the hostname has already been verified.
182fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
183fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * <p>Wildcard certificates are allowed to verify any matching hostname,
184fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * so "foo.bar.example.com" is verified if the peer has a certificate
185fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * for "*.example.com".
186fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     *
187fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param socket An SSL socket which has been connected to a server
188fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @param hostname The expected hostname of the remote server
189fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws IOException if something goes wrong handshaking with the server
190fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler     * @throws SSLPeerUnverifiedException if the server cannot prove its identity
191fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler      */
1921b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy    private static void verifyHostname(Socket socket, String hostname) throws IOException {
193fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // The code at the start of OpenSSLSocketImpl.startHandshake()
194fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // ensures that the call is idempotent, so we can safely call it.
195fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSocket ssl = (SSLSocket) socket;
196fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        ssl.startHandshake();
197fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
198fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        SSLSession session = ssl.getSession();
199fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (session == null) {
200fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLException("Cannot verify SSL socket without session");
201fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
202fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // TODO: Instead of reporting the name of the server we think we're connecting to,
203fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // we should be reporting the bad name in the certificate.  Unfortunately this is buried
204fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // in the verifier code and is not available in the verifier API, and extracting the
205fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        // CN & alts is beyond the scope of this patch.
206fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
207fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler            throw new SSLPeerUnverifiedException(
208fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler                    "Certificate hostname not useable for server: " + hostname);
209fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler        }
210fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler    }
211fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler
21296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
2130c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     * Get the socket timeout.
2140c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     * @return the read timeout value in milliseconds
2150c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     * @throws SocketException
2160c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler     */
2170c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler    public int getSoTimeout() throws SocketException {
2180c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler        return mSocket.getSoTimeout();
2190c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler    }
2200c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler
2210c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler    /**
22296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Set the socket timeout.
22396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or
22496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     *            {@code 0} for an infinite timeout.
22596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
22696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void setSoTimeout(int timeoutMilliseconds) throws SocketException {
22796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket.setSoTimeout(timeoutMilliseconds);
22896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
22996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
23096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public boolean isOpen() {
2318d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy        return (mIn != null && mOut != null &&
23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                mSocket != null && mSocket.isConnected() && !mSocket.isClosed());
23396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
23496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
23596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
23696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Close the connection.  MUST NOT return any exceptions - must be "best effort" and safe.
23796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
23896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void close() {
23996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
24096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mIn.close();
24196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mOut.close();
24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        try {
25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            mSocket.close();
25196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        } catch (Exception e) {
25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            // May fail if the connection is already closed.
25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mIn = null;
25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mOut = null;
25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        mSocket = null;
25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public InputStream getInputStream() {
26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mIn;
26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public OutputStream getOutputStream() {
26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return mOut;
26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
2668d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Writes a single line to the server using \r\n termination.
26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    public void writeLine(String s, String sensitiveReplacement) throws IOException {
27151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (DebugUtils.DEBUG) {
272bfac9f2e8a13f6c719608a6948203bbef921c99fMakoto Onuki            if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
273560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
275560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy                LogUtils.d(Logging.LOG_TAG, ">>> " + s);
27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
2788d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        OutputStream out = getOutputStream();
28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write(s.getBytes());
28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\r');
28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.write('\n');
28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        out.flush();
28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
2858d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy
28696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    /**
28796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * Reads a single line from the server, using either \r\n or \n as the delimiter.  The
28896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     * delimiter char(s) are not included in the result.
28996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project     */
290b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank    public String readLine(boolean loggable) throws IOException {
29196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        StringBuffer sb = new StringBuffer();
29296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        InputStream in = getInputStream();
29396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        int d;
29496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        while ((d = in.read()) != -1) {
29596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            if (((char)d) == '\r') {
29696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                continue;
29796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else if (((char)d) == '\n') {
29896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                break;
29996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            } else {
30096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project                sb.append((char)d);
30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project            }
30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30351c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (d == -1 && DebugUtils.DEBUG) {
304560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "End of stream reached while trying to read line.");
30596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        String ret = sb.toString();
30751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon        if (loggable && DebugUtils.DEBUG) {
308560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy            LogUtils.d(Logging.LOG_TAG, "<<< " + ret);
30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        }
31096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project        return ret;
31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project    }
31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project
313f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    public InetAddress getLocalAddress() {
314f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        if (isOpen()) {
315f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return mSocket.getLocalAddress();
316f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        } else {
317f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki            return null;
318f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki        }
319f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki    }
32096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project}
321