1 /*
2  * Copyright (C) 2007 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.webkit;
18 
19 import android.annotation.Nullable;
20 import android.text.TextUtils;
21 
22 import libcore.content.type.MimeMap;
23 
24 import java.util.regex.Pattern;
25 
26 /**
27  * Two-way map that maps MIME-types to file extensions and vice versa.
28  *
29  * <p>See also {@link java.net.URLConnection#guessContentTypeFromName}
30  * and {@link java.net.URLConnection#guessContentTypeFromStream}. This
31  * class and {@code URLConnection} share the same MIME-type database.
32  */
33 public class MimeTypeMap {
34     private static final MimeTypeMap sMimeTypeMap = new MimeTypeMap();
35 
MimeTypeMap()36     private MimeTypeMap() {
37     }
38 
39     /**
40      * Returns the file extension or an empty string if there is no
41      * extension. This method is a convenience method for obtaining the
42      * extension of a url and has undefined results for other Strings.
43      * @param url
44      * @return The file extension of the given url.
45      */
getFileExtensionFromUrl(String url)46     public static String getFileExtensionFromUrl(String url) {
47         if (!TextUtils.isEmpty(url)) {
48             int fragment = url.lastIndexOf('#');
49             if (fragment > 0) {
50                 url = url.substring(0, fragment);
51             }
52 
53             int query = url.lastIndexOf('?');
54             if (query > 0) {
55                 url = url.substring(0, query);
56             }
57 
58             int filenamePos = url.lastIndexOf('/');
59             String filename =
60                 0 <= filenamePos ? url.substring(filenamePos + 1) : url;
61 
62             // if the filename contains special characters, we don't
63             // consider it valid for our matching purposes:
64             if (!filename.isEmpty() &&
65                 Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) {
66                 int dotPos = filename.lastIndexOf('.');
67                 if (0 <= dotPos) {
68                     return filename.substring(dotPos + 1);
69                 }
70             }
71         }
72 
73         return "";
74     }
75 
76     /**
77      * Return {@code true} if the given MIME type has an entry in the map.
78      * @param mimeType A MIME type (i.e. text/plain)
79      * @return {@code true} if there is a mimeType entry in the map.
80      */
hasMimeType(String mimeType)81     public boolean hasMimeType(String mimeType) {
82         return MimeMap.getDefault().hasMimeType(mimeType);
83     }
84 
85     /**
86      * Return the MIME type for the given extension.
87      * @param extension A file extension without the leading '.'
88      * @return The MIME type for the given extension or {@code null} if there is none.
89      */
90     @Nullable
getMimeTypeFromExtension(String extension)91     public String getMimeTypeFromExtension(String extension) {
92         return MimeMap.getDefault().guessMimeTypeFromExtension(extension);
93     }
94 
95     // Static method called by jni.
mimeTypeFromExtension(String extension)96     private static String mimeTypeFromExtension(String extension) {
97         return MimeMap.getDefault().guessMimeTypeFromExtension(extension);
98     }
99 
100     /**
101      * Return {@code true} if the given extension has a registered MIME type.
102      * @param extension A file extension without the leading '.'
103      * @return {@code true} if there is an extension entry in the map.
104      */
hasExtension(String extension)105     public boolean hasExtension(String extension) {
106         return MimeMap.getDefault().hasExtension(extension);
107     }
108 
109     /**
110      * Return the registered extension for the given MIME type. Note that some
111      * MIME types map to multiple extensions. This call will return the most
112      * common extension for the given MIME type.
113      * @param mimeType A MIME type (i.e. text/plain)
114      * @return The extension for the given MIME type or {@code null} if there is none.
115      */
116     @Nullable
getExtensionFromMimeType(String mimeType)117     public String getExtensionFromMimeType(String mimeType) {
118         return MimeMap.getDefault().guessExtensionFromMimeType(mimeType);
119     }
120 
121     /**
122      * If the given MIME type is {@code null}, or one of the "generic" types (text/plain
123      * or application/octet-stream) map it to a type that Android can deal with.
124      * If the given type is not generic, return it unchanged.
125      *
126      * @param mimeType MIME type provided by the server.
127      * @param url URL of the data being loaded.
128      * @param contentDisposition Content-disposition header given by the server.
129      * @return The MIME type that should be used for this data.
130      */
remapGenericMimeType(@ullable String mimeType, String url, String contentDisposition)131     /* package */ String remapGenericMimeType(@Nullable String mimeType, String url,
132             String contentDisposition) {
133         // If we have one of "generic" MIME types, try to deduce
134         // the right MIME type from the file extension (if any):
135         if ("text/plain".equals(mimeType) ||
136                 "application/octet-stream".equals(mimeType)) {
137 
138             // for attachment, use the filename in the Content-Disposition
139             // to guess the mimetype
140             String filename = null;
141             if (contentDisposition != null) {
142                 filename = URLUtil.parseContentDisposition(contentDisposition);
143             }
144             if (filename != null) {
145                 url = filename;
146             }
147             String extension = getFileExtensionFromUrl(url);
148             String newMimeType = getMimeTypeFromExtension(extension);
149             if (newMimeType != null) {
150                 mimeType = newMimeType;
151             }
152         } else if ("text/vnd.wap.wml".equals(mimeType)) {
153             // As we don't support wml, render it as plain text
154             mimeType = "text/plain";
155         } else {
156             // It seems that xhtml+xml and vnd.wap.xhtml+xml mime
157             // subtypes are used interchangeably. So treat them the same.
158             if ("application/vnd.wap.xhtml+xml".equals(mimeType)) {
159                 mimeType = "application/xhtml+xml";
160             }
161         }
162         return mimeType;
163     }
164 
165     /**
166      * Get the singleton instance of MimeTypeMap.
167      * @return The singleton instance of the MIME-type map.
168      */
getSingleton()169     public static MimeTypeMap getSingleton() {
170         return sMimeTypeMap;
171     }
172 }
173