1 /*
2  * Copyright (C) 2019 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 
17 package android.content.type.cts;
18 
19 import android.content.type.cts.StockAndroidMimeMapFactory;
20 
21 import org.junit.Before;
22 import org.junit.Test;
23 
24 import java.net.FileNameMap;
25 import java.net.URLConnection;
26 import java.util.Locale;
27 import java.util.Objects;
28 import java.util.TreeMap;
29 import libcore.content.type.MimeMap;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertFalse;
33 import static org.junit.Assert.assertNotNull;
34 import static org.junit.Assert.assertNull;
35 import static org.junit.Assert.assertTrue;
36 import static org.junit.Assert.fail;
37 
38 /**
39  * Tests {@link MimeMap#getDefault()}.
40  */
41 public class MimeMapTest {
42 
43     /** Stock Android's default MimeMap. */
44     private MimeMap stockAndroidMimeMap;
45 
46     /** The platform's actual default MimeMap. */
47     private MimeMap mimeMap;
48 
setUp()49     @Before public void setUp() {
50         mimeMap = MimeMap.getDefault();
51         // A copy of stock Android's MimeMap.getDefault() from when this test was built,
52         // useful for comparing the platform's behavior vs. stock Android's.
53         // The resources are placed into the testres/ path by the "mimemap-testing-res.jar" genrule.
54         stockAndroidMimeMap = StockAndroidMimeMapFactory.create(
55                 s -> MimeMapTest.class.getResourceAsStream("/testres/" + s));
56     }
57 
58     @Test
defaultMap_15715370()59     public void defaultMap_15715370() {
60         assertEquals("audio/flac", mimeMap.guessMimeTypeFromExtension("flac"));
61         assertEquals("flac", mimeMap.guessExtensionFromMimeType("audio/flac"));
62         assertEquals("flac", mimeMap.guessExtensionFromMimeType("application/x-flac"));
63     }
64 
65     // https://code.google.com/p/android/issues/detail?id=78909
bug78909()66     @Test public void bug78909() {
67         assertEquals("mka", mimeMap.guessExtensionFromMimeType("audio/x-matroska"));
68         assertEquals("mkv", mimeMap.guessExtensionFromMimeType("video/x-matroska"));
69     }
70 
bug16978217()71     @Test public void bug16978217() {
72         assertEquals("image/x-ms-bmp", mimeMap.guessMimeTypeFromExtension("bmp"));
73         assertEquals("image/x-icon", mimeMap.guessMimeTypeFromExtension("ico"));
74         assertEquals("video/mp2ts", mimeMap.guessMimeTypeFromExtension("ts"));
75     }
76 
testCommon()77     @Test public void testCommon() {
78         assertEquals("audio/mpeg", mimeMap.guessMimeTypeFromExtension("mp3"));
79         assertEquals("image/png", mimeMap.guessMimeTypeFromExtension("png"));
80         assertEquals("application/zip", mimeMap.guessMimeTypeFromExtension("zip"));
81 
82         assertEquals("mp3", mimeMap.guessExtensionFromMimeType("audio/mpeg"));
83         assertEquals("png", mimeMap.guessExtensionFromMimeType("image/png"));
84         assertEquals("zip", mimeMap.guessExtensionFromMimeType("application/zip"));
85     }
86 
bug18390752()87     @Test public void bug18390752() {
88         assertEquals("jpg", mimeMap.guessExtensionFromMimeType("image/jpeg"));
89     }
90 
bug30207891()91     @Test public void bug30207891() {
92         assertTrue(mimeMap.hasMimeType("IMAGE/PNG"));
93         assertTrue(mimeMap.hasMimeType("IMAGE/png"));
94         assertFalse(mimeMap.hasMimeType(""));
95         assertEquals("png", mimeMap.guessExtensionFromMimeType("IMAGE/PNG"));
96         assertEquals("png", mimeMap.guessExtensionFromMimeType("IMAGE/png"));
97         assertNull(mimeMap.guessMimeTypeFromExtension(""));
98         assertNull(mimeMap.guessMimeTypeFromExtension("doesnotexist"));
99         assertTrue(mimeMap.hasExtension("PNG"));
100         assertTrue(mimeMap.hasExtension("PnG"));
101         assertFalse(mimeMap.hasExtension(""));
102         assertFalse(mimeMap.hasExtension(".png"));
103         assertEquals("image/png", mimeMap.guessMimeTypeFromExtension("PNG"));
104         assertEquals("image/png", mimeMap.guessMimeTypeFromExtension("PnG"));
105         assertNull(mimeMap.guessMimeTypeFromExtension(".png"));
106         assertNull(mimeMap.guessMimeTypeFromExtension(""));
107         assertNull(mimeMap.guessExtensionFromMimeType("doesnotexist"));
108     }
109 
bug30793548()110     @Test public void bug30793548() {
111         assertEquals("video/3gpp", mimeMap.guessMimeTypeFromExtension("3gpp"));
112         assertEquals("video/3gpp", mimeMap.guessMimeTypeFromExtension("3gp"));
113         assertEquals("video/3gpp2", mimeMap.guessMimeTypeFromExtension("3gpp2"));
114         assertEquals("video/3gpp2", mimeMap.guessMimeTypeFromExtension("3g2"));
115     }
116 
bug37167977()117     @Test public void bug37167977() {
118         // https://tools.ietf.org/html/rfc5334#section-10.1
119         assertEquals("audio/ogg", mimeMap.guessMimeTypeFromExtension("ogg"));
120         assertEquals("audio/ogg", mimeMap.guessMimeTypeFromExtension("oga"));
121         assertEquals("audio/ogg", mimeMap.guessMimeTypeFromExtension("spx"));
122         assertEquals("video/ogg", mimeMap.guessMimeTypeFromExtension("ogv"));
123     }
124 
bug70851634_mimeTypeFromExtension()125     @Test public void bug70851634_mimeTypeFromExtension() {
126         assertEquals("video/vnd.youtube.yt", mimeMap.guessMimeTypeFromExtension("yt"));
127     }
128 
bug70851634_extensionFromMimeType()129     @Test public void bug70851634_extensionFromMimeType() {
130         assertEquals("yt", mimeMap.guessExtensionFromMimeType("video/vnd.youtube.yt"));
131         assertEquals("yt", mimeMap.guessExtensionFromMimeType("application/vnd.youtube.yt"));
132     }
133 
bug112162449_audio()134     @Test public void bug112162449_audio() {
135         // According to https://en.wikipedia.org/wiki/M3U#Internet_media_types
136         // this is a giant mess, so we pick "audio/x-mpegurl" because a similar
137         // playlist format uses "audio/x-scpls".
138         assertMimeTypeFromExtension("audio/x-mpegurl", "m3u");
139         assertMimeTypeFromExtension("audio/x-mpegurl", "m3u8");
140         assertExtensionFromMimeType("m3u", "audio/x-mpegurl");
141 
142         assertExtensionFromMimeType("m4a", "audio/mp4");
143         assertMimeTypeFromExtension("audio/mpeg", "m4a");
144 
145         assertBidirectional("audio/aac", "aac");
146     }
147 
bug112162449_video()148     @Test public void bug112162449_video() {
149         assertBidirectional("video/x-flv", "flv");
150         assertBidirectional("video/quicktime", "mov");
151         assertBidirectional("video/mpeg", "mpeg");
152     }
153 
bug112162449_image()154     @Test public void bug112162449_image() {
155         assertBidirectional("image/heif", "heif");
156         assertBidirectional("image/heif-sequence", "heifs");
157         assertBidirectional("image/heic", "heic");
158         assertBidirectional("image/heic-sequence", "heics");
159         assertMimeTypeFromExtension("image/heif", "hif");
160 
161         assertBidirectional("image/x-adobe-dng", "dng");
162         assertBidirectional("image/x-photoshop", "psd");
163 
164         assertBidirectional("image/jp2", "jp2");
165         assertMimeTypeFromExtension("image/jp2", "jpg2");
166     }
167 
bug141654151_image()168     @Test public void bug141654151_image() {
169         assertBidirectional("image/avif", "avif");
170     }
171 
bug120135571_audio()172     @Test public void bug120135571_audio() {
173         assertMimeTypeFromExtension("audio/mpeg", "m4r");
174     }
175 
bug154667531_consistent()176     @Test public void bug154667531_consistent() {
177         // Verify that if developers start from a strongly-typed MIME type, that
178         // sending it through a file extension doesn't lose that fidelity. We're
179         // only interested in the major MIME types that are relevant to
180         // permission models; we're not worried about generic types like
181         // "application/x-flac" being mapped to "audio/flac".
182         for (String before : mimeMap.mimeTypes()) {
183             final String beforeMajor = extractMajorMimeType(before);
184             switch (beforeMajor.toLowerCase(Locale.US)) {
185                 case "audio":
186                 case "video":
187                 case "image":
188                     final String extension = mimeMap.guessExtensionFromMimeType(before);
189                     final String after = mimeMap.guessMimeTypeFromExtension(extension);
190                     final String afterMajor = extractMajorMimeType(after);
191                     if (!beforeMajor.equalsIgnoreCase(afterMajor)) {
192                         fail("Expected " + before + " to map back to " + beforeMajor
193                                 + "/* after bouncing through file extension, "
194                                 + "but instead mapped to " + after);
195                     }
196                     break;
197             }
198         }
199     }
200 
wifiConfig_xml()201     @Test public void wifiConfig_xml() {
202         assertExtensionFromMimeType("xml", "application/x-wifi-config");
203         assertMimeTypeFromExtension("text/xml", "xml");
204     }
205 
x509CaCert()206     @Test public void x509CaCert() {
207         assertMimeTypeFromExtension("application/x-x509-ca-cert", "crt");
208         assertMimeTypeFromExtension("application/x-x509-ca-cert", "der");
209     }
210 
211     // http://b/122734564
nonLowercaseMimeType()212     @Test public void nonLowercaseMimeType() {
213         // A mixed-case mimeType that appears in mime.types; we expect guessMimeTypeFromExtension()
214         // to return it in lowercase because MimeMap considers lowercase to be the canonical form.
215         String mimeType = "application/vnd.ms-word.document.macroEnabled.12".toLowerCase(Locale.US);
216         assertBidirectional(mimeType, "docm");
217     }
218 
219     // Check that the keys given for lookups in either direction are not case sensitive
caseInsensitiveKeys()220     @Test public void caseInsensitiveKeys() {
221         String mimeType = mimeMap.guessMimeTypeFromExtension("apk");
222         assertNotNull(mimeType);
223 
224         assertEquals(mimeType, mimeMap.guessMimeTypeFromExtension("APK"));
225         assertEquals(mimeType, mimeMap.guessMimeTypeFromExtension("aPk"));
226 
227         assertEquals("apk", mimeMap.guessExtensionFromMimeType(mimeType));
228         assertEquals("apk", mimeMap.guessExtensionFromMimeType(
229                 mimeType.toUpperCase(Locale.US)));
230         assertEquals("apk", mimeMap.guessExtensionFromMimeType(
231                 mimeType.toLowerCase(Locale.US)));
232     }
233 
invalidKey_empty()234     @Test public void invalidKey_empty() {
235         checkInvalidExtension("");
236         checkInvalidMimeType("");
237     }
238 
invalidKey_null()239     @Test public void invalidKey_null() {
240         checkInvalidExtension(null);
241         checkInvalidMimeType(null);
242     }
243 
invalidKey()244     @Test public void invalidKey() {
245         checkInvalidMimeType("invalid mime type");
246         checkInvalidExtension("invalid extension");
247     }
248 
containsAllStockAndroidMappings_mimeToExt()249     @Test public void containsAllStockAndroidMappings_mimeToExt() {
250         // The minimum expected mimeType -> extension mappings that should be present.
251         TreeMap<String, String> expected = new TreeMap<>();
252         // The extensions that these mimeTypes are actually mapped to.
253         TreeMap<String, String> actual = new TreeMap<>();
254         for (String mimeType : stockAndroidMimeMap.mimeTypes()) {
255             expected.put(mimeType, stockAndroidMimeMap.guessExtensionFromMimeType(mimeType));
256             actual.put(mimeType, mimeMap.guessExtensionFromMimeType(mimeType));
257         }
258         assertEquals(expected, actual);
259     }
260 
containsAllExpectedMappings_extToMime()261     @Test public void containsAllExpectedMappings_extToMime() {
262         // The minimum expected extension -> mimeType mappings that should be present.
263         TreeMap<String, String> expected = new TreeMap<>();
264         // The mimeTypes that these extensions are actually mapped to.
265         TreeMap<String, String> actual = new TreeMap<>();
266         for (String extension : stockAndroidMimeMap.extensions()) {
267             expected.put(extension, stockAndroidMimeMap.guessMimeTypeFromExtension(extension));
268             actual.put(extension, mimeMap.guessMimeTypeFromExtension(extension));
269         }
270         assertEquals(expected, actual);
271     }
272 
273     /**
274      * Checks that MimeTypeMap and URLConnection.getFileNameMap()'s behavior is
275      * consistent with MimeMap.getDefault(), i.e. that they are implemented on
276      * top of MimeMap.getDefault().
277      */
agreesWithPublicApi()278     @Test public void agreesWithPublicApi() {
279         android.webkit.MimeTypeMap webkitMap = android.webkit.MimeTypeMap.getSingleton();
280         FileNameMap urlConnectionMap = URLConnection.getFileNameMap();
281 
282         for (String extension : mimeMap.extensions()) {
283             String mimeType = mimeMap.guessMimeTypeFromExtension(extension);
284             Objects.requireNonNull(mimeType);
285             assertEquals(mimeType, webkitMap.getMimeTypeFromExtension(extension));
286             assertTrue(webkitMap.hasExtension(extension));
287             assertEquals(mimeType, urlConnectionMap.getContentTypeFor("filename." + extension));
288         }
289 
290         for (String mimeType : mimeMap.mimeTypes()) {
291             String extension = mimeMap.guessExtensionFromMimeType(mimeType);
292             Objects.requireNonNull(extension);
293             assertEquals(extension, webkitMap.getExtensionFromMimeType(mimeType));
294             assertTrue(webkitMap.hasMimeType(mimeType));
295         }
296     }
297 
checkInvalidExtension(String s)298     private void checkInvalidExtension(String s) {
299         assertFalse(mimeMap.hasExtension(s));
300         assertNull(mimeMap.guessMimeTypeFromExtension(s));
301     }
302 
checkInvalidMimeType(String s)303     private void checkInvalidMimeType(String s) {
304         assertFalse(mimeMap.hasMimeType(s));
305         assertNull(mimeMap.guessExtensionFromMimeType(s));
306     }
307 
assertMimeTypeFromExtension(String mimeType, String extension)308     private void assertMimeTypeFromExtension(String mimeType, String extension) {
309         final String actual = mimeMap.guessMimeTypeFromExtension(extension);
310         if (!Objects.equals(mimeType, actual)) {
311             fail("Expected " + mimeType + " but was " + actual + " for extension " + extension);
312         }
313     }
314 
assertExtensionFromMimeType(String extension, String mimeType)315     private void assertExtensionFromMimeType(String extension, String mimeType) {
316         final String actual = mimeMap.guessExtensionFromMimeType(mimeType);
317         if (!Objects.equals(extension, actual)) {
318             fail("Expected " + extension + " but was " + actual + " for type " + mimeType);
319         }
320     }
321 
assertBidirectional(String mimeType, String extension)322     private void assertBidirectional(String mimeType, String extension) {
323         assertMimeTypeFromExtension(mimeType, extension);
324         assertExtensionFromMimeType(extension, mimeType);
325     }
326 
extractMajorMimeType(String mimeType)327     private static String extractMajorMimeType(String mimeType) {
328         final int slash = mimeType.indexOf('/');
329         return (slash != -1) ? mimeType.substring(0, slash) : mimeType;
330     }
331 }
332