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