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