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