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