]>
Commit | Line | Data |
---|---|---|
1 | package ro.ieval.fonbot; | |
2 | ||
3 | import static ro.ieval.fonbot.R.string.*; | |
4 | import static ro.ieval.fonbot.Utils.toNonNull; | |
5 | ||
6 | import java.io.InputStream; | |
7 | import java.io.OutputStream; | |
8 | import java.net.URL; | |
9 | import java.security.cert.CertificateException; | |
10 | import java.security.cert.X509Certificate; | |
11 | import java.util.Arrays; | |
12 | import java.util.Collection; | |
13 | ||
14 | import javax.net.ssl.HttpsURLConnection; | |
15 | import javax.net.ssl.SSLContext; | |
16 | import javax.net.ssl.SSLSocketFactory; | |
17 | import javax.net.ssl.X509TrustManager; | |
18 | ||
19 | import org.eclipse.jdt.annotation.Nullable; | |
20 | ||
21 | import android.content.Context; | |
22 | import android.preference.PreferenceManager; | |
23 | import android.util.Base64; | |
24 | import android.util.Log; | |
25 | ||
26 | import com.google.android.gcm.GCMRegistrar; | |
27 | ||
28 | /* | |
29 | * Copyright © 2013 Marius Gavrilescu | |
30 | * | |
31 | * This file is part of FonBot. | |
32 | * | |
33 | * FonBot is free software: you can redistribute it and/or modify | |
34 | * it under the terms of the GNU General Public License as published by | |
35 | * the Free Software Foundation, either version 3 of the License, or | |
36 | * (at your option) any later version. | |
37 | * | |
38 | * FonBot is distributed in the hope that it will be useful, | |
39 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
40 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
41 | * GNU General Public License for more details. | |
42 | * | |
43 | * You should have received a copy of the GNU General Public License | |
44 | * along with FonBot. If not, see <http://www.gnu.org/licenses/>. | |
45 | */ | |
46 | ||
47 | /** | |
48 | * ExecutableRunnable that makes a HTTPS call to the server and hands the response to a callback | |
49 | * | |
50 | * @author Marius Gavrilescu <marius@ieval.ro> | |
51 | */ | |
52 | public final class HttpCallExecutableRunnable extends ExecutableRunnable{ | |
53 | /** | |
54 | * X509TrustManager that trusts any certificate | |
55 | * | |
56 | * @author Marius Gavrilescu | |
57 | */ | |
58 | private static final class TotallyInsecureTrustManager implements X509TrustManager { | |
59 | @Override | |
60 | public @Nullable X509Certificate[] getAcceptedIssuers() { | |
61 | return new X509Certificate[0]; | |
62 | } | |
63 | ||
64 | @Override | |
65 | public void checkServerTrusted(final @Nullable X509Certificate[] chain, final @Nullable String authType) | |
66 | throws CertificateException { | |
67 | //do nothing | |
68 | } | |
69 | ||
70 | @Override | |
71 | public void checkClientTrusted(final @Nullable X509Certificate[] chain, final @Nullable String authType) | |
72 | throws CertificateException { | |
73 | //do nothing | |
74 | } | |
75 | } | |
76 | ||
77 | /** | |
78 | * X509TrustManager that trusts self-signed certificates with a certain public key. | |
79 | * | |
80 | * It is used by default when the server hostname is 'fonbot.ieval.ro' | |
81 | * | |
82 | * @author Marius Gavrilescu <marius@ieval.ro> | |
83 | */ | |
84 | private static final class IEvalTrustManager implements X509TrustManager{ | |
85 | /** The iEval public key */ | |
86 | private static final byte[] pubkey={-66, -35, -47, -78, 8, 14, -118, -49, 72, 37, -106, 122, 102, 124, -58, -38, 107, -124, 104, -70, -53, -126, -121, 125, -117, -121, -70, -61, -83, 114, -93, -95, -26, 26, 75, 114, -101, 77, -83, -91, -64, -79, 26, -15, 46, -101, -3, 111, -94, -105, 92, -79, -61, 45, -37, -4, -23, -37, -77, 59, 98, -22, -87, 28, -86, 124, 105, -21, 66, 24, -15, -101, 30, 98, -109, -76, 81, 27, -65, -21, 90, -22, 70, 100, -63, 75, -67, -11, -69, 65, 115, 44, -70, -31, 21, 96, -114, -72, 7, -20, 99, 2, -13, 43, 107, 99, -94, -69, 17, -48, 2, 87, -55, 2, -24, 47, 74, 23, -123, -120, -17, 116, 7, -86, -8, -9, 55, 1, 126, -12, 59, -4, 76, 31, -35, 99, 46, 63, -125, 13, 103, 49, -103, -118, 106, -69, -68, 33, 98, 84, 31, 45, 120, -26, 12, -114, -7, 54, -114, 60, -66, -105, -6, 108, -38, -37, 30, 119, -2, -101, -75, 127, 11, -8, 110, -83, 34, -97, -6, 33, -18, 86, 38, -75, 52, -114, -113, 57, 97, 38, -64, -6, -55, 81, -15, 92, -111, 13, 127, 123, 54, 95, -66, -8, 124, 76, 37, -64, 54, -126, -8, -61, -78, 10, -89, -79, 5, 58, -47, -52, 59, -92, -118, 114, 98, 51, 78, 77, -93, 19, -46, -108, 25, -27, -57, -78, -48, -104, -18, -57, -126, 55, 12, 117, -93, -60, -5, -6, 53, -104, 49, 108, -18, 43, 71, 28, -99, 70, -81, -3, 33, -7, -69, -72, -11, 33, 79, -59, -25, 55, -54, -118, -52, -44, 73, 75, -35, 30, 24, -67, 95, -12, -16, -104, -88, 94, 106, -18, -47, -87, -13, -79, 127, -70, 66, 61, -98, -62, 124, -51, 13, 93, -72, -100, -15, -97, 112, -94, -109, 3, -86, -72, 5, -18, -3, 103, 58, -6, 102, -10, -54, 89, -51, 12, -15, -42, -112, -14, -49, -102, -55, 42, -78, -28, -46, 15, -14, -90, -27, 56, 126, 26, -53, 83, 98, 87, 97, 86, -73, -64, 56, -86, 92, 2, 72, -69, 43, -6, -72, -22, -38, -100, -115, -64, -11, 115, 102, -66, 33, 120, -106, -41, -7, -107, 12, -13, -113, 116, -21, 61, -36, 40, 2, -3, -56, -93, -24, 32, 73, -47, 9, -110, 56, -120, -45, -104, 8, 1, 46, -93, -64, -76, -39, -19, 27, 81, -90, -110, -22, 80, 67, -45, 12, 63, -55, -117, -28, -41, -76, 31, 46, -19, 15, 58, 81, -113, 26, -76, -16, -93, -5, 59, -12, 85, -4, 100, -12, -93, 70, 73, 96, -12, -76, 120, 29, -113, 4, 56, 48, 67, -26, -45, 83, 9, -57, -111, 82, 75, 88, 58, 71, 72, -114, -53, 42, -5, 97, 115, 36, -17, 55, -7, -109, -117, -20, 45, -51, -96, -106, -3, 95, 28, 39, 91, -19, 100, -24, -104, 81, -28, 9, -16, -38, 104, -96, -53, 107, -50, -24, -92, 41, 61, 26, -82, 108, 29, 2, -112, 6, -9, -125, 110, 26, -117, 99, 77, 19, -8, 31, 82, 110, 116, -5, -91, 74, -56, 6, 50, -43, 32, -104, 105, 82, 51, 61, -58, -72, 77, 111, -66, 11, 30, -75, 82, -9, 11, 26, 25, -72, 116, -92, 51, 61, -6, 108, -38, -72, -41, 42, 69, -9, 19, 123, 68, -28, 84, -89, -20, -120, -113, 66, 59, 102, -106, 6, -26, 1, 61, -109, -123, -50, -23, 33, 79, -43, -9, 3, 86, -25, -16, 10, 70, 80, -76, -99, -12, -19, 15, 1, 114, -2, 42, -63, -31, 97, -66, -39, 125, 125, 122, 62, -78, 61, 26, -47, -120, -75, -98, 79, -61, -93, 28, -61, -30, -19, 44, -41, -32, -96, -34, -23, -76, -21, 3, -91, -66, -69, 109, -52, -75, 20, 85, 51, -85, -76, -98, 77, 56, 40, -114, 52, 101, -91, -116, 101, -119, -117, 52, 127, 31, -32, 21, -114, -66, 52, -114, 15, -47, 78, 75, 10, 37, -9, 50, -49, -55, -41, 32, 81, 17, 78, -3, 85, -92, -41, -53, -80, -23, -64, 86, -91, 123, 108, 29, -89, 58, 77, 81, 77, 102, -115, 126, -71, 127, -116, 1, 38, -110, -110, 88, 93, -69, 1, 57, 82, 19, 121, -127, 50, 56, 34, -8, -67, 22, -122, -33, -27, -32, 42, -29, -123, -81, -8, -62, -103, 63, -12, 75, -100, 55, 21, 81, -99, -105, 99, 26, -27, -124, -78, -2, 23, -2, 29, 45, -113, 9, 19, -81, -53, 5, -44, -120, 100, 86, 81, -106, 95, -55, -125, -20, 58, -37, -17, -27, -67, 7, 5, -123, 7, -6, 96, 17, -6, -31, -66, -43, 65, -46, -56, 40, -73, 98, 124, -48, 109, -59, -44, -92, 16, -62, -69, -31, -111, -92, -91, -17, -84, -105, 103, 94, 33, 100, 104, 88, 8, -106, 106, 98, 97, -100, 24, -51, -99, 73, -90, -109, 75, 57, -78, -52, 66, 11, 49, 16, -10, -80, 13, 81, -60, 27, -5, 4, -102, 13, 7, 14, 116, -96, -13, 85, -82, 50, 16, -59, 87, -71, 62, -61, 97, -107, -6, -98, 31, 118, -22, 55, -125, -120, 105, -120, 101, 61, -46, -19, -104, 78, 102, -121, 84, -97, 22, 45, 8, 63, -52, 25, 118, 30, 38, 7, 99, 102, -89, -51, 7, 4, -126, 36, 89, -110, -90, 33, 16, 127, -16, 109, 116, 45, -8, -96, -31, 114, -15, -116, 121, 55, -30, 95, -44, -53, -7, -124, -94, 62, -9, 99, 101, -38, 23, -59, 127, 6, -63, 81, 73, -12, 39, 2, -55, 94, -54, 75, -92, 36, 102, -26, -77, 32, -127, -108, -98, -41, -66, 11, 30, 106, -91, 115, -67, 125, -124, -53, -121, 66, -114, 84, 111, 46, -1, -114, -61, -55, 45, 106, 119, 88, 19, -105, 5, 124, 85, 94, -99, -42, 20, 37, -28, -40, -116, 106, 10, 80, -86, -65, 12, -93, 35, 117, 87, -75, -91, -57, -118, -126, 2, 12, 10, -33, 83, -52, -80, -38, 68, -80, -24, 95, 107, 117, 113, -121, -14, 120, 78, 84}; | |
87 | ||
88 | @Override | |
89 | public void checkClientTrusted(final @Nullable X509Certificate[] chain, final @Nullable String authType) | |
90 | throws CertificateException { | |
91 | //do nothing | |
92 | } | |
93 | ||
94 | @Override | |
95 | public void checkServerTrusted(final @Nullable X509Certificate[] chain, final @Nullable String authType) | |
96 | throws CertificateException { | |
97 | if(chain == null) | |
98 | throw new CertificateException("Certificate chain is null"); | |
99 | if(chain.length>1) | |
100 | throw new CertificateException("Certificate chain length is greater than 1"); | |
101 | if(!Arrays.equals(pubkey, chain[0].getSignature())) | |
102 | throw new CertificateException("Certificate signature is incorrect"); | |
103 | } | |
104 | ||
105 | @Override | |
106 | public X509Certificate[] getAcceptedIssuers() { | |
107 | return new X509Certificate[0]; | |
108 | } | |
109 | ||
110 | } | |
111 | /** | |
112 | * Callback which is run after a HTTP call. | |
113 | * | |
114 | * @author Marius Gavrilescu | |
115 | */ | |
116 | public static interface ResultCallback{ | |
117 | /** | |
118 | * Callback invoked if the HTTP call is successful. | |
119 | * | |
120 | * @param responseCode HTTP response code | |
121 | * @param responseMessage HTTP response message | |
122 | * @param inputStream HTTP content InputStream | |
123 | */ | |
124 | public void onResult(final int responseCode, final String responseMessage, final InputStream inputStream); | |
125 | /** | |
126 | * Callback invoked if the HTTP call is unsuccessful. | |
127 | * | |
128 | * @param error localized error message | |
129 | */ | |
130 | public void onError(final String error); | |
131 | } | |
132 | ||
133 | /** SSLSocketFactory that uses {@link TotallyInsecureTrustManager} */ | |
134 | private static final SSLSocketFactory DEFAULT_SOCKET_FACTORY; | |
135 | /** SSLSocketFactory that uses {@link IEvalTrustManager} */ | |
136 | private static final SSLSocketFactory IEVAL_SOCKET_FACTORY; | |
137 | static{ | |
138 | try{ | |
139 | { | |
140 | final SSLContext sslcontext=SSLContext.getInstance("TLS"); | |
141 | sslcontext.init(null, new X509TrustManager[]{new TotallyInsecureTrustManager()}, null); | |
142 | DEFAULT_SOCKET_FACTORY=sslcontext.getSocketFactory(); | |
143 | } | |
144 | { | |
145 | final SSLContext sslcontext=SSLContext.getInstance("TLS"); | |
146 | sslcontext.init(null, new X509TrustManager[]{new IEvalTrustManager()}, null); | |
147 | IEVAL_SOCKET_FACTORY=sslcontext.getSocketFactory(); | |
148 | } | |
149 | } catch(final Exception e){ | |
150 | Log.wtf("HttpCallExecutableRunnable", "Cannot create SSLSocketFactory", e); | |
151 | throw new AssertionError("Log.wtf did not terminate the process"); | |
152 | } | |
153 | } | |
154 | ||
155 | /** | |
156 | * List of extra request headers. | |
157 | */ | |
158 | private final Collection<Header> headers; | |
159 | /** | |
160 | * Context instance used by this class | |
161 | */ | |
162 | private final Context context; | |
163 | /** | |
164 | * Data to send to the server | |
165 | */ | |
166 | private final byte[] data; | |
167 | /** | |
168 | * Server URL path | |
169 | */ | |
170 | private final String path; | |
171 | /** | |
172 | * Callback to run after the request returns | |
173 | */ | |
174 | private final ResultCallback callback; | |
175 | ||
176 | /** | |
177 | * Constructs a SendHttpMessageAsyncTask which sends a binary message. | |
178 | * | |
179 | * @param path URL path | |
180 | * @param headers the extra headers | |
181 | * @param context the context instance | |
182 | * @param resultCallback {@link ResultCallback} instance | |
183 | * @param data the message to send | |
184 | */ | |
185 | public HttpCallExecutableRunnable(final String path, final @Nullable Collection<Header> headers, final Context context, final @Nullable ResultCallback resultCallback, final byte[] data){//NOPMD array is supposed to be immutable. | |
186 | this.path=path; | |
187 | this.headers=headers; | |
188 | this.context=context; | |
189 | this.callback=resultCallback; | |
190 | this.data=data; | |
191 | } | |
192 | ||
193 | /** | |
194 | * Constructs a SendHttpMessageAsyncTask which sends a text message. | |
195 | * | |
196 | * @param path URL path | |
197 | * @param headers the extra headers | |
198 | * @param context the context instance | |
199 | * @param resultCallback {@link ResultCallback} instance | |
200 | * @param message message to send | |
201 | */ | |
202 | public HttpCallExecutableRunnable(final String path, final @Nullable Collection<Header> headers, final Context context, final @Nullable ResultCallback resultCallback, final String... message){ | |
203 | this.path=path; | |
204 | this.headers=headers; | |
205 | this.context=context; | |
206 | this.callback=resultCallback; | |
207 | if(message.length == 0) | |
208 | this.data=null;//NOPMD final field | |
209 | else | |
210 | this.data=Utils.join(" ", message).getBytes(); | |
211 | } | |
212 | ||
213 | @Override | |
214 | public void run() { | |
215 | try { | |
216 | final URL url=Utils.getServerURL(toNonNull(context),toNonNull(path)); | |
217 | final HttpsURLConnection conn=(HttpsURLConnection) url.openConnection(); | |
218 | if(url.getHost().equals("fonbot.ieval.ro")) | |
219 | conn.setSSLSocketFactory(IEVAL_SOCKET_FACTORY); | |
220 | else | |
221 | conn.setSSLSocketFactory(DEFAULT_SOCKET_FACTORY); | |
222 | if(data!=null){ | |
223 | conn.setDoOutput(true); | |
224 | conn.setFixedLengthStreamingMode(data.length); | |
225 | } | |
226 | conn.setRequestProperty("X-ID", GCMRegistrar.getRegistrationId(context)); | |
227 | final String user=PreferenceManager.getDefaultSharedPreferences(context).getString("username", null); | |
228 | final String password=PreferenceManager.getDefaultSharedPreferences(context).getString("password", null); | |
229 | if(user == null || password == null){ | |
230 | if(callback!=null) | |
231 | callback.onError(toNonNull(context.getString(user_or_password_not_set))); | |
232 | return; | |
233 | } | |
234 | ||
235 | conn.setRequestProperty("Authorization", "Basic "+Base64.encodeToString( | |
236 | (user+':'+password).getBytes(), Base64.NO_WRAP)); | |
237 | if(headers != null) | |
238 | for (final Header header : headers) | |
239 | conn.setRequestProperty(header.name, header.value); | |
240 | conn.connect(); | |
241 | if(data!=null){ | |
242 | final OutputStream stream=conn.getOutputStream(); | |
243 | stream.write(data); | |
244 | stream.close(); | |
245 | } | |
246 | Log.d(getClass().getName(),"HTTP Response: "+conn.getResponseCode()+" "+conn.getResponseMessage()); | |
247 | final String message=conn.getResponseMessage(); | |
248 | if(message==null && callback != null) | |
249 | callback.onError(toNonNull(context.getString(no_response_returned_from_server))); | |
250 | else if(message != null && callback != null) | |
251 | callback.onResult(conn.getResponseCode(), message, | |
252 | toNonNull(conn.getInputStream())); | |
253 | conn.disconnect(); | |
254 | } catch (Exception e) { | |
255 | e.printStackTrace(); | |
256 | if(callback != null) | |
257 | callback.onError(toNonNull(context.getString(connection_error))); | |
258 | } | |
259 | } | |
260 | } |