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