e5a440ddd5c85d36191bd39f7974c3d3984f59b5
[WebKit-https.git] / Source / WebCore / platform / MIMETypeRegistry.cpp
1 /*
2  * Copyright (C) 2006-2016 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 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 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/MainThread.h>
33 #include <wtf/NeverDestroyed.h>
34 #include <wtf/StdLibExtras.h>
35
36 #if USE(CG)
37 #include "ImageSourceCG.h"
38 #include "UTIRegistry.h"
39 #include <wtf/RetainPtr.h>
40 #endif
41
42 #if USE(CG) && !PLATFORM(IOS)
43 #include <ApplicationServices/ApplicationServices.h>
44 #endif
45
46 #if PLATFORM(IOS)
47 #include <ImageIO/CGImageDestination.h>
48 #endif
49
50 #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML)
51 #include "ArchiveFactory.h"
52 #endif
53
54 namespace WebCore {
55
56 namespace {
57 struct TypeExtensionPair {
58     const char* type;
59     const char* extension;
60 };
61 }
62
63 // A table of common media MIME types and file extenstions used when a platform's
64 // specific MIME type lookup doesn't have a match for a media file extension.
65 static const TypeExtensionPair commonMediaTypes[] = {
66
67     // Ogg
68     { "application/ogg", "ogx" },
69     { "audio/ogg", "ogg" },
70     { "audio/ogg", "oga" },
71     { "video/ogg", "ogv" },
72
73     // Annodex
74     { "application/annodex", "anx" },
75     { "audio/annodex", "axa" },
76     { "video/annodex", "axv" },
77     { "audio/speex", "spx" },
78
79     // WebM
80     { "video/webm", "webm" },
81     { "audio/webm", "webm" },
82
83     // MPEG
84     { "audio/mpeg", "m1a" },
85     { "audio/mpeg", "m2a" },
86     { "audio/mpeg", "m1s" },
87     { "audio/mpeg", "mpa" },
88     { "video/mpeg", "mpg" },
89     { "video/mpeg", "m15" },
90     { "video/mpeg", "m1s" },
91     { "video/mpeg", "m1v" },
92     { "video/mpeg", "m75" },
93     { "video/mpeg", "mpa" },
94     { "video/mpeg", "mpeg" },
95     { "video/mpeg", "mpm" },
96     { "video/mpeg", "mpv" },
97
98     // MPEG playlist
99     { "application/vnd.apple.mpegurl", "m3u8" },
100     { "application/mpegurl", "m3u8" },
101     { "application/x-mpegurl", "m3u8" },
102     { "audio/mpegurl", "m3url" },
103     { "audio/x-mpegurl", "m3url" },
104     { "audio/mpegurl", "m3u" },
105     { "audio/x-mpegurl", "m3u" },
106
107     // MPEG-4
108     { "video/x-m4v", "m4v" },
109     { "audio/x-m4a", "m4a" },
110     { "audio/x-m4b", "m4b" },
111     { "audio/x-m4p", "m4p" },
112     { "audio/mp4", "m4a" },
113
114     // MP3
115     { "audio/mp3", "mp3" },
116     { "audio/x-mp3", "mp3" },
117     { "audio/x-mpeg", "mp3" },
118
119     // MPEG-2
120     { "video/x-mpeg2", "mp2" },
121     { "video/mpeg2", "vob" },
122     { "video/mpeg2", "mod" },
123     { "video/m2ts", "m2ts" },
124     { "video/x-m2ts", "m2t" },
125     { "video/x-m2ts", "ts" },
126
127     // 3GP/3GP2
128     { "audio/3gpp", "3gpp" }, 
129     { "audio/3gpp2", "3g2" }, 
130     { "application/x-mpeg", "amc" },
131
132     // AAC
133     { "audio/aac", "aac" },
134     { "audio/aac", "adts" },
135     { "audio/x-aac", "m4r" },
136
137     // CoreAudio File
138     { "audio/x-caf", "caf" },
139     { "audio/x-gsm", "gsm" },
140
141     // ADPCM
142     { "audio/x-wav", "wav" }
143 };
144
145 static HashSet<String, ASCIICaseInsensitiveHash>* supportedImageResourceMIMETypes;
146 static HashSet<String, ASCIICaseInsensitiveHash>* supportedImageMIMETypes;
147 static HashSet<String, ASCIICaseInsensitiveHash>* supportedImageMIMETypesForEncoding;
148 static HashSet<String, ASCIICaseInsensitiveHash>* supportedJavaScriptMIMETypes;
149 static HashSet<String, ASCIICaseInsensitiveHash>* supportedNonImageMIMETypes;
150 static HashSet<String, ASCIICaseInsensitiveHash>* supportedMediaMIMETypes;
151 static HashSet<String, ASCIICaseInsensitiveHash>* pdfMIMETypes;
152 static HashSet<String, ASCIICaseInsensitiveHash>* pdfAndPostScriptMIMETypes;
153 static HashSet<String, ASCIICaseInsensitiveHash>* unsupportedTextMIMETypes;
154
155 typedef HashMap<String, Vector<String>*, ASCIICaseInsensitiveHash> MediaMIMETypeMap;
156     
157 static void initializeSupportedImageMIMETypes()
158 {
159 #if USE(CG)
160     HashSet<String>& imageUTIs = allowedImageUTIs();
161     for (auto& imageUTI : imageUTIs) {
162         String mimeType = MIMETypeForImageSourceType(imageUTI);
163         if (!mimeType.isEmpty()) {
164             supportedImageMIMETypes->add(mimeType);
165             supportedImageResourceMIMETypes->add(mimeType);
166         }
167     }
168
169     // On Tiger and Leopard, com.microsoft.bmp doesn't have a MIME type in the registry.
170     supportedImageMIMETypes->add("image/bmp");
171     supportedImageResourceMIMETypes->add("image/bmp");
172
173     // Favicons don't have a MIME type in the registry either.
174     supportedImageMIMETypes->add("image/vnd.microsoft.icon");
175     supportedImageMIMETypes->add("image/x-icon");
176     supportedImageResourceMIMETypes->add("image/vnd.microsoft.icon");
177     supportedImageResourceMIMETypes->add("image/x-icon");
178
179     //  We only get one MIME type per UTI, hence our need to add these manually
180     supportedImageMIMETypes->add("image/pjpeg");
181     supportedImageResourceMIMETypes->add("image/pjpeg");
182
183     //  We don't want to try to treat all binary data as an image
184     supportedImageMIMETypes->remove("application/octet-stream");
185     supportedImageResourceMIMETypes->remove("application/octet-stream");
186
187     //  Don't treat pdf/postscript as images directly
188     supportedImageMIMETypes->remove("application/pdf");
189     supportedImageMIMETypes->remove("application/postscript");
190
191 #if PLATFORM(IOS)
192     // Add malformed image mimetype for compatibility with Mail and to handle malformed mimetypes from the net
193     // These were removed for <rdar://problem/6564538> Re-enable UTI code in WebCore now that MobileCoreServices exists
194     // But Mail relies on at least image/tif reported as being supported (should be image/tiff).
195     // This can be removed when Mail addresses:
196     // <rdar://problem/7879510> Mail should use standard image mimetypes 
197     // and we fix sniffing so that it corrects items such as image/jpg -> image/jpeg.
198     static const char* malformedMIMETypes[] = {
199         // JPEG (image/jpeg)
200         "image/jpg", "image/jp_", "image/jpe_", "application/jpg", "application/x-jpg", "image/pipeg",
201         "image/vnd.switfview-jpeg", "image/x-xbitmap",
202         // GIF (image/gif)
203         "image/gi_",
204         // PNG (image/png)
205         "application/png", "application/x-png",
206         // TIFF (image/tiff)
207         "image/x-tif", "image/tif", "image/x-tiff", "application/tif", "application/x-tif", "application/tiff",
208         "application/x-tiff",
209         // BMP (image/bmp, image/x-bitmap)
210         "image/x-bmp", "image/x-win-bitmap", "image/x-windows-bmp", "image/ms-bmp", "image/x-ms-bmp",
211         "application/bmp", "application/x-bmp", "application/x-win-bitmap",
212     };
213     for (auto& type : malformedMIMETypes) {
214         supportedImageMIMETypes->add(type);
215         supportedImageResourceMIMETypes->add(type);
216     }
217 #endif
218
219 #else
220     // assume that all implementations at least support the following standard
221     // image types:
222     static const char* types[] = {
223         "image/jpeg",
224         "image/png",
225         "image/gif",
226         "image/bmp",
227         "image/vnd.microsoft.icon",    // ico
228         "image/x-icon",    // ico
229         "image/x-xbitmap"  // xbm
230     };
231     for (auto& type : types) {
232         supportedImageMIMETypes->add(type);
233         supportedImageResourceMIMETypes->add(type);
234     }
235
236 #if USE(WEBP)
237     supportedImageMIMETypes->add("image/webp");
238     supportedImageResourceMIMETypes->add("image/webp");
239 #endif
240
241 #endif // USE(CG)
242 }
243
244 static void initializeSupportedImageMIMETypesForEncoding()
245 {
246     supportedImageMIMETypesForEncoding = new HashSet<String, ASCIICaseInsensitiveHash>;
247
248 #if USE(CG)
249 #if PLATFORM(COCOA)
250     RetainPtr<CFArrayRef> supportedTypes = adoptCF(CGImageDestinationCopyTypeIdentifiers());
251     CFIndex count = CFArrayGetCount(supportedTypes.get());
252     for (CFIndex i = 0; i < count; i++) {
253         CFStringRef supportedType = reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(supportedTypes.get(), i));
254         String mimeType = MIMETypeForImageSourceType(supportedType);
255         if (!mimeType.isEmpty())
256             supportedImageMIMETypesForEncoding->add(mimeType);
257     }
258 #else
259     // FIXME: Add Windows support for all the supported UTI's when a way to convert from MIMEType to UTI reliably is found.
260     // For now, only support PNG, JPEG and GIF.  See <rdar://problem/6095286>.
261     supportedImageMIMETypesForEncoding->add("image/png");
262     supportedImageMIMETypesForEncoding->add("image/jpeg");
263     supportedImageMIMETypesForEncoding->add("image/gif");
264 #endif
265 #elif PLATFORM(GTK)
266     supportedImageMIMETypesForEncoding->add("image/png");
267     supportedImageMIMETypesForEncoding->add("image/jpeg");
268     supportedImageMIMETypesForEncoding->add("image/tiff");
269     supportedImageMIMETypesForEncoding->add("image/bmp");
270     supportedImageMIMETypesForEncoding->add("image/ico");
271 #elif USE(CAIRO)
272     supportedImageMIMETypesForEncoding->add("image/png");
273 #endif
274 }
275
276 static void initializeSupportedJavaScriptMIMETypes()
277 {
278     // https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type
279     static const char* types[] = {
280         "text/javascript",
281         "text/ecmascript",
282         "application/javascript",
283         "application/ecmascript",
284         "application/x-javascript",
285         "application/x-ecmascript",
286         "text/javascript1.0",
287         "text/javascript1.1",
288         "text/javascript1.2",
289         "text/javascript1.3",
290         "text/javascript1.4",
291         "text/javascript1.5",
292         "text/jscript",
293         "text/livescript",
294         "text/x-javascript",
295         "text/x-ecmascript"
296     };
297     for (auto* type : types)
298         supportedJavaScriptMIMETypes->add(type);
299 }
300
301 static void initializePDFMIMETypes()
302 {
303     const char* const types[] = {
304         "application/pdf",
305         "text/pdf"
306     };
307     for (auto& type : types)
308         pdfMIMETypes->add(type);
309 }
310
311 static void initializePostScriptMIMETypes()
312 {
313     pdfAndPostScriptMIMETypes->add("application/postscript");
314 }
315
316 static void initializeSupportedNonImageMimeTypes()
317 {
318     static const char* types[] = {
319         "text/html",
320         "text/xml",
321         "text/xsl",
322         "text/plain",
323         "text/",
324         "application/xml",
325         "application/xhtml+xml",
326 #if !PLATFORM(IOS)
327         "application/vnd.wap.xhtml+xml",
328         "application/rss+xml",
329         "application/atom+xml",
330 #endif
331         "application/json",
332         "image/svg+xml",
333 #if ENABLE(FTPDIR)
334         "application/x-ftp-directory",
335 #endif
336         "multipart/x-mixed-replace"
337         // Note: Adding a new type here will probably render it as HTML.
338         // This can result in cross-site scripting vulnerabilities.
339     };
340
341     for (auto& type : types)
342         supportedNonImageMIMETypes->add(type);
343
344 #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML)
345     ArchiveFactory::registerKnownArchiveMIMETypes();
346 #endif
347 }
348
349 static MediaMIMETypeMap& mediaMIMETypeMap()
350 {
351     static NeverDestroyed<MediaMIMETypeMap> mediaMIMETypeForExtensionMap;
352
353     if (!mediaMIMETypeForExtensionMap.get().isEmpty())
354         return mediaMIMETypeForExtensionMap;
355
356     const unsigned numPairs = sizeof(commonMediaTypes) / sizeof(commonMediaTypes[0]);
357     for (unsigned ndx = 0; ndx < numPairs; ++ndx) {
358
359         if (mediaMIMETypeForExtensionMap.get().contains(commonMediaTypes[ndx].extension))
360             mediaMIMETypeForExtensionMap.get().get(commonMediaTypes[ndx].extension)->append(commonMediaTypes[ndx].type);
361         else {
362             Vector<String>* synonyms = new Vector<String>;
363
364             // If there is a system specific type for this extension, add it as the first type so
365             // getMediaMIMETypeForExtension will always return it.
366             String systemType = MIMETypeRegistry::getMIMETypeForExtension(commonMediaTypes[ndx].extension);
367             if (!systemType.isEmpty() && commonMediaTypes[ndx].type != systemType)
368                 synonyms->append(systemType);
369             synonyms->append(commonMediaTypes[ndx].type);
370             mediaMIMETypeForExtensionMap.get().add(commonMediaTypes[ndx].extension, synonyms);
371         }
372     }
373
374     return mediaMIMETypeForExtensionMap;
375 }
376
377 String MIMETypeRegistry::getMediaMIMETypeForExtension(const String& ext)
378 {
379     // Look in the system-specific registry first.
380     String type = getMIMETypeForExtension(ext);
381     if (!type.isEmpty())
382         return type;
383
384     Vector<String>* typeList = mediaMIMETypeMap().get(ext);
385     if (typeList)
386         return (*typeList)[0];
387     
388     return String();
389 }
390     
391 Vector<String> MIMETypeRegistry::getMediaMIMETypesForExtension(const String& ext)
392 {
393     Vector<String>* typeList = mediaMIMETypeMap().get(ext);
394     if (typeList)
395         return *typeList;
396
397     // Only need to look in the system-specific registry if mediaMIMETypeMap() doesn't contain
398     // the extension at all, because it always contains the system-specific type if the
399     // extension is in the static mapping table.
400     String type = getMIMETypeForExtension(ext);
401     if (!type.isEmpty()) {
402         Vector<String> typeList;
403         typeList.append(type);
404         return typeList;
405     }
406     
407     return Vector<String>();
408 }
409
410 static void initializeSupportedMediaMIMETypes()
411 {
412     supportedMediaMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>;
413 #if ENABLE(VIDEO)
414     MediaPlayer::getSupportedTypes(*supportedMediaMIMETypes);
415 #endif
416 }
417
418 static void initializeUnsupportedTextMIMETypes()
419 {
420     static const char* types[] = {
421         "text/calendar",
422         "text/x-calendar",
423         "text/x-vcalendar",
424         "text/vcalendar",
425         "text/vcard",
426         "text/x-vcard",
427         "text/directory",
428         "text/ldif",
429         "text/qif",
430         "text/x-qif",
431         "text/x-csv",
432         "text/x-vcf",
433 #if !PLATFORM(IOS)
434         "text/rtf",
435 #else
436         "text/vnd.sun.j2me.app-descriptor",
437 #endif
438     };
439     for (auto& type : types)
440         unsupportedTextMIMETypes->add(type);
441 }
442
443 static void initializeMIMETypeRegistry()
444 {
445     supportedJavaScriptMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>;
446     initializeSupportedJavaScriptMIMETypes();
447
448     supportedNonImageMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>(*supportedJavaScriptMIMETypes);
449     initializeSupportedNonImageMimeTypes();
450
451     supportedImageResourceMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>;
452     supportedImageMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>;
453     initializeSupportedImageMIMETypes();
454
455     pdfMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>;
456     initializePDFMIMETypes();
457
458     pdfAndPostScriptMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>(*pdfMIMETypes);
459     initializePostScriptMIMETypes();
460
461     unsupportedTextMIMETypes = new HashSet<String, ASCIICaseInsensitiveHash>;
462     initializeUnsupportedTextMIMETypes();
463 }
464
465 String MIMETypeRegistry::getMIMETypeForPath(const String& path)
466 {
467     size_t pos = path.reverseFind('.');
468     if (pos != notFound) {
469         String extension = path.substring(pos + 1);
470         String result = getMIMETypeForExtension(extension);
471         if (result.length())
472             return result;
473     }
474     return defaultMIMEType();
475 }
476
477 bool MIMETypeRegistry::isSupportedImageMIMEType(const String& mimeType)
478 {
479     if (mimeType.isEmpty())
480         return false;
481     if (!supportedImageMIMETypes)
482         initializeMIMETypeRegistry();
483     return supportedImageMIMETypes->contains(getNormalizedMIMEType(mimeType));
484 }
485
486 bool MIMETypeRegistry::isSupportedImageResourceMIMEType(const String& mimeType)
487 {
488     if (mimeType.isEmpty())
489         return false;
490     if (!supportedImageResourceMIMETypes)
491         initializeMIMETypeRegistry();
492     return supportedImageResourceMIMETypes->contains(getNormalizedMIMEType(mimeType));
493 }
494
495 bool MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(const String& mimeType)
496 {
497     ASSERT(isMainThread());
498
499     if (mimeType.isEmpty())
500         return false;
501     if (!supportedImageMIMETypesForEncoding)
502         initializeSupportedImageMIMETypesForEncoding();
503     return supportedImageMIMETypesForEncoding->contains(mimeType);
504 }
505
506 bool MIMETypeRegistry::isSupportedJavaScriptMIMEType(const String& mimeType)
507 {
508     if (mimeType.isEmpty())
509         return false;
510     if (!supportedJavaScriptMIMETypes)
511         initializeMIMETypeRegistry();
512     return supportedJavaScriptMIMETypes->contains(mimeType);
513 }
514
515 bool MIMETypeRegistry::isSupportedJSONMIMEType(const String& mimeType)
516 {
517     if (mimeType.isEmpty())
518         return false;
519
520     if (equalLettersIgnoringASCIICase(mimeType, "application/json"))
521         return true;
522
523     // When detecting +json ensure there is a non-empty type / subtype preceeding the suffix.
524     if (mimeType.endsWith("+json", false) && mimeType.length() >= 8) {
525         size_t slashPosition = mimeType.find('/');
526         if (slashPosition != notFound && slashPosition > 0 && slashPosition <= mimeType.length() - 6)
527             return true;
528     }
529
530     return false;
531 }
532
533 bool MIMETypeRegistry::isSupportedNonImageMIMEType(const String& mimeType)
534 {
535     if (mimeType.isEmpty())
536         return false;
537     if (!supportedNonImageMIMETypes)
538         initializeMIMETypeRegistry();
539     return supportedNonImageMIMETypes->contains(mimeType);
540 }
541
542 bool MIMETypeRegistry::isSupportedMediaMIMEType(const String& mimeType)
543 {
544     if (mimeType.isEmpty())
545         return false;
546     if (!supportedMediaMIMETypes)
547         initializeSupportedMediaMIMETypes();
548     return supportedMediaMIMETypes->contains(mimeType);
549 }
550
551 bool MIMETypeRegistry::isUnsupportedTextMIMEType(const String& mimeType)
552 {
553     if (mimeType.isEmpty())
554         return false;
555     if (!unsupportedTextMIMETypes)
556         initializeMIMETypeRegistry();
557     return unsupportedTextMIMETypes->contains(mimeType);
558 }
559
560 bool MIMETypeRegistry::isTextMIMEType(const String& mimeType)
561 {
562     return isSupportedJavaScriptMIMEType(mimeType)
563         || isSupportedJSONMIMEType(mimeType) // Render JSON as text/plain.
564         || (mimeType.startsWith("text/", false)
565             && !equalLettersIgnoringASCIICase(mimeType, "text/html")
566             && !equalLettersIgnoringASCIICase(mimeType, "text/xml")
567             && !equalLettersIgnoringASCIICase(mimeType, "text/xsl"));
568 }
569
570
571 static inline bool isValidXMLMIMETypeChar(UChar c)
572 {
573     // Valid characters per RFCs 3023 and 2045: 0-9a-zA-Z_-+~!$^{}|.%'`#&*
574     return isASCIIAlphanumeric(c) || c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+'
575         || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '{' || c == '|' || c == '}' || c == '~';
576 }
577
578 bool MIMETypeRegistry::isXMLMIMEType(const String& mimeType)
579 {
580     if (equalLettersIgnoringASCIICase(mimeType, "text/xml") || equalLettersIgnoringASCIICase(mimeType, "application/xml") || equalLettersIgnoringASCIICase(mimeType, "text/xsl"))
581         return true;
582
583     if (!mimeType.endsWith("+xml", false))
584         return false;
585
586     size_t slashPosition = mimeType.find('/');
587     // Take into account the '+xml' ending of mimeType.
588     if (slashPosition == notFound || !slashPosition || slashPosition == mimeType.length() - 5)
589         return false;
590
591     // Again, mimeType ends with '+xml', no need to check the validity of that substring.
592     size_t mimeLength = mimeType.length();
593     for (size_t i = 0; i < mimeLength - 4; ++i) {
594         if (!isValidXMLMIMETypeChar(mimeType[i]) && i != slashPosition)
595             return false;
596     }
597
598     return true;
599 }
600
601 bool MIMETypeRegistry::isJavaAppletMIMEType(const String& mimeType)
602 {
603     // Since this set is very limited and is likely to remain so we won't bother with the overhead
604     // of using a hash set.
605     // Any of the MIME types below may be followed by any number of specific versions of the JVM,
606     // which is why we use startsWith()
607     return mimeType.startsWith("application/x-java-applet", false)
608         || mimeType.startsWith("application/x-java-bean", false)
609         || mimeType.startsWith("application/x-java-vm", false);
610 }
611
612 bool MIMETypeRegistry::isPDFOrPostScriptMIMEType(const String& mimeType)
613 {
614     if (mimeType.isEmpty())
615         return false;
616     if (!pdfAndPostScriptMIMETypes)
617         initializeMIMETypeRegistry();
618     return pdfAndPostScriptMIMETypes->contains(mimeType);
619 }
620
621 bool MIMETypeRegistry::isPDFMIMEType(const String& mimeType)
622 {
623     if (mimeType.isEmpty())
624         return false;
625     if (!pdfMIMETypes)
626         initializeMIMETypeRegistry();
627     return pdfMIMETypes->contains(mimeType);
628 }
629
630 bool MIMETypeRegistry::canShowMIMEType(const String& mimeType)
631 {
632     if (isSupportedImageMIMEType(mimeType) || isSupportedNonImageMIMEType(mimeType) || isSupportedMediaMIMEType(mimeType))
633         return true;
634
635     if (mimeType.startsWith("text/", false))
636         return !isUnsupportedTextMIMEType(mimeType);
637
638     return false;
639 }
640
641 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getSupportedImageMIMETypes()
642 {
643     if (!supportedImageMIMETypes)
644         initializeMIMETypeRegistry();
645     return *supportedImageMIMETypes;
646 }
647
648 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getSupportedImageResourceMIMETypes()
649 {
650     if (!supportedImageResourceMIMETypes)
651         initializeMIMETypeRegistry();
652     return *supportedImageResourceMIMETypes;
653 }
654
655 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getSupportedImageMIMETypesForEncoding()
656 {
657     if (!supportedImageMIMETypesForEncoding)
658         initializeSupportedImageMIMETypesForEncoding();
659     return *supportedImageMIMETypesForEncoding;
660 }
661
662 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getSupportedNonImageMIMETypes()
663 {
664     if (!supportedNonImageMIMETypes)
665         initializeMIMETypeRegistry();
666     return *supportedNonImageMIMETypes;
667 }
668
669 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getSupportedMediaMIMETypes()
670 {
671     if (!supportedMediaMIMETypes)
672         initializeSupportedMediaMIMETypes();
673     return *supportedMediaMIMETypes;
674 }
675
676
677 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getPDFMIMETypes()
678 {
679     if (!pdfMIMETypes)
680         initializeMIMETypeRegistry();
681     return *pdfMIMETypes;
682 }
683
684 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getPDFAndPostScriptMIMETypes()
685 {
686     if (!pdfAndPostScriptMIMETypes)
687         initializeMIMETypeRegistry();
688     return *pdfAndPostScriptMIMETypes;
689 }
690
691 HashSet<String, ASCIICaseInsensitiveHash>& MIMETypeRegistry::getUnsupportedTextMIMETypes()
692 {
693     if (!unsupportedTextMIMETypes)
694         initializeMIMETypeRegistry();
695     return *unsupportedTextMIMETypes;
696 }
697
698 const String& defaultMIMEType()
699 {
700     static NeverDestroyed<const String> defaultMIMEType(ASCIILiteral("application/octet-stream"));
701     return defaultMIMEType;
702 }
703
704 #if !USE(CURL)
705
706 // FIXME: Not sure why it makes sense to have a cross-platform function when only CURL has the concept
707 // of a "normalized" MIME type.
708 String MIMETypeRegistry::getNormalizedMIMEType(const String& mimeType)
709 {
710     return mimeType;
711 }
712
713 #else
714
715 typedef HashMap<String, String, ASCIICaseInsensitiveHash> MIMETypeAssociationMap;
716
717 static const MIMETypeAssociationMap& mimeTypeAssociationMap()
718 {
719     static MIMETypeAssociationMap* mimeTypeMap = 0;
720     if (mimeTypeMap)
721         return *mimeTypeMap;
722
723     // FIXME: Should not allocate this on the heap; use NeverDestroyed instead.
724     mimeTypeMap = new MIMETypeAssociationMap;
725
726     // FIXME: Writing the function out like this will create a giant function.
727     // Should use a loop instead.
728     mimeTypeMap->add(ASCIILiteral("image/x-ms-bmp"), ASCIILiteral("image/bmp"));
729     mimeTypeMap->add(ASCIILiteral("image/x-windows-bmp"), ASCIILiteral("image/bmp"));
730     mimeTypeMap->add(ASCIILiteral("image/x-bmp"), ASCIILiteral("image/bmp"));
731     mimeTypeMap->add(ASCIILiteral("image/x-bitmap"), ASCIILiteral("image/bmp"));
732     mimeTypeMap->add(ASCIILiteral("image/x-ms-bitmap"), ASCIILiteral("image/bmp"));
733     mimeTypeMap->add(ASCIILiteral("image/jpg"), ASCIILiteral("image/jpeg"));
734     mimeTypeMap->add(ASCIILiteral("image/pjpeg"), ASCIILiteral("image/jpeg"));
735     mimeTypeMap->add(ASCIILiteral("image/x-png"), ASCIILiteral("image/png"));
736     mimeTypeMap->add(ASCIILiteral("image/vnd.rim.png"), ASCIILiteral("image/png"));
737     mimeTypeMap->add(ASCIILiteral("image/ico"), ASCIILiteral("image/vnd.microsoft.icon"));
738     mimeTypeMap->add(ASCIILiteral("image/icon"), ASCIILiteral("image/vnd.microsoft.icon"));
739     mimeTypeMap->add(ASCIILiteral("text/ico"), ASCIILiteral("image/vnd.microsoft.icon"));
740     mimeTypeMap->add(ASCIILiteral("application/ico"), ASCIILiteral("image/vnd.microsoft.icon"));
741     mimeTypeMap->add(ASCIILiteral("image/x-icon"), ASCIILiteral("image/vnd.microsoft.icon"));
742     mimeTypeMap->add(ASCIILiteral("audio/vnd.qcelp"), ASCIILiteral("audio/qcelp"));
743     mimeTypeMap->add(ASCIILiteral("audio/qcp"), ASCIILiteral("audio/qcelp"));
744     mimeTypeMap->add(ASCIILiteral("audio/vnd.qcp"), ASCIILiteral("audio/qcelp"));
745     mimeTypeMap->add(ASCIILiteral("audio/wav"), ASCIILiteral("audio/x-wav"));
746     mimeTypeMap->add(ASCIILiteral("audio/mid"), ASCIILiteral("audio/midi"));
747     mimeTypeMap->add(ASCIILiteral("audio/sp-midi"), ASCIILiteral("audio/midi"));
748     mimeTypeMap->add(ASCIILiteral("audio/x-mid"), ASCIILiteral("audio/midi"));
749     mimeTypeMap->add(ASCIILiteral("audio/x-midi"), ASCIILiteral("audio/midi"));
750     mimeTypeMap->add(ASCIILiteral("audio/x-mpeg"), ASCIILiteral("audio/mpeg"));
751     mimeTypeMap->add(ASCIILiteral("audio/mp3"), ASCIILiteral("audio/mpeg"));
752     mimeTypeMap->add(ASCIILiteral("audio/x-mp3"), ASCIILiteral("audio/mpeg"));
753     mimeTypeMap->add(ASCIILiteral("audio/mpeg3"), ASCIILiteral("audio/mpeg"));
754     mimeTypeMap->add(ASCIILiteral("audio/x-mpeg3"), ASCIILiteral("audio/mpeg"));
755     mimeTypeMap->add(ASCIILiteral("audio/mpg3"), ASCIILiteral("audio/mpeg"));
756     mimeTypeMap->add(ASCIILiteral("audio/mpg"), ASCIILiteral("audio/mpeg"));
757     mimeTypeMap->add(ASCIILiteral("audio/x-mpg"), ASCIILiteral("audio/mpeg"));
758     mimeTypeMap->add(ASCIILiteral("audio/m4a"), ASCIILiteral("audio/mp4"));
759     mimeTypeMap->add(ASCIILiteral("audio/x-m4a"), ASCIILiteral("audio/mp4"));
760     mimeTypeMap->add(ASCIILiteral("audio/x-mp4"), ASCIILiteral("audio/mp4"));
761     mimeTypeMap->add(ASCIILiteral("audio/x-aac"), ASCIILiteral("audio/aac"));
762     mimeTypeMap->add(ASCIILiteral("audio/x-amr"), ASCIILiteral("audio/amr"));
763     mimeTypeMap->add(ASCIILiteral("audio/mpegurl"), ASCIILiteral("audio/x-mpegurl"));
764     mimeTypeMap->add(ASCIILiteral("audio/flac"), ASCIILiteral("audio/x-flac"));
765     mimeTypeMap->add(ASCIILiteral("video/3gp"), ASCIILiteral("video/3gpp"));
766     mimeTypeMap->add(ASCIILiteral("video/avi"), ASCIILiteral("video/x-msvideo"));
767     mimeTypeMap->add(ASCIILiteral("video/x-m4v"), ASCIILiteral("video/mp4"));
768     mimeTypeMap->add(ASCIILiteral("video/x-quicktime"), ASCIILiteral("video/quicktime"));
769     mimeTypeMap->add(ASCIILiteral("application/java"), ASCIILiteral("application/java-archive"));
770     mimeTypeMap->add(ASCIILiteral("application/x-java-archive"), ASCIILiteral("application/java-archive"));
771     mimeTypeMap->add(ASCIILiteral("application/x-zip-compressed"), ASCIILiteral("application/zip"));
772     mimeTypeMap->add(ASCIILiteral("text/cache-manifest"), ASCIILiteral("text/plain"));
773
774     return *mimeTypeMap;
775 }
776
777 String MIMETypeRegistry::getNormalizedMIMEType(const String& mimeType)
778 {
779     auto it = mimeTypeAssociationMap().find(mimeType);
780     if (it != mimeTypeAssociationMap().end())
781         return it->value;
782     return mimeType;
783 }
784
785 #endif
786
787 String MIMETypeRegistry::appendFileExtensionIfNecessary(const String& filename, const String& mimeType)
788 {
789     if (filename.isEmpty())
790         return emptyString();
791
792     if (filename.reverseFind('.') != notFound)
793         return filename;
794
795     String preferredExtension = getPreferredExtensionForMIMEType(mimeType);
796     if (preferredExtension.isEmpty())
797         return filename;
798
799     return filename + "." + preferredExtension;
800 }
801
802 } // namespace WebCore