FileProviderTest.java revision 2002bed7d4769fc985b7a8e8b5ba875ffc7e829a
1/* 2 * Copyright (C) 2013 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 17package android.support.v4.content; 18 19import static android.provider.OpenableColumns.DISPLAY_NAME; 20import static android.provider.OpenableColumns.SIZE; 21 22import android.content.ContentResolver; 23import android.database.Cursor; 24import android.net.Uri; 25import android.os.Environment; 26import android.support.v4.content.FileProvider.SimplePathStrategy; 27import android.test.AndroidTestCase; 28import android.test.MoreAsserts; 29 30import java.io.ByteArrayOutputStream; 31import java.io.Closeable; 32import java.io.File; 33import java.io.FileNotFoundException; 34import java.io.FileOutputStream; 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.OutputStream; 38 39/** 40 * Tests for {@link FileProvider} 41 */ 42public class FileProviderTest extends AndroidTestCase { 43 private static final String TEST_AUTHORITY = "moocow"; 44 45 private static final String TEST_FILE = "file.test"; 46 private static final byte[] TEST_DATA = new byte[] { (byte) 0xf0, 0x00, 0x0d }; 47 private static final byte[] TEST_DATA_ALT = new byte[] { (byte) 0x33, 0x66 }; 48 49 private ContentResolver mResolver; 50 51 @Override 52 protected void setUp() throws Exception { 53 super.setUp(); 54 55 mResolver = getContext().getContentResolver(); 56 } 57 58 public void testStrategyUriSimple() throws Exception { 59 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 60 strat.addRoot("tag", mContext.getFilesDir()); 61 62 File file = buildPath(mContext.getFilesDir(), "file.test"); 63 assertEquals("content://authority/tag/file.test", 64 strat.getUriForFile(file).toString()); 65 66 file = buildPath(mContext.getFilesDir(), "subdir", "file.test"); 67 assertEquals("content://authority/tag/subdir/file.test", 68 strat.getUriForFile(file).toString()); 69 70 file = buildPath(Environment.getExternalStorageDirectory(), "file.test"); 71 try { 72 strat.getUriForFile(file); 73 fail("somehow got uri for file outside roots?"); 74 } catch (IllegalArgumentException e) { 75 } 76 } 77 78 public void testStrategyUriJumpOutside() throws Exception { 79 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 80 strat.addRoot("tag", mContext.getFilesDir()); 81 82 File file = buildPath(mContext.getFilesDir(), "..", "file.test"); 83 try { 84 strat.getUriForFile(file); 85 fail("file escaped!"); 86 } catch (IllegalArgumentException e) { 87 } 88 } 89 90 public void testStrategyUriShortestRoot() throws Exception { 91 SimplePathStrategy strat = new SimplePathStrategy("authority"); 92 strat.addRoot("tag1", mContext.getFilesDir()); 93 strat.addRoot("tag2", new File("/")); 94 95 File file = buildPath(mContext.getFilesDir(), "file.test"); 96 assertEquals("content://authority/tag1/file.test", 97 strat.getUriForFile(file).toString()); 98 99 strat = new SimplePathStrategy("authority"); 100 strat.addRoot("tag1", new File("/")); 101 strat.addRoot("tag2", mContext.getFilesDir()); 102 103 file = buildPath(mContext.getFilesDir(), "file.test"); 104 assertEquals("content://authority/tag2/file.test", 105 strat.getUriForFile(file).toString()); 106 } 107 108 public void testStrategyFileSimple() throws Exception { 109 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 110 strat.addRoot("tag", mContext.getFilesDir()); 111 112 File expectedRoot = mContext.getFilesDir().getCanonicalFile(); 113 File file = buildPath(expectedRoot, "file.test"); 114 assertEquals(file.getPath(), 115 strat.getFileForUri(Uri.parse("content://authority/tag/file.test")).getPath()); 116 117 file = buildPath(expectedRoot, "subdir", "file.test"); 118 assertEquals(file.getPath(), strat.getFileForUri( 119 Uri.parse("content://authority/tag/subdir/file.test")).getPath()); 120 } 121 122 public void testStrategyFileJumpOutside() throws Exception { 123 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 124 strat.addRoot("tag", mContext.getFilesDir()); 125 126 try { 127 strat.getFileForUri(Uri.parse("content://authority/tag/../file.test")); 128 fail("file escaped!"); 129 } catch (SecurityException e) { 130 } 131 } 132 133 public void testStrategyEscaping() throws Exception { 134 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 135 strat.addRoot("t/g", mContext.getFilesDir()); 136 137 File expectedRoot = mContext.getFilesDir().getCanonicalFile(); 138 File file = buildPath(expectedRoot, "lol\"wat?foo&bar", "wat.txt"); 139 final String expected = "content://authority/t%2Fg/lol%22wat%3Ffoo%26bar/wat.txt"; 140 141 assertEquals(expected, 142 strat.getUriForFile(file).toString()); 143 assertEquals(file.getPath(), 144 strat.getFileForUri(Uri.parse(expected)).getPath()); 145 } 146 147 public void testStrategyExtraParams() throws Exception { 148 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 149 strat.addRoot("tag", mContext.getFilesDir()); 150 151 File expectedRoot = mContext.getFilesDir().getCanonicalFile(); 152 File file = buildPath(expectedRoot, "file.txt"); 153 assertEquals(file.getPath(), strat.getFileForUri( 154 Uri.parse("content://authority/tag/file.txt?extra=foo")).getPath()); 155 } 156 157 public void testStrategyExtraSeparators() throws Exception { 158 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 159 strat.addRoot("tag", mContext.getFilesDir()); 160 161 // When canonicalized, the path separators are trimmed 162 File inFile = new File(mContext.getFilesDir(), "//foo//bar//"); 163 File expectedRoot = mContext.getFilesDir().getCanonicalFile(); 164 File outFile = new File(expectedRoot, "/foo/bar"); 165 final String expected = "content://authority/tag/foo/bar"; 166 167 assertEquals(expected, 168 strat.getUriForFile(inFile).toString()); 169 assertEquals(outFile.getPath(), 170 strat.getFileForUri(Uri.parse(expected)).getPath()); 171 } 172 173 public void testQueryProjectionNull() throws Exception { 174 final File file = new File(mContext.getFilesDir(), TEST_FILE); 175 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 176 177 // Verify that null brings out default columns 178 Cursor cursor = mResolver.query(uri, null, null, null, null); 179 try { 180 assertEquals(1, cursor.getCount()); 181 cursor.moveToFirst(); 182 assertEquals(TEST_FILE, cursor.getString(cursor.getColumnIndex(DISPLAY_NAME))); 183 assertEquals(TEST_DATA.length, cursor.getLong(cursor.getColumnIndex(SIZE))); 184 } finally { 185 cursor.close(); 186 } 187 } 188 189 public void testQueryProjectionOrder() throws Exception { 190 final File file = new File(mContext.getFilesDir(), TEST_FILE); 191 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 192 193 // Verify that swapped order works 194 Cursor cursor = mResolver.query(uri, new String[] { 195 SIZE, DISPLAY_NAME }, null, null, null); 196 try { 197 assertEquals(1, cursor.getCount()); 198 cursor.moveToFirst(); 199 assertEquals(TEST_DATA.length, cursor.getLong(0)); 200 assertEquals(TEST_FILE, cursor.getString(1)); 201 } finally { 202 cursor.close(); 203 } 204 205 cursor = mResolver.query(uri, new String[] { 206 DISPLAY_NAME, SIZE }, null, null, null); 207 try { 208 assertEquals(1, cursor.getCount()); 209 cursor.moveToFirst(); 210 assertEquals(TEST_FILE, cursor.getString(0)); 211 assertEquals(TEST_DATA.length, cursor.getLong(1)); 212 } finally { 213 cursor.close(); 214 } 215 } 216 217 public void testQueryExtraColumn() throws Exception { 218 final File file = new File(mContext.getFilesDir(), TEST_FILE); 219 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 220 221 // Verify that extra column doesn't gook things up 222 Cursor cursor = mResolver.query(uri, new String[] { 223 SIZE, "foobar", DISPLAY_NAME }, null, null, null); 224 try { 225 assertEquals(1, cursor.getCount()); 226 cursor.moveToFirst(); 227 assertEquals(TEST_DATA.length, cursor.getLong(0)); 228 assertEquals(TEST_FILE, cursor.getString(1)); 229 } finally { 230 cursor.close(); 231 } 232 } 233 234 public void testReadFile() throws Exception { 235 final File file = new File(mContext.getFilesDir(), TEST_FILE); 236 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 237 238 assertContentsEquals(TEST_DATA, uri); 239 } 240 241 public void testWriteFile() throws Exception { 242 final File file = new File(mContext.getFilesDir(), TEST_FILE); 243 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 244 245 assertContentsEquals(TEST_DATA, uri); 246 247 final OutputStream out = mResolver.openOutputStream(uri); 248 try { 249 out.write(TEST_DATA_ALT); 250 } finally { 251 closeQuietly(out); 252 } 253 254 assertContentsEquals(TEST_DATA_ALT, uri); 255 } 256 257 public void testWriteMissingFile() throws Exception { 258 final File file = new File(mContext.getFilesDir(), TEST_FILE); 259 final Uri uri = stageFileAndGetUri(file, null); 260 261 try { 262 assertContentsEquals(new byte[0], uri); 263 fail("Somehow read missing file?"); 264 } catch(FileNotFoundException e) { 265 } 266 267 final OutputStream out = mResolver.openOutputStream(uri); 268 try { 269 out.write(TEST_DATA_ALT); 270 } finally { 271 closeQuietly(out); 272 } 273 274 assertContentsEquals(TEST_DATA_ALT, uri); 275 } 276 277 public void testDelete() throws Exception { 278 final File file = new File(mContext.getFilesDir(), TEST_FILE); 279 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 280 281 assertContentsEquals(TEST_DATA, uri); 282 283 assertEquals(1, mResolver.delete(uri, null, null)); 284 assertEquals(0, mResolver.delete(uri, null, null)); 285 286 try { 287 assertContentsEquals(new byte[0], uri); 288 fail("Somehow read missing file?"); 289 } catch(FileNotFoundException e) { 290 } 291 } 292 293 public void testMetaDataTargets() { 294 Uri actual; 295 296 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 297 new File("/proc/version")); 298 assertEquals("content://moocow/test_root/proc/version", actual.toString()); 299 300 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 301 new File("/proc/1/mountinfo")); 302 assertEquals("content://moocow/test_init/mountinfo", actual.toString()); 303 304 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 305 buildPath(mContext.getFilesDir(), "meow")); 306 assertEquals("content://moocow/test_files/meow", actual.toString()); 307 308 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 309 buildPath(mContext.getFilesDir(), "thumbs", "rawr")); 310 assertEquals("content://moocow/test_thumbs/rawr", actual.toString()); 311 312 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 313 buildPath(mContext.getCacheDir(), "up", "down")); 314 assertEquals("content://moocow/test_cache/up/down", actual.toString()); 315 316 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 317 buildPath(Environment.getExternalStorageDirectory(), "Android", "obb", "foobar")); 318 assertEquals("content://moocow/test_external/Android/obb/foobar", actual.toString()); 319 320 File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(mContext, null); 321 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 322 buildPath(externalFilesDirs[0], "foo", "bar")); 323 assertEquals("content://moocow/test_external_files/foo/bar", actual.toString()); 324 325 File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(mContext); 326 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 327 buildPath(externalCacheDirs[0], "foo", "bar")); 328 assertEquals("content://moocow/test_external_cache/foo/bar", actual.toString()); 329 } 330 331 private void assertContentsEquals(byte[] expected, Uri actual) throws Exception { 332 final InputStream in = mResolver.openInputStream(actual); 333 try { 334 MoreAsserts.assertEquals(expected, readFully(in)); 335 } finally { 336 closeQuietly(in); 337 } 338 } 339 340 private Uri stageFileAndGetUri(File file, byte[] data) throws Exception { 341 if (data != null) { 342 final FileOutputStream out = new FileOutputStream(file); 343 try { 344 out.write(data); 345 } finally { 346 out.close(); 347 } 348 } else { 349 file.delete(); 350 } 351 return FileProvider.getUriForFile(mContext, TEST_AUTHORITY, file); 352 } 353 354 private static File buildPath(File base, String... segments) { 355 File cur = base; 356 for (String segment : segments) { 357 if (cur == null) { 358 cur = new File(segment); 359 } else { 360 cur = new File(cur, segment); 361 } 362 } 363 return cur; 364 } 365 366 /** 367 * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null. 368 */ 369 private static void closeQuietly(AutoCloseable closeable) { 370 if (closeable != null) { 371 try { 372 closeable.close(); 373 } catch (RuntimeException rethrown) { 374 throw rethrown; 375 } catch (Exception ignored) { 376 } 377 } 378 } 379 380 /** 381 * Returns a byte[] containing the remainder of 'in', closing it when done. 382 */ 383 private static byte[] readFully(InputStream in) throws IOException { 384 try { 385 return readFullyNoClose(in); 386 } finally { 387 in.close(); 388 } 389 } 390 391 /** 392 * Returns a byte[] containing the remainder of 'in'. 393 */ 394 private static byte[] readFullyNoClose(InputStream in) throws IOException { 395 ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 396 byte[] buffer = new byte[1024]; 397 int count; 398 while ((count = in.read(buffer)) != -1) { 399 bytes.write(buffer, 0, count); 400 } 401 return bytes.toByteArray(); 402 } 403} 404