diff --git a/Tests/LibWeb/TestMimeSniff.cpp b/Tests/LibWeb/TestMimeSniff.cpp index 1bb1a0e324..cc45169361 100644 --- a/Tests/LibWeb/TestMimeSniff.cpp +++ b/Tests/LibWeb/TestMimeSniff.cpp @@ -202,3 +202,44 @@ TEST_CASE(determine_computed_mime_type_in_audio_or_video_sniffing_context) EXPECT_EQ(mime_type, computed_mime_type.essence()); } + +TEST_CASE(determine_computed_mime_type_in_a_font_context) +{ + // Cover case where supplied type is an XML MIME type. + auto mime_type = "application/rss+xml"sv; + auto supplied_type = MUST(Web::MimeSniff::MimeType::parse(mime_type)).release_value(); + auto computed_mime_type = MUST(Web::MimeSniff::Resource::sniff(""sv.bytes(), Web::MimeSniff::SniffingConfiguration { + .sniffing_context = Web::MimeSniff::SniffingContext::Font, + .supplied_type = supplied_type, + })); + + EXPECT_EQ(mime_type, MUST(computed_mime_type.serialized())); + + HashMap> mime_type_to_headers_map; + mime_type_to_headers_map.set("application/octet-stream"sv, { "\x00"sv }); + mime_type_to_headers_map.set("application/vnd.ms-fontobject"sv, { "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00LP"sv }); + mime_type_to_headers_map.set("font/ttf"sv, { "\x00\x01\x00\x00"sv }); + mime_type_to_headers_map.set("font/otf"sv, { "OTTO"sv }); + mime_type_to_headers_map.set("font/collection"sv, { "ttcf"sv }); + mime_type_to_headers_map.set("font/woff"sv, { "wOFF"sv }); + mime_type_to_headers_map.set("font/woff2"sv, { "wOF2"sv }); + + for (auto const& mime_type_to_headers : mime_type_to_headers_map) { + auto mime_type = mime_type_to_headers.key; + + for (auto const& header : mime_type_to_headers.value) { + auto computed_mime_type = MUST(Web::MimeSniff::Resource::sniff(header.bytes(), Web::MimeSniff::SniffingConfiguration { .sniffing_context = Web::MimeSniff::SniffingContext::Font })); + EXPECT_EQ(mime_type, computed_mime_type.essence()); + } + } + + // Cover case where we aren't dealing with a font MIME type. + mime_type = "text/html"sv; + supplied_type = MUST(Web::MimeSniff::MimeType::parse("text/html"sv)).release_value(); + computed_mime_type = MUST(Web::MimeSniff::Resource::sniff(""sv.bytes(), Web::MimeSniff::SniffingConfiguration { + .sniffing_context = Web::MimeSniff::SniffingContext::Font, + .supplied_type = supplied_type, + })); + + EXPECT_EQ(mime_type, computed_mime_type.essence()); +} diff --git a/Userland/Libraries/LibWeb/MimeSniff/Resource.cpp b/Userland/Libraries/LibWeb/MimeSniff/Resource.cpp index d4fa09a626..4c8f0bcb37 100644 --- a/Userland/Libraries/LibWeb/MimeSniff/Resource.cpp +++ b/Userland/Libraries/LibWeb/MimeSniff/Resource.cpp @@ -182,6 +182,50 @@ ErrorOr> match_an_audio_or_video_type_pattern(ReadonlyBytes i return OptionalNone {}; } +// https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern +ErrorOr> match_a_font_type_pattern(ReadonlyBytes input) +{ + // 1. Execute the following steps for each row row in the following table: + static Array constexpr pattern_table { + // 34 bytes followed by the string "LP", the Embedded OpenType signature. + BytePatternTableRow { + .byte_pattern = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4C\x50"sv, + .pattern_mask = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF"sv, + .ignored_leading_bytes = no_ignored_bytes, + .mime_type = "application/vnd.ms-fontobject"sv, + }, + + // 4 bytes representing the version number 1.0, a TrueType signature. + BytePatternTableRow { "\x00\x01\x00\x00"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/ttf"sv }, + + // The string "OTTO", the OpenType signature. + BytePatternTableRow { "\x4F\x54\x54\x4F"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/otf"sv }, + + // The string "ttcf", the TrueType Collection signature. + BytePatternTableRow { "\x74\x74\x63\x66"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/collection"sv }, + + // The string "wOFF", the Web Open Font Format 1.0 signature. + BytePatternTableRow { "\x77\x4F\x46\x46"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/woff"sv }, + + // The string "wOF2", the Web Open Font Format 2.0 signature. + BytePatternTableRow { "\x77\x4F\x46\x32"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/woff2"sv }, + }; + + for (auto const& row : pattern_table) { + // 1. Let patternMatched be the result of the pattern matching algorithm given input, the + // value in the first column of row, the value in the second column of row, and the + // value in the third column of row. + auto pattern_matched = pattern_matching_algorithm(input, row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes); + + // 2. If patternMatched is true, return the value in the fourth column of row. + if (pattern_matched) + return MimeType::parse(row.mime_type); + } + + // 2. Return undefined. + return OptionalNone {}; +} + // https://mimesniff.spec.whatwg.org/#matching-an-archive-type-pattern ErrorOr> match_an_archive_type_pattern(ReadonlyBytes input) { @@ -542,6 +586,8 @@ ErrorOr Resource::context_specific_sniffing_algorithm(SniffingContext snif return rules_for_sniffing_images_specifically(); if (sniffing_context == SniffingContext::AudioOrVideo) return rules_for_sniffing_audio_or_video_specifically(); + if (sniffing_context == SniffingContext::Font) + return rules_for_sniffing_fonts_specifically(); return {}; } @@ -610,4 +656,36 @@ ErrorOr Resource::rules_for_sniffing_audio_or_video_specifically() return {}; } +// https://mimesniff.spec.whatwg.org/#sniffing-in-a-font-context +ErrorOr Resource::rules_for_sniffing_fonts_specifically() +{ + // 1. If the supplied MIME type is an XML MIME type, the computed MIME type is the supplied MIME type. + // Abort these steps. + // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. + if (m_supplied_mime_type.has_value() && m_supplied_mime_type->is_xml()) { + m_computed_mime_type = m_supplied_mime_type.value(); + return {}; + } + + // 2. Let font-type-matched be the result of executing the font type pattern matching algorithm with the + // resource header as the byte sequence to be matched. + auto font_type_matched = TRY(match_a_font_type_pattern(resource_header())); + + // 3. If font-type-matched is not undefined, the computed MIME type is font-type-matched. + // Abort these steps. + if (font_type_matched.has_value()) { + m_computed_mime_type = font_type_matched.release_value(); + return {}; + } + + // 4. The computed MIME type is the supplied MIME type. + // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. + if (m_supplied_mime_type.has_value()) { + m_computed_mime_type = m_supplied_mime_type.value(); + } + + // NOTE: Non-standard but if the supplied mime type is undefined, we use computed mime type's default value. + return {}; +} + } diff --git a/Userland/Libraries/LibWeb/MimeSniff/Resource.h b/Userland/Libraries/LibWeb/MimeSniff/Resource.h index 17a89221f4..3c3a367d8c 100644 --- a/Userland/Libraries/LibWeb/MimeSniff/Resource.h +++ b/Userland/Libraries/LibWeb/MimeSniff/Resource.h @@ -14,7 +14,8 @@ enum class SniffingContext { None, Browsing, Image, - AudioOrVideo + AudioOrVideo, + Font, }; struct SniffingConfiguration { @@ -44,6 +45,7 @@ private: ErrorOr context_specific_sniffing_algorithm(SniffingContext sniffing_context); ErrorOr rules_for_sniffing_images_specifically(); ErrorOr rules_for_sniffing_audio_or_video_specifically(); + ErrorOr rules_for_sniffing_fonts_specifically(); // https://mimesniff.spec.whatwg.org/#supplied-mime-type // A supplied MIME type, the MIME type determined by the supplied MIME type detection algorithm.