MIMETypeRegistry.cpp revision d73b16bdebb9d20b17be0a30e626dc9e66b6d868
1/*
2 * Copyright (C) 2006, 2008, 2009 Apple Inc.  All rights reserved.
3 * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "MIMETypeRegistry.h"
29
30#include "MediaPlayer.h"
31#include <wtf/HashMap.h>
32#include <wtf/HashSet.h>
33#include <wtf/StdLibExtras.h>
34#include <wtf/text/StringHash.h>
35
36#if PLATFORM(CG)
37#include "ImageSourceCG.h"
38#include <ApplicationServices/ApplicationServices.h>
39#include <wtf/RetainPtr.h>
40#endif
41#if PLATFORM(QT)
42#include <qimagereader.h>
43#include <qimagewriter.h>
44#endif
45
46#if ENABLE(WEB_ARCHIVE)
47#include "ArchiveFactory.h"
48#endif
49
50namespace WebCore {
51
52static HashSet<String>* supportedImageResourceMIMETypes;
53static HashSet<String>* supportedImageMIMETypes;
54static HashSet<String>* supportedImageMIMETypesForEncoding;
55static HashSet<String>* supportedJavaScriptMIMETypes;
56static HashSet<String>* supportedNonImageMIMETypes;
57static HashSet<String>* supportedMediaMIMETypes;
58
59typedef HashMap<String, Vector<String>*, CaseFoldingHash> MediaMIMETypeMap;
60
61static void initializeSupportedImageMIMETypes()
62{
63#if PLATFORM(CG)
64    RetainPtr<CFArrayRef> supportedTypes(AdoptCF, CGImageSourceCopyTypeIdentifiers());
65    CFIndex count = CFArrayGetCount(supportedTypes.get());
66    for (CFIndex i = 0; i < count; i++) {
67        RetainPtr<CFStringRef> supportedType(AdoptCF, reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i)));
68        String mimeType = MIMETypeForImageSourceType(supportedType.get());
69        if (!mimeType.isEmpty()) {
70            supportedImageMIMETypes->add(mimeType);
71            supportedImageResourceMIMETypes->add(mimeType);
72        }
73    }
74
75    // On Tiger and Leopard, com.microsoft.bmp doesn't have a MIME type in the registry.
76    supportedImageMIMETypes->add("image/bmp");
77    supportedImageResourceMIMETypes->add("image/bmp");
78
79    // Favicons don't have a MIME type in the registry either.
80    supportedImageMIMETypes->add("image/vnd.microsoft.icon");
81    supportedImageMIMETypes->add("image/x-icon");
82    supportedImageResourceMIMETypes->add("image/vnd.microsoft.icon");
83    supportedImageResourceMIMETypes->add("image/x-icon");
84
85    //  We only get one MIME type per UTI, hence our need to add these manually
86    supportedImageMIMETypes->add("image/pjpeg");
87    supportedImageResourceMIMETypes->add("image/pjpeg");
88
89    //  We don't want to try to treat all binary data as an image
90    supportedImageMIMETypes->remove("application/octet-stream");
91    supportedImageResourceMIMETypes->remove("application/octet-stream");
92
93    //  Don't treat pdf/postscript as images directly
94    supportedImageMIMETypes->remove("application/pdf");
95    supportedImageMIMETypes->remove("application/postscript");
96
97#elif PLATFORM(QT)
98    QList<QByteArray> formats = QImageReader::supportedImageFormats();
99    for (size_t i = 0; i < static_cast<size_t>(formats.size()); ++i) {
100#if ENABLE(SVG)
101        /*
102         * Qt has support for SVG, but we want to use KSVG2
103         */
104        if (formats.at(i).toLower().startsWith("svg"))
105            continue;
106#endif
107        String mimeType = MIMETypeRegistry::getMIMETypeForExtension(formats.at(i).constData());
108        if (!mimeType.isEmpty()) {
109            supportedImageMIMETypes->add(mimeType);
110            supportedImageResourceMIMETypes->add(mimeType);
111        }
112    }
113#elif PLATFORM(ANDROID)
114    static const char* types[] = {
115        "image/jpeg",
116        "image/png",
117        "image/gif",
118        "image/bmp",
119        "image/x-icon",    // ico
120        "image/ico",
121        "image/x-xbitmap"  // xbm
122    };
123    for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
124        supportedImageMIMETypes->add(types[i]);
125        supportedImageResourceMIMETypes->add(types[i]);
126    }
127    // Checked Safari impl, it seems that the HTTP stack returns
128    // multiple responses, the initial response, and then one for
129    // multipart segment. Each response is sent to the same ResourceLoader
130    // so for us to support this we would need to do the same.
131    supportedNonImageMIMETypes->remove("multipart/x-mixed-replace");
132#if !ENABLE(XSLT)
133    supportedNonImageMIMETypes->remove("text/xsl");
134#endif
135#else
136    // assume that all implementations at least support the following standard
137    // image types:
138    static const char* types[] = {
139        "image/jpeg",
140        "image/png",
141        "image/gif",
142        "image/bmp",
143        "image/vnd.microsoft.icon",    // ico
144        "image/x-icon",    // ico
145        "image/x-xbitmap"  // xbm
146    };
147    for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i) {
148        supportedImageMIMETypes->add(types[i]);
149        supportedImageResourceMIMETypes->add(types[i]);
150    }
151#endif
152}
153
154static void initializeSupportedImageMIMETypesForEncoding()
155{
156    supportedImageMIMETypesForEncoding = new HashSet<String>;
157
158#if PLATFORM(CG)
159#if PLATFORM(MAC)
160    RetainPtr<CFArrayRef> supportedTypes(AdoptCF, CGImageDestinationCopyTypeIdentifiers());
161    CFIndex count = CFArrayGetCount(supportedTypes.get());
162    for (CFIndex i = 0; i < count; i++) {
163        RetainPtr<CFStringRef> supportedType(AdoptCF, reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i)));
164        String mimeType = MIMETypeForImageSourceType(supportedType.get());
165        if (!mimeType.isEmpty())
166            supportedImageMIMETypesForEncoding->add(mimeType);
167    }
168#else
169    // FIXME: Add Windows support for all the supported UTI's when a way to convert from MIMEType to UTI reliably is found.
170    // For now, only support PNG, JPEG and GIF.  See <rdar://problem/6095286>.
171    supportedImageMIMETypesForEncoding->add("image/png");
172    supportedImageMIMETypesForEncoding->add("image/jpeg");
173    supportedImageMIMETypesForEncoding->add("image/gif");
174#endif
175#elif PLATFORM(QT)
176    QList<QByteArray> formats = QImageWriter::supportedImageFormats();
177    for (int i = 0; i < formats.size(); ++i) {
178        String mimeType = MIMETypeRegistry::getMIMETypeForExtension(formats.at(i).constData());
179        if (!mimeType.isEmpty())
180            supportedImageMIMETypesForEncoding->add(mimeType);
181    }
182#elif PLATFORM(GTK)
183    supportedImageMIMETypesForEncoding->add("image/png");
184    supportedImageMIMETypesForEncoding->add("image/jpeg");
185    supportedImageMIMETypesForEncoding->add("image/tiff");
186    supportedImageMIMETypesForEncoding->add("image/bmp");
187    supportedImageMIMETypesForEncoding->add("image/ico");
188#elif PLATFORM(CAIRO)
189    supportedImageMIMETypesForEncoding->add("image/png");
190#endif
191}
192
193static void initializeSupportedJavaScriptMIMETypes()
194{
195    /*
196        Mozilla 1.8 and WinIE 7 both accept text/javascript and text/ecmascript.
197        Mozilla 1.8 accepts application/javascript, application/ecmascript, and application/x-javascript, but WinIE 7 doesn't.
198        WinIE 7 accepts text/javascript1.1 - text/javascript1.3, text/jscript, and text/livescript, but Mozilla 1.8 doesn't.
199        Mozilla 1.8 allows leading and trailing whitespace, but WinIE 7 doesn't.
200        Mozilla 1.8 and WinIE 7 both accept the empty string, but neither accept a whitespace-only string.
201        We want to accept all the values that either of these browsers accept, but not other values.
202     */
203    static const char* types[] = {
204        "text/javascript",
205        "text/ecmascript",
206        "application/javascript",
207        "application/ecmascript",
208        "application/x-javascript",
209        "text/javascript1.1",
210        "text/javascript1.2",
211        "text/javascript1.3",
212        "text/jscript",
213        "text/livescript",
214    };
215    for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i)
216      supportedJavaScriptMIMETypes->add(types[i]);
217}
218
219static void initializeSupportedNonImageMimeTypes()
220{
221    static const char* types[] = {
222#if ENABLE(WML)
223        "text/vnd.wap.wml",
224        "application/vnd.wap.wmlc",
225#endif
226        "text/html",
227        "text/xml",
228        "text/xsl",
229        "text/plain",
230        "text/",
231        "application/xml",
232        "application/xhtml+xml",
233        "application/vnd.wap.xhtml+xml",
234        "application/rss+xml",
235        "application/atom+xml",
236        "application/json",
237#if ENABLE(SVG)
238        "image/svg+xml",
239#endif
240#if ENABLE(FTPDIR)
241        "application/x-ftp-directory",
242#endif
243        "multipart/x-mixed-replace"
244        // Note: ADDING a new type here will probably render it as HTML. This can
245        // result in cross-site scripting.
246    };
247    COMPILE_ASSERT(sizeof(types) / sizeof(types[0]) <= 16,
248                   nonimage_mime_types_must_be_less_than_or_equal_to_16);
249
250    for (size_t i = 0; i < WTF_ARRAY_LENGTH(types); ++i)
251        supportedNonImageMIMETypes->add(types[i]);
252
253#if ENABLE(WEB_ARCHIVE)
254    ArchiveFactory::registerKnownArchiveMIMETypes();
255#endif
256}
257
258static MediaMIMETypeMap& mediaMIMETypeMap()
259{
260    struct TypeExtensionPair {
261        const char* type;
262        const char* extension;
263    };
264
265    // A table of common media MIME types and file extenstions used when a platform's
266    // specific MIME type lookup doesn't have a match for a media file extension.
267    static const TypeExtensionPair pairs[] = {
268
269        // Ogg
270        { "application/ogg", "ogx" },
271        { "audio/ogg", "ogg" },
272        { "audio/ogg", "oga" },
273        { "video/ogg", "ogv" },
274
275        // Annodex
276        { "application/annodex", "anx" },
277        { "audio/annodex", "axa" },
278        { "video/annodex", "axv" },
279        { "audio/speex", "spx" },
280
281        // WebM
282        { "video/webm", "webm" },
283        { "audio/webm", "webm" },
284
285        // MPEG
286        { "audio/mpeg", "m1a" },
287        { "audio/mpeg", "m2a" },
288        { "audio/mpeg", "m1s" },
289        { "audio/mpeg", "mpa" },
290        { "video/mpeg", "mpg" },
291        { "video/mpeg", "m15" },
292        { "video/mpeg", "m1s" },
293        { "video/mpeg", "m1v" },
294        { "video/mpeg", "m75" },
295        { "video/mpeg", "mpa" },
296        { "video/mpeg", "mpeg" },
297        { "video/mpeg", "mpm" },
298        { "video/mpeg", "mpv" },
299
300        // MPEG playlist
301        { "application/vnd.apple.mpegurl", "m3u8" },
302        { "application/mpegurl", "m3u8" },
303        { "application/x-mpegurl", "m3u8" },
304        { "audio/mpegurl", "m3url" },
305        { "audio/x-mpegurl", "m3url" },
306        { "audio/mpegurl", "m3u" },
307        { "audio/x-mpegurl", "m3u" },
308
309        // MPEG-4
310        { "video/x-m4v", "m4v" },
311        { "audio/x-m4a", "m4a" },
312        { "audio/x-m4b", "m4b" },
313        { "audio/x-m4p", "m4p" },
314        { "audio/mp4", "m4a" },
315
316        // MP3
317        { "audio/mp3", "mp3" },
318        { "audio/x-mp3", "mp3" },
319        { "audio/x-mpeg", "mp3" },
320
321        // MPEG-2
322        { "video/x-mpeg2", "mp2" },
323        { "video/mpeg2", "vob" },
324        { "video/mpeg2", "mod" },
325        { "video/m2ts", "m2ts" },
326        { "video/x-m2ts", "m2t" },
327        { "video/x-m2ts", "ts" },
328
329        // 3GP/3GP2
330        { "audio/3gpp", "3gpp" },
331        { "audio/3gpp2", "3g2" },
332        { "application/x-mpeg", "amc" },
333
334        // AAC
335        { "audio/aac", "aac" },
336        { "audio/aac", "adts" },
337        { "audio/x-aac", "m4r" },
338
339        // CoreAudio File
340        { "audio/x-caf", "caf" },
341        { "audio/x-gsm", "gsm" },
342
343        // ADPCM
344        { "audio/x-wav", "wav" }
345    };
346
347    DEFINE_STATIC_LOCAL(MediaMIMETypeMap, mediaMIMETypeForExtensionMap, ());
348
349    if (!mediaMIMETypeForExtensionMap.isEmpty())
350        return mediaMIMETypeForExtensionMap;
351
352    const unsigned numPairs = sizeof(pairs) / sizeof(pairs[0]);
353    for (unsigned ndx = 0; ndx < numPairs; ++ndx) {
354
355        if (mediaMIMETypeForExtensionMap.contains(pairs[ndx].extension))
356            mediaMIMETypeForExtensionMap.get(pairs[ndx].extension)->append(pairs[ndx].type);
357        else {
358            Vector<String>* synonyms = new Vector<String>;
359
360            // If there is a system specific type for this extension, add it as the first type so
361            // getMediaMIMETypeForExtension will always return it.
362            String systemType = MIMETypeRegistry::getMIMETypeForExtension(pairs[ndx].extension);
363            if (!systemType.isEmpty() && pairs[ndx].type != systemType)
364                synonyms->append(systemType);
365            synonyms->append(pairs[ndx].type);
366            mediaMIMETypeForExtensionMap.add(pairs[ndx].extension, synonyms);
367        }
368    }
369
370    return mediaMIMETypeForExtensionMap;
371}
372
373#if ENABLE(FILE_SYSTEM) && ENABLE(WORKERS)
374String MIMETypeRegistry::getMIMETypeForExtension(const String& extension)
375{
376    return getMIMETypeForExtensionThreadSafe(extension);
377}
378#endif
379
380String MIMETypeRegistry::getMediaMIMETypeForExtension(const String& ext)
381{
382    // Look in the system-specific registry first.
383    String type = getMIMETypeForExtension(ext);
384    if (!type.isEmpty())
385        return type;
386
387    Vector<String>* typeList = mediaMIMETypeMap().get(ext);
388    if (typeList)
389        return (*typeList)[0];
390
391    return String();
392}
393
394Vector<String> MIMETypeRegistry::getMediaMIMETypesForExtension(const String& ext)
395{
396    Vector<String>* typeList = mediaMIMETypeMap().get(ext);
397    if (typeList)
398        return *typeList;
399
400    // Only need to look in the system-specific registry if mediaMIMETypeMap() doesn't contain
401    // the extension at all, because it always contains the system-specific type if the
402    // extension is in the static mapping table.
403    String type = getMIMETypeForExtension(ext);
404    if (!type.isEmpty()) {
405        Vector<String> typeList;
406        typeList.append(type);
407        return typeList;
408    }
409
410    return Vector<String>();
411}
412
413static void initializeSupportedMediaMIMETypes()
414{
415    supportedMediaMIMETypes = new HashSet<String>;
416#if ENABLE(VIDEO)
417    MediaPlayer::getSupportedTypes(*supportedMediaMIMETypes);
418#endif
419}
420
421static void initializeMIMETypeRegistry()
422{
423    supportedJavaScriptMIMETypes = new HashSet<String>;
424    initializeSupportedJavaScriptMIMETypes();
425
426    supportedNonImageMIMETypes = new HashSet<String>(*supportedJavaScriptMIMETypes);
427    initializeSupportedNonImageMimeTypes();
428
429    supportedImageResourceMIMETypes = new HashSet<String>;
430    supportedImageMIMETypes = new HashSet<String>;
431    initializeSupportedImageMIMETypes();
432}
433
434String MIMETypeRegistry::getMIMETypeForPath(const String& path)
435{
436    size_t pos = path.reverseFind('.');
437    if (pos != notFound) {
438        String extension = path.substring(pos + 1);
439        String result = getMIMETypeForExtension(extension);
440        if (result.length())
441            return result;
442    }
443    return "application/octet-stream";
444}
445
446bool MIMETypeRegistry::isSupportedImageMIMEType(const String& mimeType)
447{
448    if (mimeType.isEmpty())
449        return false;
450    if (!supportedImageMIMETypes)
451        initializeMIMETypeRegistry();
452    return supportedImageMIMETypes->contains(mimeType);
453}
454
455bool MIMETypeRegistry::isSupportedImageResourceMIMEType(const String& mimeType)
456{
457    if (mimeType.isEmpty())
458        return false;
459    if (!supportedImageResourceMIMETypes)
460        initializeMIMETypeRegistry();
461    return supportedImageResourceMIMETypes->contains(mimeType);
462}
463
464bool MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(const String& mimeType)
465{
466    ASSERT(isMainThread());
467
468    if (mimeType.isEmpty())
469        return false;
470    if (!supportedImageMIMETypesForEncoding)
471        initializeSupportedImageMIMETypesForEncoding();
472    return supportedImageMIMETypesForEncoding->contains(mimeType);
473}
474
475bool MIMETypeRegistry::isSupportedJavaScriptMIMEType(const String& mimeType)
476{
477    if (mimeType.isEmpty())
478        return false;
479    if (!supportedJavaScriptMIMETypes)
480        initializeMIMETypeRegistry();
481    return supportedJavaScriptMIMETypes->contains(mimeType);
482}
483
484bool MIMETypeRegistry::isSupportedNonImageMIMEType(const String& mimeType)
485{
486    if (mimeType.isEmpty())
487        return false;
488    if (!supportedNonImageMIMETypes)
489        initializeMIMETypeRegistry();
490    return supportedNonImageMIMETypes->contains(mimeType);
491}
492
493bool MIMETypeRegistry::isSupportedMediaMIMEType(const String& mimeType)
494{
495    if (mimeType.isEmpty())
496        return false;
497    if (!supportedMediaMIMETypes)
498        initializeSupportedMediaMIMETypes();
499    return supportedMediaMIMETypes->contains(mimeType);
500}
501
502bool MIMETypeRegistry::isJavaAppletMIMEType(const String& mimeType)
503{
504    // Since this set is very limited and is likely to remain so we won't bother with the overhead
505    // of using a hash set.
506    // Any of the MIME types below may be followed by any number of specific versions of the JVM,
507    // which is why we use startsWith()
508    return mimeType.startsWith("application/x-java-applet", false)
509        || mimeType.startsWith("application/x-java-bean", false)
510        || mimeType.startsWith("application/x-java-vm", false);
511}
512
513HashSet<String>& MIMETypeRegistry::getSupportedImageMIMETypes()
514{
515    if (!supportedImageMIMETypes)
516        initializeMIMETypeRegistry();
517    return *supportedImageMIMETypes;
518}
519
520HashSet<String>& MIMETypeRegistry::getSupportedImageResourceMIMETypes()
521{
522    if (!supportedImageResourceMIMETypes)
523        initializeMIMETypeRegistry();
524    return *supportedImageResourceMIMETypes;
525}
526
527HashSet<String>& MIMETypeRegistry::getSupportedImageMIMETypesForEncoding()
528{
529    if (!supportedImageMIMETypesForEncoding)
530        initializeSupportedImageMIMETypesForEncoding();
531    return *supportedImageMIMETypesForEncoding;
532}
533
534HashSet<String>& MIMETypeRegistry::getSupportedNonImageMIMETypes()
535{
536    if (!supportedNonImageMIMETypes)
537        initializeMIMETypeRegistry();
538    return *supportedNonImageMIMETypes;
539}
540
541HashSet<String>& MIMETypeRegistry::getSupportedMediaMIMETypes()
542{
543    if (!supportedMediaMIMETypes)
544        initializeSupportedMediaMIMETypes();
545    return *supportedMediaMIMETypes;
546}
547
548const String& defaultMIMEType()
549{
550    DEFINE_STATIC_LOCAL(const String, defaultMIMEType, ("application/octet-stream"));
551    return defaultMIMEType;
552}
553
554} // namespace WebCore
555