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