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 */
16package libcore.tzdata.update;
17
18import junit.framework.TestCase;
19
20import java.io.ByteArrayInputStream;
21import java.io.ByteArrayOutputStream;
22import java.io.File;
23import java.io.FilterInputStream;
24import java.io.IOException;
25import java.io.InputStream;
26import java.util.ArrayList;
27import java.util.Collections;
28import java.util.List;
29import java.util.zip.ZipEntry;
30import java.util.zip.ZipOutputStream;
31import libcore.io.IoUtils;
32
33/**
34 * Tests for {@link ConfigBundle}.
35 */
36public class ConfigBundleTest extends TestCase {
37
38    private final List<File> testFiles = new ArrayList<>();
39
40    @Override
41    public void tearDown() throws Exception {
42        // Delete files / directories in reverse order.
43        Collections.reverse(testFiles);
44        for (File tempFile : testFiles) {
45            tempFile.delete();
46        }
47        super.tearDown();
48    }
49
50    public void testExtractZipSafely_goodZip() throws Exception {
51        ByteArrayOutputStream baos = new ByteArrayOutputStream();
52        try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
53            addZipEntry(zipOutputStream, "/leadingSlash");
54            addZipEntry(zipOutputStream, "absolute");
55            addZipEntry(zipOutputStream, "subDir/../file");
56            addZipEntry(zipOutputStream, "subDir/subDir/subDir/file");
57            addZipEntry(zipOutputStream, "subDir/subDir2/"); // Directory entry
58            addZipEntry(zipOutputStream, "subDir/../subDir3/"); // Directory entry
59        }
60        File dir = createTempDir();
61        File targetDir = new File(dir, "target");
62        TestInputStream inputStream =
63                new TestInputStream(new ByteArrayInputStream(baos.toByteArray()));
64        ConfigBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
65        inputStream.assertClosed();
66        assertFilesExist(
67                new File(targetDir, "leadingSlash"),
68                new File(targetDir, "absolute"),
69                new File(targetDir, "file"),
70                new File(targetDir, "subDir/subDir/subDir/file"));
71        assertDirsExist(
72                new File(targetDir, "subDir/subDir2"),
73                new File(targetDir, "subDir3"));
74    }
75
76    public void testExtractZipSafely_badZip_fileOutsideTarget() throws Exception {
77        ByteArrayOutputStream baos = new ByteArrayOutputStream();
78        try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
79            addZipEntry(zipOutputStream, "../one");
80        }
81        doExtractZipFails(baos);
82    }
83
84    public void testExtractZipSafely_badZip_dirOutsideTarget() throws Exception {
85        ByteArrayOutputStream baos = new ByteArrayOutputStream();
86        try (ZipOutputStream zipOutputStream = new ZipOutputStream(baos)) {
87            addZipEntry(zipOutputStream, "../one/");
88        }
89        doExtractZipFails(baos);
90    }
91
92    private void doExtractZipFails(ByteArrayOutputStream baos) {
93        File dir = createTempDir();
94        File targetDir = new File(dir, "target");
95        TestInputStream inputStream = new TestInputStream(
96                new ByteArrayInputStream(baos.toByteArray()));
97        try {
98            ConfigBundle.extractZipSafely(inputStream, targetDir, true /* makeWorldReadable */);
99            fail();
100        } catch (IOException expected) {
101        }
102        inputStream.assertClosed();
103    }
104
105    private static void addZipEntry(ZipOutputStream zipOutputStream, String name)
106            throws IOException {
107        ZipEntry zipEntry = new ZipEntry(name);
108        zipOutputStream.putNextEntry(zipEntry);
109        if (!zipEntry.isDirectory()) {
110            zipOutputStream.write('a');
111        }
112    }
113
114    private File createTempDir() {
115        final String tempPrefix = getClass().getSimpleName();
116        File tempDir = IoUtils.createTemporaryDirectory(tempPrefix);
117        testFiles.add(tempDir);
118        return tempDir;
119    }
120
121    private static void assertFilesExist(File... files) {
122        for (File f : files) {
123            assertTrue(f + " file expected to exist", f.exists() && f.isFile());
124        }
125    }
126
127    private static void assertDirsExist(File... dirs) {
128        for (File dir : dirs) {
129            assertTrue(dir + " directory expected to exist", dir.exists() && dir.isDirectory());
130        }
131    }
132
133    private static class TestInputStream extends FilterInputStream {
134
135        private boolean closed;
136
137        public TestInputStream(InputStream in) {
138            super(in);
139        }
140
141        @Override
142        public void close() throws IOException {
143            closed = true;
144            super.close();
145        }
146
147        public void assertClosed() {
148            assertTrue(closed);
149        }
150    }
151}
152