1 /* 2 * Copyright (C) 2010 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 libcore.net; 18 19 import dalvik.annotation.compat.UnsupportedAppUsage; 20 import java.io.BufferedReader; 21 import java.io.IOException; 22 import java.io.InputStreamReader; 23 import java.util.HashMap; 24 import java.util.Locale; 25 import java.util.Map; 26 import java.util.regex.Pattern; 27 28 /** 29 * Utilities for dealing with MIME types. 30 * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap. 31 * @hide 32 */ 33 @libcore.api.CorePlatformApi 34 public final class MimeUtils { 35 36 private static final Pattern splitPattern = Pattern.compile("\\s+"); 37 38 /** 39 * Note: These maps only contain lowercase keys/values, regarded as the 40 * {@link #canonicalize(String) canonical form}. 41 * 42 * <p>This is the case for both extensions and MIME types. The mime.types 43 * data file contains examples of mixed-case MIME types, but some applications 44 * use the lowercase version of these same types. RFC 2045 section 2 states 45 * that MIME types are case insensitive. 46 */ 47 private static final Map<String, String> mimeTypeToExtensionMap = new HashMap<>(); 48 private static final Map<String, String> extensionToMimeTypeMap = new HashMap<>(); 49 50 static { 51 parseTypes("mime.types"); 52 parseTypes("android.mime.types"); 53 } 54 parseTypes(String resource)55 private static void parseTypes(String resource) { 56 try (BufferedReader r = new BufferedReader( 57 new InputStreamReader(MimeUtils.class.getResourceAsStream(resource)))) { 58 String line; 59 while ((line = r.readLine()) != null) { 60 int commentPos = line.indexOf('#'); 61 if (commentPos >= 0) { 62 line = line.substring(0, commentPos); 63 } 64 line = line.trim(); 65 if (line.equals("")) { 66 continue; 67 } 68 69 final String[] split = splitPattern.split(line); 70 final String mimeType = canonicalize(split[0]); 71 if (!allowedInMap(mimeType)) { 72 throw new IllegalArgumentException( 73 "Invalid mimeType " + mimeType + " in: " + line); 74 } 75 for (int i = 1; i < split.length; i++) { 76 String extension = canonicalize(split[i]); 77 if (!allowedInMap(extension)) { 78 throw new IllegalArgumentException( 79 "Invalid extension " + extension + " in: " + line); 80 } 81 82 // Normally the first MIME type definition wins, and the 83 // last extension definition wins. However, a file can 84 // override a MIME type definition by adding the "!" suffix 85 // to an extension. 86 87 if (extension.endsWith("!")) { 88 extension = extension.substring(0, extension.length() - 1); 89 90 // Overriding MIME definition wins 91 mimeTypeToExtensionMap.put(mimeType, extension); 92 } else { 93 // First MIME definition wins 94 if (!mimeTypeToExtensionMap.containsKey(mimeType)) { 95 mimeTypeToExtensionMap.put(mimeType, extension); 96 } 97 } 98 99 // Last extension definition wins 100 extensionToMimeTypeMap.put(extension, mimeType); 101 } 102 } 103 } catch (IOException e) { 104 throw new RuntimeException("Failed to parse " + resource, e); 105 } 106 } 107 MimeUtils()108 private MimeUtils() { 109 } 110 111 /** 112 * Returns the canonical (lowercase) form of the given extension or MIME type. 113 */ canonicalize(String s)114 private static String canonicalize(String s) { 115 return s.toLowerCase(Locale.ROOT); 116 } 117 118 /** 119 * Checks whether the given extension or MIME type might be valid and 120 * therefore may appear in the mimeType <-> extension maps. 121 */ allowedInMap(String s)122 private static boolean allowedInMap(String s) { 123 return s != null && !s.isEmpty(); 124 } 125 126 /** 127 * Returns true if the given case insensitive MIME type has an entry in the map. 128 * @param mimeType A MIME type (i.e. text/plain) 129 * @return True if a extension has been registered for 130 * the given case insensitive MIME type. 131 */ 132 @libcore.api.CorePlatformApi hasMimeType(String mimeType)133 public static boolean hasMimeType(String mimeType) { 134 return (guessExtensionFromMimeType(mimeType) != null); 135 } 136 137 /** 138 * Returns the MIME type for the given case insensitive file extension. 139 * @param extension A file extension without the leading '.' 140 * @return The MIME type has been registered for 141 * the given case insensitive file extension or null if there is none. 142 */ 143 @UnsupportedAppUsage 144 @libcore.api.CorePlatformApi guessMimeTypeFromExtension(String extension)145 public static String guessMimeTypeFromExtension(String extension) { 146 if (!allowedInMap(extension)) { 147 return null; 148 } 149 extension = canonicalize(extension); 150 return extensionToMimeTypeMap.get(extension); 151 } 152 153 /** 154 * Returns true if the given case insensitive extension has a registered MIME type. 155 * @param extension A file extension without the leading '.' 156 * @return True if a MIME type has been registered for 157 * the given case insensitive file extension. 158 */ 159 @libcore.api.CorePlatformApi hasExtension(String extension)160 public static boolean hasExtension(String extension) { 161 return (guessMimeTypeFromExtension(extension) != null); 162 } 163 164 /** 165 * Returns the registered extension for the given case insensitive MIME type. Note that some 166 * MIME types map to multiple extensions. This call will return the most 167 * common extension for the given MIME type. 168 * @param mimeType A MIME type (i.e. text/plain) 169 * @return The extension has been registered for 170 * the given case insensitive MIME type or null if there is none. 171 */ 172 @UnsupportedAppUsage 173 @libcore.api.CorePlatformApi guessExtensionFromMimeType(String mimeType)174 public static String guessExtensionFromMimeType(String mimeType) { 175 if (!allowedInMap(mimeType)) { 176 return null; 177 } 178 mimeType = canonicalize(mimeType); 179 return mimeTypeToExtensionMap.get(mimeType); 180 } 181 } 182