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.FILES_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.FILES_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\" domain=\"sharedpref\"/>" +
122                        "<include path=\"include3\" domain=\"sharedpref\"/>" +
123                         "<exclude path=\"exclude4.xml\" domain=\"sharedpref\"/>" +
124                        "<include path=\"include4.xml\" domain=\"sharedpref\"/>" +
125                "</full-backup-content>"));
126
127
128        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
129        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
130
131        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
132        assertEquals("Didn't find expected file domain include.", 1, fileDomainIncludes.size());
133        assertEquals("Invalid path parsed for <include/>",
134                new File(mContext.getFilesDir(), "include1.txt").getCanonicalPath(),
135                fileDomainIncludes.iterator().next());
136
137        Set<String> databaseDomainIncludes = includeMap.get(FullBackup.DATABASE_TREE_TOKEN);
138        // Three expected here because of "-journal" and "-wal" files
139        assertEquals("Didn't find expected database domain include.",
140                3, databaseDomainIncludes.size());
141        assertTrue("Invalid path parsed for <include/>",
142                databaseDomainIncludes.contains(
143                        new File(mContext.getDatabasePath("foo").getParentFile(), "include2.txt")
144                                .getCanonicalPath()));
145        assertTrue("Invalid path parsed for <include/>",
146                databaseDomainIncludes.contains(
147                        new File(
148                                mContext.getDatabasePath("foo").getParentFile(),
149                                "include2.txt-journal")
150                                .getCanonicalPath()));
151        assertTrue("Invalid path parsed for <include/>",
152                databaseDomainIncludes.contains(
153                        new File(
154                                mContext.getDatabasePath("foo").getParentFile(),
155                                "include2.txt-wal")
156                                .getCanonicalPath()));
157
158        List<String> sharedPrefDomainIncludes = new ArrayList<String>(
159                includeMap.get(FullBackup.SHAREDPREFS_TREE_TOKEN));
160        Collections.sort(sharedPrefDomainIncludes);
161
162        assertEquals("Didn't find expected sharedpref domain include.",
163                3, sharedPrefDomainIncludes.size());
164        assertEquals("Invalid path parsed for <include/>",
165                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3")
166                        .getCanonicalPath(),
167                sharedPrefDomainIncludes.get(0));
168        assertEquals("Invalid path parsed for <include/>",
169                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include3.xml")
170                        .getCanonicalPath(),
171                sharedPrefDomainIncludes.get(1));
172        assertEquals("Invalid path parsed for <include/>",
173                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "include4.xml")
174                        .getCanonicalPath(),
175                sharedPrefDomainIncludes.get(2));
176
177
178        assertEquals("Unexpected number of <exclude/>s", 7, excludesSet.size());
179        // Sets are annoying to iterate over b/c order isn't enforced - convert to an array and
180        // sort lexicographically.
181        List<String> arrayedSet = new ArrayList<String>(excludesSet);
182        Collections.sort(arrayedSet);
183
184        assertEquals("Invalid path parsed for <exclude/>",
185                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt")
186                        .getCanonicalPath(),
187                arrayedSet.get(0));
188        assertEquals("Invalid path parsed for <exclude/>",
189                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-journal")
190                        .getCanonicalPath(),
191                arrayedSet.get(1));
192        assertEquals("Invalid path parsed for <exclude/>",
193                new File(mContext.getDatabasePath("foo").getParentFile(), "exclude2.txt-wal")
194                        .getCanonicalPath(),
195                arrayedSet.get(2));
196        assertEquals("Invalid path parsed for <exclude/>",
197                new File(mContext.getFilesDir(), "exclude1.txt").getCanonicalPath(),
198                arrayedSet.get(3));
199        assertEquals("Invalid path parsed for <exclude/>",
200                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3")
201                        .getCanonicalPath(),
202                arrayedSet.get(4));
203        assertEquals("Invalid path parsed for <exclude/>",
204                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude3.xml")
205                        .getCanonicalPath(),
206                arrayedSet.get(5));
207        assertEquals("Invalid path parsed for <exclude/>",
208                new File(mContext.getSharedPrefsFile("foo").getParentFile(), "exclude4.xml")
209                        .getCanonicalPath(),
210                arrayedSet.get(6));
211    }
212
213    public void testParseBackupSchemeFromXml_invalidXmlFails() throws Exception {
214        // Invalid root tag.
215        mXpp.setInput(new StringReader(
216                "<full-weird-tag>" +
217                        "<exclude path=\"invalidRootTag.txt\" domain=\"file\"/>" +
218                        "</ffull-weird-tag>" ));
219
220        try {
221            FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
222            bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
223            fail("Invalid root xml tag should throw an XmlPullParserException");
224        } catch (XmlPullParserException expected) {}
225
226        // Invalid exclude tag.
227        mXpp.setInput(new StringReader(
228                "<full-backup-content>" +
229                        "<excluded path=\"invalidExcludeTag.txt\" domain=\"file\"/>" +
230                "</full-backup-conten>t" ));
231        try {
232            FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
233            bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
234            fail("Misspelled xml exclude tag should throw an XmlPullParserException");
235        } catch (XmlPullParserException expected) {}
236
237        // Just for good measure - invalid include tag.
238        mXpp.setInput(new StringReader(
239                "<full-backup-content>" +
240                        "<yinclude path=\"invalidIncludeTag.txt\" domain=\"file\"/>" +
241                        "</full-backup-conten>t" ));
242        try {
243            FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
244            bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
245            fail("Misspelled xml exclude tag should throw an XmlPullParserException");
246        } catch (XmlPullParserException expected) {}
247
248    }
249
250    public void testInvalidPath_doesNotBackup() throws Exception {
251        mXpp.setInput(new StringReader(
252                "<full-backup-content>" +
253                        "<exclude path=\"..\" domain=\"file\"/>" +  // Invalid use of ".." dir.
254                        "</full-backup-content>" ));
255
256        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
257        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
258
259        assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
260
261        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
262        assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
263    }
264    public void testDoubleDotInPath_isIgnored() throws Exception {
265        mXpp.setInput(new StringReader(
266                "<full-backup-content>" +
267                        "<include path=\"..\" domain=\"file\"/>" +  // Invalid use of ".." dir.
268                        "</full-backup-content>" ));
269
270        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
271        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
272
273        assertEquals("Didn't throw away invalid \"..\" path.", 0, includeMap.size());
274
275        Set<String> fileDomainIncludes = includeMap.get(FullBackup.FILES_TREE_TOKEN);
276        assertNull("Didn't throw away invalid \"..\" path.", fileDomainIncludes);
277    }
278
279    public void testDoubleSlashInPath_isIgnored() throws Exception {
280        mXpp.setInput(new StringReader(
281                "<full-backup-content>" +
282                        "<exclude path=\"//hello.txt\" domain=\"file\"/>" +  // Invalid use of "//"
283                        "</full-backup-content>" ));
284
285        FullBackup.BackupScheme bs = FullBackup.getBackupSchemeForTest(mContext);
286        bs.parseBackupSchemeFromXmlLocked(mXpp, excludesSet, includeMap);
287
288        assertEquals("Didn't throw away invalid path containing \"//\".", 0, excludesSet.size());
289    }
290}
291