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