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 
NetworkSecurityPolicyTestBase(boolean cleartextTrafficExpectedToBePermitted)34     NetworkSecurityPolicyTestBase(boolean cleartextTrafficExpectedToBePermitted) {
35         mCleartextTrafficExpectedToBePermitted = cleartextTrafficExpectedToBePermitted;
36     }
37 
38     @Override
setUp()39     protected void setUp() throws Exception {
40         super.setUp();
41         mHttpOnlyWebServer = new CtsTestServer(mContext, false);
42     }
43 
44     @Override
tearDown()45     protected void tearDown() throws Exception {
46         try {
47             mHttpOnlyWebServer.shutdown();
48         } finally {
49             super.tearDown();
50         }
51     }
52 
testNetworkSecurityPolicy()53     public void testNetworkSecurityPolicy() {
54         assertEquals(mCleartextTrafficExpectedToBePermitted,
55                 NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted());
56     }
57 
testApplicationInfoFlag()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 
testDefaultHttpURLConnection()65     public void testDefaultHttpURLConnection() throws Exception {
66         if (mCleartextTrafficExpectedToBePermitted) {
67             assertCleartextHttpURLConnectionSucceeds();
68         } else {
69             assertCleartextHttpURLConnectionBlocked();
70         }
71     }
72 
assertCleartextHttpURLConnectionSucceeds()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 
assertCleartextHttpURLConnectionBlocked()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 
testAndroidHttpClient()114     public void testAndroidHttpClient() throws Exception {
115         if (mCleartextTrafficExpectedToBePermitted) {
116             assertAndroidHttpClientCleartextRequestSucceeds();
117         } else {
118             assertAndroidHttpClientCleartextRequestBlocked();
119         }
120     }
121 
assertAndroidHttpClientCleartextRequestSucceeds()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 
assertAndroidHttpClientCleartextRequestBlocked()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 
testMediaPlayer()152     public void testMediaPlayer() throws Exception {
153         if (mCleartextTrafficExpectedToBePermitted) {
154             assertMediaPlayerCleartextRequestSucceeds();
155         } else {
156             assertMediaPlayerCleartextRequestBlocked();
157         }
158     }
159 
assertMediaPlayerCleartextRequestSucceeds()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 
assertMediaPlayerCleartextRequestBlocked()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 
testDownloadManager()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 
downloadUsingDownloadManager(Uri uri)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 
removeAllDownloads(DownloadManager downloadManager)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 
set(T value)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 
setException(Throwable exception)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
cancel(boolean mayInterruptIfRunning)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
get()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
get(long timeout, TimeUnit timeUnit)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 
getValue()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
isCancelled()367         public boolean isCancelled() {
368             synchronized (mLock) {
369                 return mCancelled;
370             }
371         }
372 
373         @Override
isDone()374         public boolean isDone() {
375             synchronized (mLock) {
376                 return mDone;
377             }
378         }
379     }
380 }
381