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