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