1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package com
.owncloud
.android
.oc_framework
.network
;
20 import java
.lang
.ref
.WeakReference
;
21 import java
.lang
.reflect
.InvocationTargetException
;
22 import java
.lang
.reflect
.Method
;
23 import java
.util
.concurrent
.atomic
.AtomicReference
;
25 import javax
.net
.ssl
.SSLSocket
;
27 import android
.util
.Log
;
31 * Enables the support of Server Name Indication if existing
32 * in the underlying network implementation.
34 * Build as a singleton.
36 * @author David A. Velasco
38 public class ServerNameIndicator
{
40 private static final String TAG
= ServerNameIndicator
.class.getSimpleName();
42 private static final AtomicReference
<ServerNameIndicator
> mSingleInstance
= new AtomicReference
<ServerNameIndicator
>();
44 private static final String METHOD_NAME
= "setHostname";
46 private final WeakReference
<Class
<?
>> mSSLSocketClassRef
;
47 private final WeakReference
<Method
> mSetHostnameMethodRef
;
51 * Private constructor, class is a singleton.
53 * @param sslSocketClass Underlying implementation class of {@link SSLSocket} used to connect with the server.
54 * @param setHostnameMethod Name of the method to call to enable the SNI support.
56 private ServerNameIndicator(Class
<?
> sslSocketClass
, Method setHostnameMethod
) {
57 mSSLSocketClassRef
= new WeakReference
<Class
<?
>>(sslSocketClass
);
58 mSetHostnameMethodRef
= (setHostnameMethod
== null
) ? null
: new WeakReference
<Method
>(setHostnameMethod
);
63 * Calls the {@code #setHostname(String)} method of the underlying implementation
64 * of {@link SSLSocket} if exists.
66 * Creates and initializes the single instance of the class when needed
68 * @param hostname The name of the server host of interest.
69 * @param sslSocket Client socket to connect with the server.
71 public static void setServerNameIndication(String hostname
, SSLSocket sslSocket
) {
72 final Method setHostnameMethod
= getMethod(sslSocket
);
73 if (setHostnameMethod
!= null
) {
75 setHostnameMethod
.invoke(sslSocket
, hostname
);
76 Log
.i(TAG
, "SNI done, hostname: " + hostname
);
78 } catch (IllegalArgumentException e
) {
79 Log
.e(TAG
, "Call to SSLSocket#setHost(String) failed ", e
);
81 } catch (IllegalAccessException e
) {
82 Log
.e(TAG
, "Call to SSLSocket#setHost(String) failed ", e
);
84 } catch (InvocationTargetException e
) {
85 Log
.e(TAG
, "Call to SSLSocket#setHost(String) failed ", e
);
88 Log
.i(TAG
, "SNI not supported");
94 * Gets the method to invoke trying to minimize the effective
95 * application of reflection.
97 * @param sslSocket Instance of the SSL socket to use in connection with server.
98 * @return Method to call to indicate the server name of interest to the server.
100 private static Method
getMethod(SSLSocket sslSocket
) {
101 final Class
<?
> sslSocketClass
= sslSocket
.getClass();
102 final ServerNameIndicator instance
= mSingleInstance
.get();
103 if (instance
== null
) {
104 return initFrom(sslSocketClass
);
106 } else if (instance
.mSSLSocketClassRef
.get() != sslSocketClass
) {
107 // the underlying class changed
108 return initFrom(sslSocketClass
);
110 } else if (instance
.mSetHostnameMethodRef
== null
) {
115 final Method cachedSetHostnameMethod
= instance
.mSetHostnameMethodRef
.get();
116 return (cachedSetHostnameMethod
== null
) ?
initFrom(sslSocketClass
) : cachedSetHostnameMethod
;
122 * Singleton initializer.
124 * Uses reflection to extract and 'cache' the method to invoke to indicate the desited host name to the server side.
126 * @param sslSocketClass Underlying class providing the implementation of {@link SSLSocket}.
127 * @return Method to call to indicate the server name of interest to the server.
129 private static Method
initFrom(Class
<?
> sslSocketClass
) {
130 Log
.i(TAG
, "SSLSocket implementation: " + sslSocketClass
.getCanonicalName());
131 Method setHostnameMethod
= null
;
133 setHostnameMethod
= sslSocketClass
.getMethod(METHOD_NAME
, String
.class);
134 } catch (SecurityException e
) {
135 Log
.e(TAG
, "Could not access to SSLSocket#setHostname(String) method ", e
);
137 } catch (NoSuchMethodException e
) {
138 Log
.i(TAG
, "Could not find SSLSocket#setHostname(String) method - SNI not supported");
140 mSingleInstance
.set(new ServerNameIndicator(sslSocketClass
, setHostnameMethod
));
141 return setHostnameMethod
;