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 libcore.io;
18
19import junit.framework.TestCase;
20
21import java.io.File;
22import java.io.FileNotFoundException;
23import java.io.IOException;
24import java.net.JarURLConnection;
25import java.net.MalformedURLException;
26import java.net.URL;
27import java.net.URLConnection;
28import java.net.URLStreamHandler;
29import java.net.UnknownServiceException;
30import java.util.Arrays;
31
32import tests.support.resource.Support_Resources;
33
34public class ClassPathURLStreamHandlerTest extends TestCase {
35
36    // A well formed jar file with 6 entries.
37    private static final String JAR = "ClassPathURLStreamHandlerTest.jar";
38    private static final String ENTRY_IN_ROOT = "root.txt";
39    private static final String DIR_ENTRY_WITHOUT_SLASH = "foo";
40    private static final String DIR_ENTRY_WITH_SLASH = DIR_ENTRY_WITHOUT_SLASH + "/";
41    private static final String ENTRY_IN_SUBDIR = "foo/bar/baz.txt";
42    private static final String ENTRY_STORED = "stored_file.txt";
43    private static final String ENTRY_WITH_SPACES_ENCODED = "file%20with%20spaces.txt";
44    private static final String ENTRY_WITH_SPACES_UNENCODED = "file with spaces.txt";
45    private static final String ENTRY_THAT_NEEDS_ESCAPING = "file_with_percent20_%20.txt";
46    private static final String ENTRY_THAT_NEEDS_ESCAPING_ENCODED = "file_with_percent20_%2520.txt";
47    private static final String ENTRY_WITH_RELATIVE_PATH = "foo/../foo/bar/baz.txt";
48    private static final String MISSING_ENTRY = "Wrong.resource";
49
50    private File jarFile;
51
52    @Override
53    protected void setUp() throws Exception {
54        File resources = Support_Resources.createTempFolder().getCanonicalFile();
55        Support_Resources.copyFile(resources, null, JAR);
56        jarFile = new File(resources, JAR);
57    }
58
59    public void testConstructor() throws Exception {
60        try {
61            ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler("Missing.file");
62            fail("Should throw IOException");
63        } catch (IOException expected) {
64        }
65
66        String fileName = jarFile.getPath();
67        ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
68        streamHandler.close();
69    }
70
71    public void testGetEntryOrNull() throws Exception {
72        String fileName = jarFile.getPath();
73        ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
74
75        checkGetEntryUrlOrNull(streamHandler, ENTRY_IN_ROOT, ENTRY_IN_ROOT);
76        checkGetEntryUrlOrNull(streamHandler, ENTRY_IN_SUBDIR, ENTRY_IN_SUBDIR);
77        checkGetEntryUrlOrNull(streamHandler, ENTRY_WITH_SPACES_UNENCODED,
78                ENTRY_WITH_SPACES_ENCODED);
79        checkGetEntryUrlOrNull(streamHandler, ENTRY_THAT_NEEDS_ESCAPING,
80                ENTRY_THAT_NEEDS_ESCAPING_ENCODED);
81
82        // getEntryOrNull() performs a lookup with and without trailing slash to handle directories.
83        // http://b/22527772
84        checkGetEntryUrlOrNull(streamHandler, DIR_ENTRY_WITHOUT_SLASH,
85                DIR_ENTRY_WITHOUT_SLASH);
86        checkGetEntryUrlOrNull(streamHandler, DIR_ENTRY_WITH_SLASH, DIR_ENTRY_WITH_SLASH);
87
88        assertNull(streamHandler.getEntryUrlOrNull(MISSING_ENTRY));
89        assertNull(streamHandler.getEntryUrlOrNull("/" + ENTRY_IN_ROOT));
90        assertNull(streamHandler.getEntryUrlOrNull("/" + ENTRY_IN_SUBDIR));
91        assertNull(streamHandler.getEntryUrlOrNull(ENTRY_WITH_SPACES_ENCODED));
92        assertNull(streamHandler.getEntryUrlOrNull(ENTRY_WITH_RELATIVE_PATH));
93        assertNull(streamHandler.getEntryUrlOrNull("/" + DIR_ENTRY_WITHOUT_SLASH));
94        assertNull(streamHandler.getEntryUrlOrNull("/" + DIR_ENTRY_WITH_SLASH));
95        streamHandler.close();
96    }
97
98    /**
99     * Check that the call to {@link ClassPathURLStreamHandler#getEntryUrlOrNull(String)} works as
100     * expected.
101     */
102    private void checkGetEntryUrlOrNull(ClassPathURLStreamHandler streamHandler,
103            String entryName, String expectedJarRelativeURI) throws IOException {
104
105        String fileName = jarFile.getPath();
106        URL urlOrNull = streamHandler.getEntryUrlOrNull(entryName);
107        assertNotNull("URL was unexpectedly null for " + entryName, urlOrNull);
108        assertEquals("jar:file:" + fileName + "!/" + expectedJarRelativeURI,
109                urlOrNull.toExternalForm());
110
111        // Make sure that the resource could be opened and the correct contents returned, i.e. the
112        // same as those read from the jar file directly.
113        assertOpenConnectionOk(jarFile, expectedJarRelativeURI, streamHandler);
114    }
115
116    public void testIsEntryStored() throws IOException {
117        String fileName = jarFile.getPath();
118        ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
119
120        assertFalse(streamHandler.isEntryStored("this/file/does/not/exist.txt"));
121        // This one is compressed
122        assertFalse(streamHandler.isEntryStored(ENTRY_IN_SUBDIR));
123        assertTrue(streamHandler.isEntryStored(ENTRY_STORED));
124
125        assertTrue(streamHandler.isEntryStored(DIR_ENTRY_WITHOUT_SLASH));
126
127        // Directory entries are just stored, empty entries with "/" on the end of the name, so
128        // "true".
129        assertTrue(streamHandler.isEntryStored(DIR_ENTRY_WITH_SLASH));
130    }
131
132    public void testOpenConnection() throws Exception {
133        String fileName = jarFile.getPath();
134        ClassPathURLStreamHandler streamHandler = new ClassPathURLStreamHandler(fileName);
135
136        assertOpenConnectionOk(jarFile, ENTRY_IN_ROOT, streamHandler);
137        assertOpenConnectionOk(jarFile, ENTRY_IN_SUBDIR, streamHandler);
138        assertOpenConnectionOk(jarFile, ENTRY_WITH_SPACES_ENCODED, streamHandler);
139        assertOpenConnectionOk(jarFile, ENTRY_WITH_SPACES_UNENCODED, streamHandler);
140        assertOpenConnectionOk(jarFile, DIR_ENTRY_WITH_SLASH, streamHandler);
141        assertOpenConnectionOk(jarFile, DIR_ENTRY_WITHOUT_SLASH, streamHandler);
142
143        assertOpenConnectionConnectFails(jarFile, ENTRY_WITH_RELATIVE_PATH, streamHandler);
144        assertOpenConnectionConnectFails(jarFile, MISSING_ENTRY, streamHandler);
145        assertOpenConnectionConnectFails(jarFile, ENTRY_THAT_NEEDS_ESCAPING, streamHandler);
146
147        streamHandler.close();
148    }
149
150    private void assertOpenConnectionConnectFails(
151            File jarFile, String entryName, URLStreamHandler streamHandler) throws IOException {
152
153        URL standardUrl = createJarUrl(jarFile, entryName, null /* use default stream handler */);
154        try {
155            standardUrl.openConnection().connect();
156            fail();
157        } catch (FileNotFoundException expected) {
158        }
159
160        URL actualUrl = createJarUrl(jarFile, entryName, streamHandler);
161        try {
162            actualUrl.openConnection().connect();
163            fail();
164        } catch (FileNotFoundException expected) {
165        }
166    }
167
168    private static void assertOpenConnectionOk(File jarFile, String entryName,
169            ClassPathURLStreamHandler streamHandler) throws IOException {
170        URL standardUrl = createJarUrl(jarFile, entryName, null /* use default stream handler */);
171        URLConnection standardUrlConnection = standardUrl.openConnection();
172        assertNotNull(standardUrlConnection);
173
174        URL actualUrl = createJarUrl(jarFile, entryName, streamHandler);
175        URLConnection actualUrlConnection = actualUrl.openConnection();
176        assertNotNull(actualUrlConnection);
177        assertBehaviorSame(standardUrlConnection, actualUrlConnection);
178    }
179
180    private static void assertBehaviorSame(URLConnection standardURLConnection,
181            URLConnection actualUrlConnection) throws IOException {
182
183        JarURLConnection standardJarUrlConnection = (JarURLConnection) standardURLConnection;
184        JarURLConnection actualJarUrlConnection = (JarURLConnection) actualUrlConnection;
185
186        byte[] actualBytes = Streams.readFully(actualJarUrlConnection.getInputStream());
187        byte[] standardBytes = Streams.readFully(standardJarUrlConnection.getInputStream());
188        assertEquals(Arrays.toString(standardBytes), Arrays.toString(actualBytes));
189
190        try {
191            actualJarUrlConnection.getOutputStream();
192            fail();
193        } catch (UnknownServiceException expected) {
194        }
195
196        assertEquals(
197                standardJarUrlConnection.getJarFile().getName(),
198                actualJarUrlConnection.getJarFile().getName());
199
200        assertEquals(
201                standardJarUrlConnection.getJarEntry().getName(),
202                actualJarUrlConnection.getJarEntry().getName());
203
204        assertEquals(
205                standardJarUrlConnection.getJarFileURL(),
206                actualJarUrlConnection.getJarFileURL());
207
208        assertEquals(
209                standardJarUrlConnection.getContentType(),
210                actualJarUrlConnection.getContentType());
211
212        assertEquals(
213                standardJarUrlConnection.getContentLength(),
214                actualJarUrlConnection.getContentLength());
215    }
216
217    private static URL createJarUrl(File jarFile, String entryName, URLStreamHandler streamHandler)
218            throws MalformedURLException {
219        return new URL("jar", null, -1, jarFile.toURI() + "!/" + entryName, streamHandler);
220    }
221
222}
223