• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.providers.downloads.public_api_access_tests;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 import static org.junit.Assert.fail;
22 
23 import android.app.DownloadManager;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.os.Environment;
30 import android.provider.Downloads;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.runner.AndroidJUnit4;
34 
35 import org.junit.After;
36 import org.junit.Before;
37 import org.junit.Ignore;
38 import org.junit.Test;
39 import org.junit.runner.RunWith;
40 
41 import java.io.File;
42 
43 /**
44  * DownloadProvider allows apps without permission ACCESS_DOWNLOAD_MANAGER to access it -- this is
45  * how the public API works.  But such access is subject to strict constraints on what can be
46  * inserted.  This test suite checks those constraints.
47  */
48 @RunWith(AndroidJUnit4.class)
49 public class PublicApiAccessTest {
50     private static final String[] DISALLOWED_COLUMNS = new String[] {
51                     Downloads.Impl.COLUMN_COOKIE_DATA,
52                     Downloads.Impl.COLUMN_REFERER,
53                     Downloads.Impl.COLUMN_USER_AGENT,
54                     Downloads.Impl.COLUMN_NO_INTEGRITY,
55                     Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
56                     Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
57                     Downloads.Impl.COLUMN_OTHER_UID,
58                     Downloads.Impl.COLUMN_APP_DATA,
59                     Downloads.Impl.COLUMN_CONTROL,
60                     Downloads.Impl.COLUMN_STATUS,
61             };
62 
63     private ContentResolver mContentResolver;
64     private DownloadManager mManager;
65 
getContext()66     private Context getContext() {
67         return InstrumentationRegistry.getContext();
68     }
69 
70     @Before
setUp()71     public void setUp() throws Exception {
72         mContentResolver = getContext().getContentResolver();
73         mManager = new DownloadManager(getContext());
74         mManager.setAccessFilename(true);
75     }
76 
77     @After
tearDown()78     public void tearDown() throws Exception {
79         if (mContentResolver != null) {
80             mContentResolver.delete(Downloads.Impl.CONTENT_URI, null, null);
81         }
82     }
83 
84     @Test
testMinimalValidWrite()85     public void testMinimalValidWrite() {
86         mContentResolver.insert(Downloads.Impl.CONTENT_URI, buildValidValues());
87     }
88 
89     @Test
testMaximalValidWrite()90     public void testMaximalValidWrite() {
91         ContentValues values = buildValidValues();
92         values.put(Downloads.Impl.COLUMN_TITLE, "foo");
93         values.put(Downloads.Impl.COLUMN_DESCRIPTION, "foo");
94         values.put(Downloads.Impl.COLUMN_MIME_TYPE, "foo");
95         values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, "foo");
96         values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, 0);
97         values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, true);
98         values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + "0", "X-Some-Header: value");
99         mContentResolver.insert(Downloads.Impl.CONTENT_URI, values);
100     }
101 
buildValidValues()102     private ContentValues buildValidValues() {
103         ContentValues values = new ContentValues();
104         values.put(Downloads.Impl.COLUMN_URI, "foo");
105         values.put(Downloads.Impl.COLUMN_DESTINATION,
106                 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
107         values.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_VISIBLE);
108         values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
109         return values;
110     }
111 
112     @Test
testNoPublicApi()113     public void testNoPublicApi() {
114         ContentValues values = buildValidValues();
115         values.remove(Downloads.Impl.COLUMN_IS_PUBLIC_API);
116         testInvalidValues(values);
117     }
118 
119     @Test
testInvalidDestination()120     public void testInvalidDestination() {
121         ContentValues values = buildValidValues();
122         values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_EXTERNAL);
123         testInvalidValues(values);
124         values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_CACHE_PARTITION);
125         testInvalidValues(values);
126     }
127 
128     @Ignore
129     @Test
testInvalidVisibility()130     public void testInvalidVisibility() {
131         ContentValues values = buildValidValues();
132         values.put(Downloads.Impl.COLUMN_VISIBILITY,
133                 Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
134         testInvalidValues(values);
135 
136         values.put(Downloads.Impl.COLUMN_VISIBILITY, Downloads.Impl.VISIBILITY_HIDDEN);
137         testInvalidValues(values);
138 
139         values.remove(Downloads.Impl.COLUMN_VISIBILITY);
140         testInvalidValues(values);
141     }
142 
143     @Test
testDisallowedColumns()144     public void testDisallowedColumns() {
145         for (String column : DISALLOWED_COLUMNS) {
146             ContentValues values = buildValidValues();
147             values.put(column, 1);
148             testInvalidValues(values);
149         }
150     }
151 
152     @Test
testFileUriWithoutExternalPermission()153     public void testFileUriWithoutExternalPermission() {
154         ContentValues values = buildValidValues();
155         values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
156         values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, "file:///sdcard/foo");
157         testInvalidValues(values);
158     }
159 
testInvalidValues(ContentValues values)160     private void testInvalidValues(ContentValues values) {
161         try {
162             mContentResolver.insert(Downloads.Impl.CONTENT_URI, values);
163             fail("Didn't get SecurityException as expected");
164         } catch (SecurityException exc) {
165             // expected
166         }
167     }
168 
169     @Test
testDownloadManagerRequest()170     public void testDownloadManagerRequest() {
171         // first try a minimal request
172         DownloadManager.Request request = new DownloadManager.Request(Uri.parse("http://localhost/path"));
173         mManager.enqueue(request);
174 
175         // now set everything we can, save for external destintion (for which we lack permission)
176         request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
177         request.setAllowedOverRoaming(false);
178         request.setTitle("test");
179         request.setDescription("test");
180         request.setMimeType("text/html");
181         request.addRequestHeader("X-Some-Header", "value");
182         mManager.enqueue(request);
183     }
184 
185     /**
186      * Internally, {@code DownloadManager} synchronizes its contents with
187      * {@code MediaStore}, which relies heavily on using file extensions to
188      * determine MIME types.
189      * <p>
190      * This test verifies that if an app attempts to add an already-completed
191      * download without an extension, that we'll force the MIME type with what
192      * {@code MediaStore} would have derived.
193      */
194     @Test
testAddCompletedWithoutExtension()195     public void testAddCompletedWithoutExtension() throws Exception {
196         final File dir = Environment
197                 .getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
198         final File file = new File(dir, "test" + System.nanoTime());
199         file.createNewFile();
200 
201         final long id = mManager.addCompletedDownload("My Title", "My Description", true,
202                 "application/pdf", file.getAbsolutePath(), file.length(), true, true,
203                 Uri.parse("http://example.com/"), Uri.parse("http://example.net/"));
204         final Uri uri = mManager.getDownloadUri(id);
205 
206         // Trigger a generic update so that we push to MediaStore
207         final ContentValues values = new ContentValues();
208         values.put(DownloadManager.COLUMN_DESCRIPTION, "Modified Description");
209         mContentResolver.update(uri, values, null);
210 
211         try (Cursor c = mContentResolver.query(uri, null, null, null)) {
212             assertTrue(c.moveToFirst());
213 
214             final String actualMime = c
215                     .getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_MEDIA_TYPE));
216             final String actualPath = c
217                     .getString(c.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME));
218 
219             assertEquals("application/octet-stream", actualMime);
220             assertEquals(file.getAbsolutePath(), actualPath);
221         }
222     }
223 }
224