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.File; 31import java.io.FileNotFoundException; 32import java.io.FileOutputStream; 33import java.io.InputStream; 34import java.io.OutputStream; 35 36import libcore.io.IoUtils; 37import libcore.io.Streams; 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 file = buildPath(mContext.getFilesDir(), "file.test"); 113 assertEquals(file.getPath(), 114 strat.getFileForUri(Uri.parse("content://authority/tag/file.test")).getPath()); 115 116 file = buildPath(mContext.getFilesDir(), "subdir", "file.test"); 117 assertEquals(file.getPath(), strat.getFileForUri( 118 Uri.parse("content://authority/tag/subdir/file.test")).getPath()); 119 } 120 121 public void testStrategyFileJumpOutside() throws Exception { 122 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 123 strat.addRoot("tag", mContext.getFilesDir()); 124 125 try { 126 strat.getFileForUri(Uri.parse("content://authority/tag/../file.test")); 127 fail("file escaped!"); 128 } catch (SecurityException e) { 129 } 130 } 131 132 public void testStrategyEscaping() throws Exception { 133 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 134 strat.addRoot("t/g", mContext.getFilesDir()); 135 136 File file = buildPath(mContext.getFilesDir(), "lol\"wat?foo&bar", "wat.txt"); 137 final String expected = "content://authority/t%2Fg/lol%22wat%3Ffoo%26bar/wat.txt"; 138 139 assertEquals(expected, 140 strat.getUriForFile(file).toString()); 141 assertEquals(file.getPath(), 142 strat.getFileForUri(Uri.parse(expected)).getPath()); 143 } 144 145 public void testStrategyExtraParams() throws Exception { 146 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 147 strat.addRoot("tag", mContext.getFilesDir()); 148 149 File file = buildPath(mContext.getFilesDir(), "file.txt"); 150 assertEquals(file.getPath(), strat.getFileForUri( 151 Uri.parse("content://authority/tag/file.txt?extra=foo")).getPath()); 152 } 153 154 public void testStrategyExtraSeparators() throws Exception { 155 final SimplePathStrategy strat = new SimplePathStrategy("authority"); 156 strat.addRoot("tag", mContext.getFilesDir()); 157 158 // When canonicalized, the path separators are trimmed 159 File inFile = new File(mContext.getFilesDir(), "//foo//bar//"); 160 File outFile = new File(mContext.getFilesDir(), "/foo/bar"); 161 final String expected = "content://authority/tag/foo/bar"; 162 163 assertEquals(expected, 164 strat.getUriForFile(inFile).toString()); 165 assertEquals(outFile.getPath(), 166 strat.getFileForUri(Uri.parse(expected)).getPath()); 167 } 168 169 public void testQueryProjectionNull() throws Exception { 170 final File file = new File(mContext.getFilesDir(), TEST_FILE); 171 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 172 173 // Verify that null brings out default columns 174 Cursor cursor = mResolver.query(uri, null, null, null, null); 175 try { 176 assertEquals(1, cursor.getCount()); 177 cursor.moveToFirst(); 178 assertEquals(TEST_FILE, cursor.getString(cursor.getColumnIndex(DISPLAY_NAME))); 179 assertEquals(TEST_DATA.length, cursor.getLong(cursor.getColumnIndex(SIZE))); 180 } finally { 181 cursor.close(); 182 } 183 } 184 185 public void testQueryProjectionOrder() throws Exception { 186 final File file = new File(mContext.getFilesDir(), TEST_FILE); 187 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 188 189 // Verify that swapped order works 190 Cursor cursor = mResolver.query(uri, new String[] { 191 SIZE, DISPLAY_NAME }, null, null, null); 192 try { 193 assertEquals(1, cursor.getCount()); 194 cursor.moveToFirst(); 195 assertEquals(TEST_DATA.length, cursor.getLong(0)); 196 assertEquals(TEST_FILE, cursor.getString(1)); 197 } finally { 198 cursor.close(); 199 } 200 201 cursor = mResolver.query(uri, new String[] { 202 DISPLAY_NAME, SIZE }, null, null, null); 203 try { 204 assertEquals(1, cursor.getCount()); 205 cursor.moveToFirst(); 206 assertEquals(TEST_FILE, cursor.getString(0)); 207 assertEquals(TEST_DATA.length, cursor.getLong(1)); 208 } finally { 209 cursor.close(); 210 } 211 } 212 213 public void testQueryExtraColumn() throws Exception { 214 final File file = new File(mContext.getFilesDir(), TEST_FILE); 215 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 216 217 // Verify that extra column doesn't gook things up 218 Cursor cursor = mResolver.query(uri, new String[] { 219 SIZE, "foobar", DISPLAY_NAME }, null, null, null); 220 try { 221 assertEquals(1, cursor.getCount()); 222 cursor.moveToFirst(); 223 assertEquals(TEST_DATA.length, cursor.getLong(0)); 224 assertEquals(TEST_FILE, cursor.getString(1)); 225 } finally { 226 cursor.close(); 227 } 228 } 229 230 public void testReadFile() throws Exception { 231 final File file = new File(mContext.getFilesDir(), TEST_FILE); 232 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 233 234 assertContentsEquals(TEST_DATA, uri); 235 } 236 237 public void testWriteFile() throws Exception { 238 final File file = new File(mContext.getFilesDir(), TEST_FILE); 239 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 240 241 assertContentsEquals(TEST_DATA, uri); 242 243 final OutputStream out = mResolver.openOutputStream(uri); 244 try { 245 out.write(TEST_DATA_ALT); 246 } finally { 247 IoUtils.closeQuietly(out); 248 } 249 250 assertContentsEquals(TEST_DATA_ALT, uri); 251 } 252 253 public void testWriteMissingFile() throws Exception { 254 final File file = new File(mContext.getFilesDir(), TEST_FILE); 255 final Uri uri = stageFileAndGetUri(file, null); 256 257 try { 258 assertContentsEquals(new byte[0], uri); 259 fail("Somehow read missing file?"); 260 } catch(FileNotFoundException e) { 261 } 262 263 final OutputStream out = mResolver.openOutputStream(uri); 264 try { 265 out.write(TEST_DATA_ALT); 266 } finally { 267 IoUtils.closeQuietly(out); 268 } 269 270 assertContentsEquals(TEST_DATA_ALT, uri); 271 } 272 273 public void testDelete() throws Exception { 274 final File file = new File(mContext.getFilesDir(), TEST_FILE); 275 final Uri uri = stageFileAndGetUri(file, TEST_DATA); 276 277 assertContentsEquals(TEST_DATA, uri); 278 279 assertEquals(1, mResolver.delete(uri, null, null)); 280 assertEquals(0, mResolver.delete(uri, null, null)); 281 282 try { 283 assertContentsEquals(new byte[0], uri); 284 fail("Somehow read missing file?"); 285 } catch(FileNotFoundException e) { 286 } 287 } 288 289 public void testMetaDataTargets() { 290 Uri actual; 291 292 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 293 new File("/proc/version")); 294 assertEquals("content://moocow/test_root/proc/version", actual.toString()); 295 296 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 297 new File("/proc/1/mountinfo")); 298 assertEquals("content://moocow/test_init/mountinfo", actual.toString()); 299 300 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 301 buildPath(mContext.getFilesDir(), "meow")); 302 assertEquals("content://moocow/test_files/meow", actual.toString()); 303 304 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 305 buildPath(mContext.getFilesDir(), "thumbs", "rawr")); 306 assertEquals("content://moocow/test_thumbs/rawr", actual.toString()); 307 308 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 309 buildPath(mContext.getCacheDir(), "up", "down")); 310 assertEquals("content://moocow/test_cache/up/down", actual.toString()); 311 312 actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY, 313 buildPath(Environment.getExternalStorageDirectory(), "Android", "obb", "foobar")); 314 assertEquals("content://moocow/test_external/Android/obb/foobar", actual.toString()); 315 } 316 317 private void assertContentsEquals(byte[] expected, Uri actual) throws Exception { 318 final InputStream in = mResolver.openInputStream(actual); 319 try { 320 MoreAsserts.assertEquals(expected, Streams.readFully(in)); 321 } finally { 322 IoUtils.closeQuietly(in); 323 } 324 } 325 326 private Uri stageFileAndGetUri(File file, byte[] data) throws Exception { 327 if (data != null) { 328 final FileOutputStream out = new FileOutputStream(file); 329 try { 330 out.write(data); 331 } finally { 332 out.close(); 333 } 334 } else { 335 file.delete(); 336 } 337 return FileProvider.getUriForFile(mContext, TEST_AUTHORITY, file); 338 } 339 340 private static File buildPath(File base, String... segments) { 341 File cur = base; 342 for (String segment : segments) { 343 if (cur == null) { 344 cur = new File(segment); 345 } else { 346 cur = new File(cur, segment); 347 } 348 } 349 return cur; 350 } 351} 352