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