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