• 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 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 
30 import java.io.ByteArrayOutputStream;
31 import java.io.Closeable;
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 public 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
setUp()52     protected void setUp() throws Exception {
53         super.setUp();
54 
55         mResolver = getContext().getContentResolver();
56     }
57 
testStrategyUriSimple()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 
testStrategyUriJumpOutside()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 
testStrategyUriShortestRoot()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 
testStrategyFileSimple()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 
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 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 
testStrategyExtraParams()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 
testStrategyExtraSeparators()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 
testQueryProjectionNull()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 
testQueryProjectionOrder()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 
testQueryExtraColumn()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 
testReadFile()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 
testWriteFile()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 
testWriteMissingFile()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 
testDelete()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 
testMetaDataTargets()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 
assertContentsEquals(byte[] expected, Uri actual)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 
stageFileAndGetUri(File file, byte[] data)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 
buildPath(File base, String... segments)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      */
closeQuietly(AutoCloseable closeable)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      */
readFully(InputStream in)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      */
readFullyNoClose(InputStream in)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