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