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