/* * Copyright (C) 2006-2021 Apple Inc. All rights reserved. * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "MIMETypeRegistry.h" #include "MediaPlayer.h" #include "ThreadGlobalData.h" #include #include #include #include #include #include #include #if USE(CG) #include "ImageSourceCG.h" #include "UTIRegistry.h" #include #include #endif #if USE(CG) && PLATFORM(COCOA) #include "UTIUtilities.h" #endif #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) #include "ArchiveFactory.h" #endif #if PLATFORM(HAIKU) #include #include #include #include #include #include #endif #if HAVE(AVASSETREADER) #include "ContentType.h" #include "ImageDecoderAVFObjC.h" #endif #if USE(QUICK_LOOK) #include "PreviewConverter.h" #endif #if USE(GSTREAMER) && ENABLE(VIDEO) #include "ImageDecoderGStreamer.h" #endif namespace WebCore { static String normalizedImageMIMEType(const String&); // On iOS, we include malformed image MIME types for compatibility with Mail. // These were removed for Re-enable UTI code in WebCore now that // MobileCoreServices exists. But Mail relies on at least image/tif reported as being // supported (should be image/tiff). This can be removed when Mail addresses: // Mail should use standard image mimetypes // and we fix sniffing so that it corrects items such as image/jpg -> image/jpeg. constexpr ComparableCaseFoldingASCIILiteral supportedImageMIMETypeArray[] = { #if PLATFORM(IOS_FAMILY) "application/bmp", "application/jpg", "application/png", "application/tif", "application/tiff", "application/x-bmp", "application/x-jpg", "application/x-png", "application/x-tif", "application/x-tiff", "application/x-win-bitmap", #endif #if USE(CG) || ENABLE(APNG) "image/apng", #endif #if USE(AVIF) "image/avif", #endif "image/bmp", #if PLATFORM(IOS_FAMILY) "image/gi_", #endif "image/gif", #if USE(CG) || USE(OPENJPEG) "image/jp2", #endif #if PLATFORM(IOS_FAMILY) "image/jp_", "image/jpe_", #endif "image/jpeg", #if !USE(CG) && USE(OPENJPEG) "image/jpeg2000", #endif #if PLATFORM(IOS_FAMILY) "image/ms-bmp", "image/pipeg", #endif #if USE(CG) "image/pjpeg", #endif "image/png", #if PLATFORM(IOS_FAMILY) "image/tif", #endif #if USE(CG) "image/tiff", #endif "image/vnd.microsoft.icon", #if PLATFORM(IOS_FAMILY) "image/vnd.switfview-jpeg", #endif #if (USE(CG) && HAVE(WEBP)) || (!USE(CG) && USE(WEBP)) "image/webp", #endif #if PLATFORM(IOS_FAMILY) "image/x-bmp", #endif "image/x-icon", #if PLATFORM(IOS_FAMILY) "image/x-ms-bmp", "image/x-tif", "image/x-tiff", "image/x-win-bitmap", "image/x-windows-bmp", #endif #if PLATFORM(IOS_FAMILY) || !USE(CG) "image/x-xbitmap", #endif }; template static FixedVector makeFixedVector(const ComparableASCIISubsetLiteral (&array)[size]) { FixedVector result(std::size(array)); std::transform(std::begin(array), std::end(array), result.begin(), [] (auto literal) { return literal.literal; }); return result; } FixedVector MIMETypeRegistry::supportedImageMIMETypes() { return makeFixedVector(supportedImageMIMETypeArray); } HashSet& MIMETypeRegistry::additionalSupportedImageMIMETypes() { static NeverDestroyed> additionalSupportedImageMIMETypes; return additionalSupportedImageMIMETypes; } // https://html.spec.whatwg.org/multipage/scripting.html#javascript-mime-type constexpr ComparableLettersLiteral supportedJavaScriptMIMETypeArray[] = { "application/ecmascript", "application/javascript", "application/x-ecmascript", "application/x-javascript", "text/ecmascript", "text/javascript", "text/javascript1.0", "text/javascript1.1", "text/javascript1.2", "text/javascript1.3", "text/javascript1.4", "text/javascript1.5", "text/jscript", "text/livescript", "text/x-ecmascript", "text/x-javascript", }; HashSet& MIMETypeRegistry::supportedNonImageMIMETypes() { static auto supportedNonImageMIMETypes = makeNeverDestroyed([] { HashSet supportedNonImageMIMETypes = std::initializer_list { "text/html"_s, "text/xml"_s, "text/xsl"_s, "text/plain"_s, "text/"_s, "application/xml"_s, "application/xhtml+xml"_s, #if !PLATFORM(IOS_FAMILY) "application/vnd.wap.xhtml+xml"_s, "application/rss+xml"_s, "application/atom+xml"_s, #endif "application/json"_s, "image/svg+xml"_s, #if ENABLE(FTPDIR) "application/x-ftp-directory"_s, #endif "multipart/x-mixed-replace"_s, // Note: Adding a new type here will probably render it as HTML. // This can result in cross-site scripting vulnerabilities. }; for (auto& type : supportedJavaScriptMIMETypeArray) supportedNonImageMIMETypes.add(type.literal); #if ENABLE(WEB_ARCHIVE) || ENABLE(MHTML) ArchiveFactory::registerKnownArchiveMIMETypes(supportedNonImageMIMETypes); #endif return supportedNonImageMIMETypes; }()); return supportedNonImageMIMETypes; } const HashSet& MIMETypeRegistry::supportedMediaMIMETypes() { static const auto supportedMediaMIMETypes = makeNeverDestroyed([] { HashSet supportedMediaMIMETypes; #if ENABLE(VIDEO) MediaPlayer::getSupportedTypes(supportedMediaMIMETypes); #endif return supportedMediaMIMETypes; }()); return supportedMediaMIMETypes; } constexpr ComparableLettersLiteral pdfMIMETypeArray[] = { "application/pdf", "text/pdf", }; FixedVector MIMETypeRegistry::pdfMIMETypes() { return makeFixedVector(pdfMIMETypeArray); } constexpr ComparableLettersLiteral unsupportedTextMIMETypeArray[] = { "text/calendar", "text/directory", "text/ldif", "text/qif", #if !PLATFORM(IOS_FAMILY) "text/rtf", #endif "text/vcalendar", "text/vcard", #if PLATFORM(IOS_FAMILY) "text/vnd.sun.j2me.app-descriptor", #endif "text/x-calendar", "text/x-csv", "text/x-qif", "text/x-vcalendar", "text/x-vcard", "text/x-vcf", }; FixedVector MIMETypeRegistry::unsupportedTextMIMETypes() { return makeFixedVector(unsupportedTextMIMETypeArray); } static const HashMap, ASCIICaseInsensitiveHash>& commonMimeTypesMap() { ASSERT(isMainThread()); static NeverDestroyed, ASCIICaseInsensitiveHash>> mimeTypesMap = [] { HashMap, ASCIICaseInsensitiveHash> map; // A table of common media MIME types and file extensions used when a platform's // specific MIME type lookup doesn't have a match for a media file extension. static constexpr TypeExtensionPair commonMediaTypes[] = { // Ogg { "application/ogg"_s, "ogx"_s }, { "audio/ogg"_s, "ogg"_s }, { "audio/ogg"_s, "oga"_s }, { "video/ogg"_s, "ogv"_s }, // Annodex { "application/annodex"_s, "anx"_s }, { "audio/annodex"_s, "axa"_s }, { "video/annodex"_s, "axv"_s }, { "audio/speex"_s, "spx"_s }, // WebM { "video/webm"_s, "webm"_s }, { "audio/webm"_s, "webm"_s }, // MPEG { "audio/mpeg"_s, "m1a"_s }, { "audio/mpeg"_s, "m2a"_s }, { "audio/mpeg"_s, "m1s"_s }, { "audio/mpeg"_s, "mpa"_s }, { "video/mpeg"_s, "mpg"_s }, { "video/mpeg"_s, "m15"_s }, { "video/mpeg"_s, "m1s"_s }, { "video/mpeg"_s, "m1v"_s }, { "video/mpeg"_s, "m75"_s }, { "video/mpeg"_s, "mpa"_s }, { "video/mpeg"_s, "mpeg"_s }, { "video/mpeg"_s, "mpm"_s }, { "video/mpeg"_s, "mpv"_s }, // MPEG playlist { "application/vnd.apple.mpegurl"_s, "m3u8"_s }, { "application/mpegurl"_s, "m3u8"_s }, { "application/x-mpegurl"_s, "m3u8"_s }, { "audio/mpegurl"_s, "m3url"_s }, { "audio/x-mpegurl"_s, "m3url"_s }, { "audio/mpegurl"_s, "m3u"_s }, { "audio/x-mpegurl"_s, "m3u"_s }, // MPEG-4 { "video/x-m4v"_s, "m4v"_s }, { "audio/x-m4a"_s, "m4a"_s }, { "audio/x-m4b"_s, "m4b"_s }, { "audio/x-m4p"_s, "m4p"_s }, { "audio/mp4"_s, "m4a"_s }, // MP3 { "audio/mp3"_s, "mp3"_s }, { "audio/x-mp3"_s, "mp3"_s }, { "audio/x-mpeg"_s, "mp3"_s }, // MPEG-2 { "video/x-mpeg2"_s, "mp2"_s }, { "video/mpeg2"_s, "vob"_s }, { "video/mpeg2"_s, "mod"_s }, { "video/m2ts"_s, "m2ts"_s }, { "video/x-m2ts"_s, "m2t"_s }, { "video/x-m2ts"_s, "ts"_s }, // 3GP/3GP2 { "audio/3gpp"_s, "3gpp"_s }, { "audio/3gpp2"_s, "3g2"_s }, { "application/x-mpeg"_s, "amc"_s }, // AAC { "audio/aac"_s, "aac"_s }, { "audio/aac"_s, "adts"_s }, { "audio/x-aac"_s, "m4r"_s }, // CoreAudio File { "audio/x-caf"_s, "caf"_s }, { "audio/x-gsm"_s, "gsm"_s }, // ADPCM { "audio/x-wav"_s, "wav"_s }, { "audio/vnd.wave"_s, "wav"_s }, }; for (auto& pair : commonMediaTypes) { ASCIILiteral type = pair.type; ASCIILiteral extension = pair.extension; map.ensure(extension, [type, extension] { // First type in the vector must always be the one from mimeTypeForExtension, // so we can use the map without also calling mimeTypeForExtension each time. Vector synonyms; String systemType = MIMETypeRegistry::mimeTypeForExtension(extension); if (!systemType.isEmpty() && type != systemType) synonyms.append(systemType); return synonyms; }).iterator->value.append(type); } return map; }(); return mimeTypesMap; } static const Vector* typesForCommonExtension(const String& extension) { auto mapEntry = commonMimeTypesMap().find(extension); if (mapEntry == commonMimeTypesMap().end()) return nullptr; return &mapEntry->value; } String MIMETypeRegistry::mediaMIMETypeForExtension(const String& extension) { auto* vector = typesForCommonExtension(extension); if (vector) return (*vector)[0]; return mimeTypeForExtension(extension); } String MIMETypeRegistry::mimeTypeForPath(const String& path) { #if PLATFORM(HAIKU) // On Haiku, files don't usually have an extension. But files usually // have a mime type file attribute. // If this is a local path, get an entry while also resolving symbolic // links and get the mime type info. BString localPath(path); if (localPath.FindFirst("file://") == 0 && localPath.Length() > 7) { BEntry entry(localPath.String() + 7, true); if (entry.Exists()) { BNode node(&entry); BNodeInfo nodeInfo(&node); char mimeType[B_MIME_TYPE_LENGTH]; if (nodeInfo.GetType(mimeType) == B_OK) return mimeType; } } #endif size_t pos = path.reverseFind('.'); if (pos != notFound) { String extension = path.substring(pos + 1); String result = mimeTypeForExtension(extension); if (result.length()) return result; } return defaultMIMEType(); } bool MIMETypeRegistry::isSupportedImageMIMEType(const String& mimeType) { if (mimeType.isEmpty()) return false; static constexpr SortedArraySet supportedImageMIMETypeSet { supportedImageMIMETypeArray }; #if USE(CG) && ASSERT_ENABLED // Esnure supportedImageMIMETypeArray matches defaultSupportedImageTypes(). static std::once_flag onceFlag; std::call_once(onceFlag, [] { for (auto& imageType : defaultSupportedImageTypes()) { auto mimeType = MIMETypeForImageType(imageType); ASSERT_IMPLIES(!mimeType.isEmpty(), supportedImageMIMETypeSet.contains(mimeType)); } }); #endif if (supportedImageMIMETypeSet.contains(mimeType)) return true; return additionalSupportedImageMIMETypes().contains(normalizedImageMIMEType(mimeType)); } bool MIMETypeRegistry::isSupportedImageVideoOrSVGMIMEType(const String& mimeType) { if (isSupportedImageMIMEType(mimeType) || equalLettersIgnoringASCIICase(mimeType, "image/svg+xml")) return true; #if HAVE(AVASSETREADER) if (ImageDecoderAVFObjC::supportsContainerType(mimeType)) return true; #endif #if USE(GSTREAMER) && ENABLE(VIDEO) if (ImageDecoderGStreamer::supportsContainerType(mimeType)) return true; #endif return false; } std::unique_ptr MIMETypeRegistry::createMIMETypeRegistryThreadGlobalData() { #if PLATFORM(COCOA) RetainPtr supportedTypes = adoptCF(CGImageDestinationCopyTypeIdentifiers()); HashSet supportedImageMIMETypesForEncoding; CFIndex count = CFArrayGetCount(supportedTypes.get()); for (CFIndex i = 0; i < count; i++) { CFStringRef supportedType = reinterpret_cast(CFArrayGetValueAtIndex(supportedTypes.get(), i)); if (!isSupportedImageType(supportedType)) continue; String mimeType = MIMETypeForImageType(supportedType); if (mimeType.isEmpty()) continue; supportedImageMIMETypesForEncoding.add(mimeType); } #else HashSet supportedImageMIMETypesForEncoding = std::initializer_list { #if USE(CG) || USE(DIRECT2D) // FIXME: Add Windows support for all the supported UTI's when a way to convert from MIMEType to UTI reliably is found. // For now, only support PNG, JPEG and GIF. See . "image/png"_s, "image/jpeg"_s, "image/gif"_s, #elif PLATFORM(GTK) "image/png"_s, "image/jpeg"_s, "image/tiff"_s, "image/bmp"_s, "image/ico"_s, #elif USE(CAIRO) "image/png"_s, #endif }; #endif return makeUnique(WTFMove(supportedImageMIMETypesForEncoding)); } bool MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(const String& mimeType) { if (mimeType.isEmpty()) return false; return threadGlobalData().mimeTypeRegistryThreadGlobalData().supportedImageMIMETypesForEncoding().contains(mimeType); } bool MIMETypeRegistry::isSupportedJavaScriptMIMEType(const String& mimeType) { static constexpr SortedArraySet supportedJavaScriptMIMETypes { supportedJavaScriptMIMETypeArray }; return supportedJavaScriptMIMETypes.contains(mimeType); } bool MIMETypeRegistry::isSupportedStyleSheetMIMEType(const String& mimeType) { return equalLettersIgnoringASCIICase(mimeType, "text/css"); } bool MIMETypeRegistry::isSupportedFontMIMEType(const String& mimeType) { static const unsigned fontLength = 5; if (!startsWithLettersIgnoringASCIICase(mimeType, "font/")) return false; auto subtype = StringView { mimeType }.substring(fontLength); return equalLettersIgnoringASCIICase(subtype, "woff") || equalLettersIgnoringASCIICase(subtype, "woff2") || equalLettersIgnoringASCIICase(subtype, "otf") || equalLettersIgnoringASCIICase(subtype, "ttf") || equalLettersIgnoringASCIICase(subtype, "sfnt"); } bool MIMETypeRegistry::isTextMediaPlaylistMIMEType(const String& mimeType) { if (startsWithLettersIgnoringASCIICase(mimeType, "application/")) { static const unsigned applicationLength = 12; auto subtype = StringView { mimeType }.substring(applicationLength); return equalLettersIgnoringASCIICase(subtype, "vnd.apple.mpegurl") || equalLettersIgnoringASCIICase(subtype, "mpegurl") || equalLettersIgnoringASCIICase(subtype, "x-mpegurl"); } if (startsWithLettersIgnoringASCIICase(mimeType, "audio/")) { static const unsigned audioLength = 6; auto subtype = StringView { mimeType }.substring(audioLength); return equalLettersIgnoringASCIICase(subtype, "mpegurl") || equalLettersIgnoringASCIICase(subtype, "x-mpegurl"); } return false; } bool MIMETypeRegistry::isSupportedJSONMIMEType(const String& mimeType) { if (mimeType.isEmpty()) return false; if (equalLettersIgnoringASCIICase(mimeType, "application/json")) return true; // When detecting +json ensure there is a non-empty type / subtype preceeding the suffix. if (mimeType.endsWithIgnoringASCIICase("+json") && mimeType.length() >= 8) { size_t slashPosition = mimeType.find('/'); if (slashPosition != notFound && slashPosition > 0 && slashPosition <= mimeType.length() - 6) return true; } return false; } bool MIMETypeRegistry::isSupportedNonImageMIMEType(const String& mimeType) { if (mimeType.isEmpty()) return false; return supportedNonImageMIMETypes().contains(mimeType); } bool MIMETypeRegistry::isSupportedMediaMIMEType(const String& mimeType) { if (mimeType.isEmpty()) return false; return supportedMediaMIMETypes().contains(mimeType); } bool MIMETypeRegistry::isSupportedTextTrackMIMEType(const String& mimeType) { return equalLettersIgnoringASCIICase(mimeType, "text/vtt"); } bool MIMETypeRegistry::isUnsupportedTextMIMEType(const String& mimeType) { static constexpr SortedArraySet unsupportedTextMIMETypes { unsupportedTextMIMETypeArray }; return unsupportedTextMIMETypes.contains(mimeType); } bool MIMETypeRegistry::isTextMIMEType(const String& mimeType) { return isSupportedJavaScriptMIMEType(mimeType) || isSupportedJSONMIMEType(mimeType) // Render JSON as text/plain. || (startsWithLettersIgnoringASCIICase(mimeType, "text/") && !equalLettersIgnoringASCIICase(mimeType, "text/html") && !equalLettersIgnoringASCIICase(mimeType, "text/xml") && !equalLettersIgnoringASCIICase(mimeType, "text/xsl")); } static inline bool isValidXMLMIMETypeChar(UChar c) { // Valid characters per RFCs 3023 and 2045: 0-9a-zA-Z_-+~!$^{}|.%'`#&* return isASCIIAlphanumeric(c) || c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '{' || c == '|' || c == '}' || c == '~'; } bool MIMETypeRegistry::isXMLMIMEType(const String& mimeType) { if (equalLettersIgnoringASCIICase(mimeType, "text/xml") || equalLettersIgnoringASCIICase(mimeType, "application/xml") || equalLettersIgnoringASCIICase(mimeType, "text/xsl")) return true; if (!mimeType.endsWithIgnoringASCIICase("+xml")) return false; size_t slashPosition = mimeType.find('/'); // Take into account the '+xml' ending of mimeType. if (slashPosition == notFound || !slashPosition || slashPosition == mimeType.length() - 5) return false; // Again, mimeType ends with '+xml', no need to check the validity of that substring. size_t mimeLength = mimeType.length(); for (size_t i = 0; i < mimeLength - 4; ++i) { if (!isValidXMLMIMETypeChar(mimeType[i]) && i != slashPosition) return false; } return true; } bool MIMETypeRegistry::isXMLEntityMIMEType(StringView mimeType) { return equalLettersIgnoringASCIICase(mimeType, "text/xml-external-parsed-entity") || equalLettersIgnoringASCIICase(mimeType, "application/xml-external-parsed-entity"); } bool MIMETypeRegistry::isJavaAppletMIMEType(const String& mimeType) { // Since this set is very limited and is likely to remain so we won't bother with the overhead // of using a hash set. // Any of the MIME types below may be followed by any number of specific versions of the JVM, // which is why we use startsWith() return startsWithLettersIgnoringASCIICase(mimeType, "application/x-java-applet") || startsWithLettersIgnoringASCIICase(mimeType, "application/x-java-bean") || startsWithLettersIgnoringASCIICase(mimeType, "application/x-java-vm"); } bool MIMETypeRegistry::isPDFMIMEType(const String& mimeType) { static constexpr SortedArraySet set { pdfMIMETypeArray }; return set.contains(mimeType); } bool MIMETypeRegistry::isPostScriptMIMEType(const String& mimeType) { return equalLettersIgnoringASCIICase(mimeType, "application/postscript"); } bool MIMETypeRegistry::isPDFOrPostScriptMIMEType(const String& mimeType) { return isPDFMIMEType(mimeType) || isPostScriptMIMEType(mimeType); } bool MIMETypeRegistry::canShowMIMEType(const String& mimeType) { if (isSupportedImageMIMEType(mimeType) || isSupportedNonImageMIMEType(mimeType) || isSupportedMediaMIMEType(mimeType)) return true; if (isSupportedJavaScriptMIMEType(mimeType) || isSupportedJSONMIMEType(mimeType)) return true; #if USE(QUICK_LOOK) if (PreviewConverter::supportsMIMEType(mimeType)) return true; #endif if (startsWithLettersIgnoringASCIICase(mimeType, "text/")) return !isUnsupportedTextMIMEType(mimeType); return false; } const String& defaultMIMEType() { static NeverDestroyed defaultMIMEType(MAKE_STATIC_STRING_IMPL("application/octet-stream")); return defaultMIMEType; } constexpr ComparableLettersLiteral systemPreviewMIMETypeArray[] = { "model/usd", // Unofficial, but supported because we documented this. "model/vnd.pixar.usd", // Unofficial, but supported because we documented this. "model/vnd.reality", "model/vnd.usdz+zip", // The official type: https://www.iana.org/assignments/media-types/model/vnd.usdz+zip }; FixedVector MIMETypeRegistry::systemPreviewMIMETypes() { return makeFixedVector(systemPreviewMIMETypeArray); } bool MIMETypeRegistry::isSystemPreviewMIMEType(const String& mimeType) { static constexpr SortedArraySet systemPreviewMIMETypeSet { systemPreviewMIMETypeArray }; return systemPreviewMIMETypeSet.contains(mimeType); } // FIXME: Not great that CURL needs this concept; other platforms do not. static String normalizedImageMIMEType(const String& mimeType) { #if USE(CURL) return mimeType; #else // FIXME: Since this is only used in isSupportedImageMIMEType, we should consider removing the non-image types below. static constexpr std::pair mimeTypeAssociationArray[] = { { "application/ico", "image/vnd.microsoft.icon"_s }, { "application/java", "application/java-archive"_s }, { "application/x-java-archive", "application/java-archive"_s }, { "application/x-zip-compressed", "application/zip"_s }, { "audio/flac", "audio/x-flac"_s }, { "audio/m4a", "audio/mp4"_s }, { "audio/mid", "audio/midi"_s }, { "audio/mp3", "audio/mpeg"_s }, { "audio/mpeg3", "audio/mpeg"_s }, { "audio/mpegurl", "audio/x-mpegurl"_s }, { "audio/mpg", "audio/mpeg"_s }, { "audio/mpg3", "audio/mpeg"_s }, { "audio/qcp", "audio/qcelp"_s }, { "audio/sp-midi", "audio/midi"_s }, { "audio/vnd.qcelp", "audio/qcelp"_s }, { "audio/vnd.qcp", "audio/qcelp"_s }, { "audio/vnd.wave", "audio/x-wav"_s }, { "audio/wav", "audio/x-wav"_s }, { "audio/x-aac", "audio/aac"_s }, { "audio/x-amr", "audio/amr"_s }, { "audio/x-m4a", "audio/mp4"_s }, { "audio/x-mid", "audio/midi"_s }, { "audio/x-midi", "audio/midi"_s }, { "audio/x-mp3", "audio/mpeg"_s }, { "audio/x-mp4", "audio/mp4"_s }, { "audio/x-mpeg", "audio/mpeg"_s }, { "audio/x-mpeg3", "audio/mpeg"_s }, { "audio/x-mpg", "audio/mpeg"_s }, { "image/ico", "image/vnd.microsoft.icon"_s }, { "image/icon", "image/vnd.microsoft.icon"_s }, { "image/jpg", "image/jpeg"_s }, { "image/pjpeg", "image/jpeg"_s }, { "image/vnd.rim.png", "image/png"_s }, { "image/x-bitmap", "image/bmp"_s }, { "image/x-bmp", "image/bmp"_s }, { "image/x-icon", "image/vnd.microsoft.icon"_s }, { "image/x-ms-bitmap", "image/bmp"_s }, { "image/x-ms-bmp", "image/bmp"_s }, { "image/x-png", "image/png"_s }, { "image/x-windows-bmp", "image/bmp"_s }, { "text/cache-manifest", "text/plain"_s }, { "text/ico", "image/vnd.microsoft.icon"_s }, { "video/3gp", "video/3gpp"_s }, { "video/avi", "video/x-msvideo"_s }, { "video/x-m4v", "video/mp4"_s }, { "video/x-quicktime", "video/quicktime"_s }, }; static constexpr SortedArrayMap associationMap { mimeTypeAssociationArray }; auto normalizedType = associationMap.tryGet(mimeType); return normalizedType ? *normalizedType : mimeType; #endif } String MIMETypeRegistry::appendFileExtensionIfNecessary(const String& filename, const String& mimeType) { if (filename.isEmpty() || filename.contains('.') || equalIgnoringASCIICase(mimeType, defaultMIMEType())) return filename; auto preferredExtension = preferredExtensionForMIMEType(mimeType); if (preferredExtension.isEmpty()) return filename; return makeString(filename, '.', preferredExtension); } static inline String trimmedExtension(const String& extension) { return extension.startsWith('.') ? extension.right(extension.length() - 1) : extension; } String MIMETypeRegistry::preferredImageMIMETypeForEncoding(const Vector& mimeTypes, const Vector& extensions) { auto allowedMIMETypes = MIMETypeRegistry::allowedMIMETypes(mimeTypes, extensions); auto position = allowedMIMETypes.findMatching([](const auto& mimeType) { return MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType); }); return position != notFound ? allowedMIMETypes[position] : nullString(); } bool MIMETypeRegistry::containsImageMIMETypeForEncoding(const Vector& mimeTypes, const Vector& extensions) { return !MIMETypeRegistry::preferredImageMIMETypeForEncoding(mimeTypes, extensions).isNull(); } Vector MIMETypeRegistry::allowedMIMETypes(const Vector& mimeTypes, const Vector& extensions) { Vector allowedMIMETypes; for (auto& mimeType : mimeTypes) allowedMIMETypes.appendIfNotContains(mimeType.convertToASCIILowercase()); for (auto& extension : extensions) { auto mimeType = MIMETypeRegistry::mimeTypeForExtension(trimmedExtension(extension)); if (mimeType.isEmpty()) continue; allowedMIMETypes.appendIfNotContains(mimeType.convertToASCIILowercase()); } return allowedMIMETypes; } Vector MIMETypeRegistry::allowedFileExtensions(const Vector& mimeTypes, const Vector& extensions) { Vector allowedFileExtensions; for (auto& mimeType : mimeTypes) { for (auto& extension : MIMETypeRegistry::extensionsForMIMEType(mimeType)) allowedFileExtensions.appendIfNotContains(extension); } for (auto& extension : extensions) allowedFileExtensions.appendIfNotContains(trimmedExtension(extension)); return allowedFileExtensions; } } // namespace WebCore