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