1 package android.security; 2 3 import android.app.DownloadManager; 4 import android.content.BroadcastReceiver; 5 import android.content.Context; 6 import android.content.Intent; 7 import android.content.IntentFilter; 8 import android.content.pm.ApplicationInfo; 9 import android.database.Cursor; 10 import android.media.MediaPlayer; 11 import android.net.Uri; 12 import android.net.http.AndroidHttpClient; 13 import android.test.AndroidTestCase; 14 import android.webkit.cts.CtsTestServer; 15 16 import org.apache.http.HttpResponse; 17 import org.apache.http.client.methods.HttpGet; 18 19 import java.io.IOException; 20 import java.net.HttpURLConnection; 21 import java.net.URL; 22 import java.net.UnknownServiceException; 23 import java.util.concurrent.CancellationException; 24 import java.util.concurrent.ExecutionException; 25 import java.util.concurrent.Future; 26 import java.util.concurrent.TimeUnit; 27 import java.util.concurrent.TimeoutException; 28 29 abstract class NetworkSecurityPolicyTestBase extends AndroidTestCase { 30 private CtsTestServer mHttpOnlyWebServer; 31 32 private final boolean mCleartextTrafficExpectedToBePermitted; 33 34 NetworkSecurityPolicyTestBase(boolean cleartextTrafficExpectedToBePermitted) { 35 mCleartextTrafficExpectedToBePermitted = cleartextTrafficExpectedToBePermitted; 36 } 37 38 @Override 39 protected void setUp() throws Exception { 40 super.setUp(); 41 mHttpOnlyWebServer = new CtsTestServer(mContext, false); 42 } 43 44 @Override 45 protected void tearDown() throws Exception { 46 try { 47 mHttpOnlyWebServer.shutdown(); 48 } finally { 49 super.tearDown(); 50 } 51 } 52 53 public void testNetworkSecurityPolicy() { 54 assertEquals(mCleartextTrafficExpectedToBePermitted, 55 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); 56 } 57 58 public void testApplicationInfoFlag() { 59 ApplicationInfo appInfo = getContext().getApplicationInfo(); 60 int expectedValue = (mCleartextTrafficExpectedToBePermitted) 61 ? ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC : 0; 62 assertEquals(expectedValue, appInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC); 63 } 64 65 public void testDefaultHttpURLConnection() throws Exception { 66 if (mCleartextTrafficExpectedToBePermitted) { 67 assertCleartextHttpURLConnectionSucceeds(); 68 } else { 69 assertCleartextHttpURLConnectionBlocked(); 70 } 71 } 72 73 private void assertCleartextHttpURLConnectionSucceeds() throws Exception { 74 URL url = new URL(mHttpOnlyWebServer.getUserAgentUrl()); 75 HttpURLConnection conn = null; 76 try { 77 mHttpOnlyWebServer.resetRequestState(); 78 conn = (HttpURLConnection) url.openConnection(); 79 conn.setConnectTimeout(5000); 80 conn.setReadTimeout(5000); 81 assertEquals(200, conn.getResponseCode()); 82 } finally { 83 if (conn != null) { 84 conn.disconnect(); 85 } 86 } 87 Uri uri = Uri.parse(url.toString()).buildUpon().scheme(null).authority(null).build(); 88 assertTrue(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 89 } 90 91 private void assertCleartextHttpURLConnectionBlocked() throws Exception { 92 URL url = new URL(mHttpOnlyWebServer.getUserAgentUrl()); 93 HttpURLConnection conn = null; 94 try { 95 mHttpOnlyWebServer.resetRequestState(); 96 conn = (HttpURLConnection) url.openConnection(); 97 conn.setConnectTimeout(5000); 98 conn.setReadTimeout(5000); 99 conn.getResponseCode(); 100 fail(); 101 } catch (IOException e) { 102 if ((e.getMessage() == null) || (!e.getMessage().toLowerCase().contains("cleartext"))) { 103 fail("Exception with which request failed does not mention cleartext: " + e); 104 } 105 } finally { 106 if (conn != null) { 107 conn.disconnect(); 108 } 109 } 110 Uri uri = Uri.parse(url.toString()).buildUpon().scheme(null).authority(null).build(); 111 assertFalse(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 112 } 113 114 public void testAndroidHttpClient() throws Exception { 115 if (mCleartextTrafficExpectedToBePermitted) { 116 assertAndroidHttpClientCleartextRequestSucceeds(); 117 } else { 118 assertAndroidHttpClientCleartextRequestBlocked(); 119 } 120 } 121 122 private void assertAndroidHttpClientCleartextRequestSucceeds() throws Exception { 123 URL url = new URL(mHttpOnlyWebServer.getUserAgentUrl()); 124 AndroidHttpClient httpClient = AndroidHttpClient.newInstance(null); 125 try { 126 HttpResponse response = httpClient.execute(new HttpGet(url.toString())); 127 assertEquals(200, response.getStatusLine().getStatusCode()); 128 } finally { 129 httpClient.close(); 130 } 131 Uri uri = Uri.parse(url.toString()).buildUpon().scheme(null).authority(null).build(); 132 assertTrue(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 133 } 134 135 private void assertAndroidHttpClientCleartextRequestBlocked() throws Exception { 136 URL url = new URL(mHttpOnlyWebServer.getUserAgentUrl()); 137 AndroidHttpClient httpClient = AndroidHttpClient.newInstance(null); 138 try { 139 HttpResponse response = httpClient.execute(new HttpGet(url.toString())); 140 fail(); 141 } catch (IOException e) { 142 if ((e.getMessage() == null) || (!e.getMessage().toLowerCase().contains("cleartext"))) { 143 fail("Exception with which request failed does not mention cleartext: " + e); 144 } 145 } finally { 146 httpClient.close(); 147 } 148 Uri uri = Uri.parse(url.toString()).buildUpon().scheme(null).authority(null).build(); 149 assertFalse(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 150 } 151 152 public void testMediaPlayer() throws Exception { 153 if (mCleartextTrafficExpectedToBePermitted) { 154 assertMediaPlayerCleartextRequestSucceeds(); 155 } else { 156 assertMediaPlayerCleartextRequestBlocked(); 157 } 158 } 159 160 private void assertMediaPlayerCleartextRequestSucceeds() throws Exception { 161 MediaPlayer mediaPlayer = new MediaPlayer(); 162 Uri uri = Uri.parse(mHttpOnlyWebServer.getUserAgentUrl()); 163 mediaPlayer.setDataSource(getContext(), uri); 164 165 try { 166 mediaPlayer.prepare(); 167 } catch (IOException expected) { 168 } finally { 169 try { 170 mediaPlayer.stop(); 171 } catch (IllegalStateException ignored) { 172 } 173 } 174 uri = uri.buildUpon().scheme(null).authority(null).build(); 175 assertTrue(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 176 } 177 178 private void assertMediaPlayerCleartextRequestBlocked() throws Exception { 179 MediaPlayer mediaPlayer = new MediaPlayer(); 180 Uri uri = Uri.parse(mHttpOnlyWebServer.getUserAgentUrl()); 181 mediaPlayer.setDataSource(getContext(), uri); 182 183 try { 184 mediaPlayer.prepare(); 185 } catch (IOException expected) { 186 } finally { 187 try { 188 mediaPlayer.stop(); 189 } catch (IllegalStateException ignored) { 190 } 191 } 192 uri = uri.buildUpon().scheme(null).authority(null).build(); 193 assertFalse(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 194 } 195 196 public void testDownloadManager() throws Exception { 197 Uri uri = Uri.parse(mHttpOnlyWebServer.getTestDownloadUrl("netsecpolicy", 0)); 198 int[] result = downloadUsingDownloadManager(uri); 199 int status = result[0]; 200 int reason = result[1]; 201 uri = uri.buildUpon().scheme(null).authority(null).build(); 202 if (mCleartextTrafficExpectedToBePermitted) { 203 assertEquals(DownloadManager.STATUS_SUCCESSFUL, status); 204 assertTrue(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 205 } else { 206 assertEquals(DownloadManager.STATUS_FAILED, status); 207 assertEquals(400, reason); 208 assertFalse(mHttpOnlyWebServer.wasResourceRequested(uri.toString())); 209 } 210 } 211 212 213 private int[] downloadUsingDownloadManager(Uri uri) throws Exception { 214 DownloadManager downloadManager = 215 (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE); 216 removeAllDownloads(downloadManager); 217 BroadcastReceiver downloadCompleteReceiver = null; 218 try { 219 final SettableFuture<Intent> downloadCompleteIntentFuture = new SettableFuture<Intent>(); 220 downloadCompleteReceiver = new BroadcastReceiver() { 221 @Override 222 public void onReceive(Context context, Intent intent) { 223 downloadCompleteIntentFuture.set(intent); 224 } 225 }; 226 getContext().registerReceiver( 227 downloadCompleteReceiver, 228 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); 229 230 Intent downloadCompleteIntent; 231 232 long downloadId = downloadManager.enqueue(new DownloadManager.Request(uri)); 233 downloadCompleteIntent = downloadCompleteIntentFuture.get(5, TimeUnit.SECONDS); 234 235 assertEquals(downloadId, 236 downloadCompleteIntent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1)); 237 Cursor c = downloadManager.query( 238 new DownloadManager.Query().setFilterById(downloadId)); 239 try { 240 if (!c.moveToNext()) { 241 fail("Download not found"); 242 return null; 243 } 244 int status = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)); 245 int reason = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON)); 246 return new int[] {status, reason}; 247 } finally { 248 c.close(); 249 } 250 } finally { 251 if (downloadCompleteReceiver != null) { 252 getContext().unregisterReceiver(downloadCompleteReceiver); 253 } 254 removeAllDownloads(downloadManager); 255 } 256 } 257 258 private static void removeAllDownloads(DownloadManager downloadManager) { 259 Cursor cursor = null; 260 try { 261 DownloadManager.Query query = new DownloadManager.Query(); 262 cursor = downloadManager.query(query); 263 if (cursor.getCount() == 0) { 264 return; 265 } 266 long[] removeIds = new long[cursor.getCount()]; 267 int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_ID); 268 for (int i = 0; cursor.moveToNext(); i++) { 269 removeIds[i] = cursor.getLong(columnIndex); 270 } 271 assertEquals(removeIds.length, downloadManager.remove(removeIds)); 272 } finally { 273 if (cursor != null) { 274 cursor.close(); 275 } 276 } 277 } 278 279 private static class SettableFuture<T> implements Future<T> { 280 281 private final Object mLock = new Object(); 282 private boolean mDone; 283 private boolean mCancelled; 284 private T mValue; 285 private Throwable mException; 286 287 public void set(T value) { 288 synchronized (mLock) { 289 if (!mDone) { 290 mValue = value; 291 mDone = true; 292 mLock.notifyAll(); 293 } 294 } 295 } 296 297 public void setException(Throwable exception) { 298 synchronized (mLock) { 299 if (!mDone) { 300 mException = exception; 301 mDone = true; 302 mLock.notifyAll(); 303 } 304 } 305 } 306 307 @Override 308 public boolean cancel(boolean mayInterruptIfRunning) { 309 synchronized (mLock) { 310 if (mDone) { 311 return false; 312 } 313 mCancelled = true; 314 mDone = true; 315 mLock.notifyAll(); 316 return true; 317 } 318 } 319 320 @Override 321 public T get() throws InterruptedException, ExecutionException { 322 synchronized (mLock) { 323 while (!mDone) { 324 mLock.wait(); 325 } 326 return getValue(); 327 } 328 } 329 330 @Override 331 public T get(long timeout, TimeUnit timeUnit) 332 throws InterruptedException, ExecutionException, TimeoutException { 333 synchronized (mLock) { 334 if (mDone) { 335 return getValue(); 336 } 337 long timeoutMillis = timeUnit.toMillis(timeout); 338 long deadlineTimeMillis = System.currentTimeMillis() + timeoutMillis; 339 340 while (!mDone) { 341 long millisTillDeadline = deadlineTimeMillis - System.currentTimeMillis(); 342 if ((millisTillDeadline <= 0) || (millisTillDeadline > timeoutMillis)) { 343 throw new TimeoutException(); 344 } 345 mLock.wait(millisTillDeadline); 346 } 347 return getValue(); 348 } 349 } 350 351 private T getValue() throws ExecutionException { 352 synchronized (mLock) { 353 if (!mDone) { 354 throw new IllegalStateException("Not yet done"); 355 } 356 if (mCancelled) { 357 throw new CancellationException(); 358 } 359 if (mException != null) { 360 throw new ExecutionException(mException); 361 } 362 return mValue; 363 } 364 } 365 366 @Override 367 public boolean isCancelled() { 368 synchronized (mLock) { 369 return mCancelled; 370 } 371 } 372 373 @Override 374 public boolean isDone() { 375 synchronized (mLock) { 376 return mDone; 377 } 378 } 379 } 380 } 381