• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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