1/*
2 * Copyright (C) 2015 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.app.backup;
18
19import static org.mockito.Mockito.mock;
20import static org.mockito.Mockito.when;
21
22import android.content.Context;
23import android.test.AndroidTestCase;
24import android.util.ArrayMap;
25import android.util.ArraySet;
26
27import org.xmlpull.v1.XmlPullParser;
28import org.xmlpull.v1.XmlPullParserException;
29import org.xmlpull.v1.XmlPullParserFactory;
30
31import java.io.File;
32import java.io.StringReader;
33import java.util.ArrayList;
34import java.util.Collections;
35import java.util.List;
36import java.util.Map;
37import java.util.Set;
38
39public class FullBackupTest extends AndroidTestCase {
40    private XmlPullParserFactory mFactory;
41    private XmlPullParser mXpp;
42    private Context mContext;
43
44    Map<String, Set<String>> includeMap;
45    Set<String> excludesSet;
46
47    @Override
48    public void setUp() throws Exception {
49        mFactory = XmlPullParserFactory.newInstance();
50        mXpp = mFactory.newPullParser();
51        mContext = getContext();
52
53        includeMap = new ArrayMap();
54        excludesSet = new ArraySet();
55    }
56
57    public void testparseBackupSchemeFromXml_onlyInclude() throws Exception {
58        mXpp.setInput(new StringReader(
59                "<full-backup-content>" +
60                        "<include path=\"onlyInclude.txt\" domain=\"file\"/>" +
61                "</full-backup-content>"));
62
63        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
64        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
65
66        assertEquals("Excluding files when there was no <exclude/> tag.", 0, excludesSet.size());
67        assertEquals("Unexpected number of <include/>s", 1, includeMap.size());
68
69        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
70        assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
71        assertEquals("Invalid path parsed for <include/>",
72                new File(mContext.getFilesDir(), "onlyInclude.txt").getCanonicalPath(),
73                fileDomainIncludes.iterator().next());
74    }
75
76    public void testparseBackupSchemeFromXml_onlyExclude() throws Exception {
77        mXpp.setInput(new StringReader(
78                "<full-backup-content>" +
79                    "<exclude path=\"onlyExclude.txt\" domain=\"file\"/>" +
80                "</full-backup-content>"));
81
82        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
83        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
84
85        assertEquals("Including files when there was no <include/> tag.", 0, includeMap.size());
86        assertEquals("Unexpected number of <exclude/>s", 1, excludesSet.size());
87        assertEquals("Invalid path parsed for <exclude/>",
88                new File(mContext.getFilesDir(), "onlyExclude.txt").getCanonicalPath(),
89                excludesSet.iterator().next());
90    }
91
92    public void testparseBackupSchemeFromXml_includeAndExclude() throws Exception {
93        mXpp.setInput(new StringReader(
94                "<full-backup-content>" +
95                        "<exclude path=\"exclude.txt\" domain=\"file\"/>" +
96                        "<include path=\"include.txt\" domain=\"file\"/>" +
97                "</full-backup-content>"));
98
99        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
100        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
101
102        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
103        assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
104        assertEquals("Invalid path parsed for <include/>",
105                new File(mContext.getFilesDir(), "include.txt").getCanonicalPath(),
106                fileDomainIncludes.iterator().next());
107
108        assertEquals("Unexpected number of <exclude/>s", 1, excludesSet.size());
109        assertEquals("Invalid path parsed for <exclude/>",
110                new File(mContext.getFilesDir(), "exclude.txt").getCanonicalPath(),
111                excludesSet.iterator().next());
112    }
113
114    public void testparseBackupSchemeFromXml_lotsOfIncludesAndExcludes() throws Exception {
115        mXpp.setInput(new StringReader(
116                "<full-backup-content>" +
117                        "<exclude path=\"exclude1.txt\" domain=\"file\"/>" +
118                        "<include path=\"include1.txt\" domain=\"file\"/>" +
119                         "<exclude path=\"exclude2.txt\" domain=\"database\"/>" +
120                        "<include path=\"include2.txt\" domain=\"database\"/>" +
121                         "<exclude path=\"exclude3.txt\" domain=\"sharedpref\"/>" +
122                        "<include path=\"include3.txt\" domain=\"sharedpref\"/>" +
123                "</full-backup-content>"));
124
125
126        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
127        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
128
129        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
130        assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
131        assertEquals("Invalid path parsed for <include/>",
132                new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
133                fileDomainIncludes.iterator().next());
134
135        Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
136        assertEquals("Didn't find expected database domain include.",
137                2, databaseDomainIncludes.size()); // two expected here because of "-journal" file
138        assertTrue("Invalid path parsed for <include/>",
139                databaseDomainIncludes.contains(
140                        new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
141                                .getCanonicalPath()));
142        assertTrue("Invalid path parsed for <include/>",
143                databaseDomainIncludes.contains(
144                        new File(
145                                mContext.getDatabasePath("foo").getParentFile(),
146                                "include2.txt-journal")
147                                .getCanonicalPath()));
148
149        Set<String> sharedPrefDomainIncludes = includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN);
150        assertEquals("Didn't find expected sharedpref domain include.",
151                1, sharedPrefDomainIncludes.size());
152        assertEquals("Invalid path parsed for <include/>",
153                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3.txt")
154                        .getCanonicalPath(),
155                sharedPrefDomainIncludes.iterator().next());
156
157
158        assertEquals("Unexpected number of <exclude/>s", 4, excludesSet.size());
159        // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
160        // sort lexicographically.
161        List<String> arrayedSet = new ArrayList<String>(excludesSet);
162        Collections.sort(arrayedSet);
163
164        assertEquals("Invalid path parsed for <exclude/>",
165                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt")
166                        .getCanonicalPath(),
167                arrayedSet.get(0));
168        assertEquals("Invalid path parsed for <exclude/>",
169                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-journal")
170                        .getCanonicalPath(),
171                arrayedSet.get(1));
172        assertEquals("Invalid path parsed for <exclude/>",
173                new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
174                arrayedSet.get(2));
175        assertEquals("Invalid path parsed for <exclude/>",
176                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.txt")
177                        .getCanonicalPath(),
178                arrayedSet.get(3));
179    }
180
181    public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
182        // Invalid root tag.
183        mXpp.setInput(new StringReader(
184                "<full-weird-tag>" +
185                        "<exclude path=\"invalidRootTag.txt\" domain=\"file\"/>" +
186                        "</ffull-weird-tag>" ));
187
188        try {
189            FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
190            bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
191            fail("Invalid root xml tag should throw an XmlPullParserException");
192        } catch (XmlPullParserException expected) {}
193
194        // Invalid exclude tag.
195        mXpp.setInput(new StringReader(
196                "<full-backup-content>" +
197                        "<excluded path=\"invalidExcludeTag.txt\" domain=\"file\"/>" +
198                "</full-backup-conten>t" ));
199        try {
200            FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
201            bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
202            fail("Misspelled xml exclude tag should throw an XmlPullParserException");
203        } catch (XmlPullParserException expected) {}
204
205        // Just for good measure - invalid include tag.
206        mXpp.setInput(new StringReader(
207                "<full-backup-content>" +
208                        "<yinclude path=\"invalidIncludeTag.txt\" domain=\"file\"/>" +
209                        "</full-backup-conten>t" ));
210        try {
211            FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
212            bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
213            fail("Misspelled xml exclude tag should throw an XmlPullParserException");
214        } catch (XmlPullParserException expected) {}
215
216    }
217
218    public void testInvalidPath_doesNotBackup() throws Exception {
219        mXpp.setInput(new StringReader(
220                "<full-backup-content>" +
221                        "<exclude path=\"..\" domain=\"file\"/>" +  // Invalid use of ".." dir.
222                        "</full-backup-content>" ));
223
224        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
225        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
226
227        assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
228
229        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
230        assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
231    }
232    public void testDoubleDotInPath_isIgnored() throws Exception {
233        mXpp.setInput(new StringReader(
234                "<full-backup-content>" +
235                        "<include path=\"..\" domain=\"file\"/>" +  // Invalid use of ".." dir.
236                        "</full-backup-content>" ));
237
238        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
239        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
240
241        assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
242
243        Set<String> fileDomainIncludes = includeMap.get(FullBackup.DATA_TREE_TOKEN);
244        assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
245    }
246
247    public void testDoubleSlashInPath_isIgnored() throws Exception {
248        mXpp.setInput(new StringReader(
249                "<full-backup-content>" +
250                        "<exclude path=\"//hello.txt\" domain=\"file\"/>" +  // Invalid use of "//"
251                        "</full-backup-content>" ));
252
253        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
254        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
255
256        assertEquals("Didn't throw away invalid path containing \"//\".", 0, excludesSet.size());
257    }
258}
259