/src/serenity/Userland/Libraries/LibWeb/MimeSniff/Resource.cpp
Line | Count | Source |
1 | | /* |
2 | | * Copyright (c) 2023-2024, Kemal Zebari <kemalzebra@gmail.com>. |
3 | | * |
4 | | * SPDX-License-Identifier: BSD-2-Clause |
5 | | */ |
6 | | |
7 | | #include <LibWeb/Fetch/Infrastructure/URL.h> |
8 | | #include <LibWeb/MimeSniff/Resource.h> |
9 | | |
10 | | namespace { |
11 | | |
12 | | using namespace Web::MimeSniff; |
13 | | |
14 | | struct BytePatternTableRow { |
15 | | StringView byte_pattern; |
16 | | StringView pattern_mask; |
17 | | ReadonlyBytes ignored_leading_bytes; |
18 | | StringView mime_type; |
19 | | |
20 | | // NOTE: If the byte pattern has a tag-terminating byte, add a byte where this byte should be. The value itself is ignored in |
21 | | // the pattern_matching_algorithm() (see the NOTE in this algorithm for more details). |
22 | | bool is_tag_terminated { false }; |
23 | | }; |
24 | | |
25 | | // https://mimesniff.spec.whatwg.org/#tag-terminating-byte |
26 | | bool is_tag_terminating_byte(u8 byte) |
27 | 0 | { |
28 | | // A tag-terminating byte (abbreviated 0xTT) is any one of the following bytes: 0x20 (SP), 0x3E (">"). |
29 | 0 | return byte == 0x20 || byte == 0x3E; |
30 | 0 | } |
31 | | |
32 | | // https://mimesniff.spec.whatwg.org/#binary-data-byte |
33 | | bool is_binary_data_byte(u8 byte) |
34 | 0 | { |
35 | | // A binary data byte is a byte in the range 0x00 to 0x08 (NUL to BS), the byte 0x0B (VT), a byte in |
36 | | // the range 0x0E to 0x1A (SO to SUB), or a byte in the range 0x1C to 0x1F (FS to US). |
37 | 0 | return (byte <= 0x08) || byte == 0x0B || (byte >= 0x0E && byte <= 0x1A) || (byte >= 0x1C && byte <= 0x1F); |
38 | 0 | } |
39 | | |
40 | | // https://mimesniff.spec.whatwg.org/#pattern-matching-algorithm |
41 | | bool pattern_matching_algorithm(ReadonlyBytes input, ReadonlyBytes pattern, ReadonlyBytes mask, ReadonlyBytes ignored, bool is_tag_terminated = false) |
42 | 0 | { |
43 | | // 1. Assert: pattern’s length is equal to mask’s length. |
44 | 0 | VERIFY(pattern.size() == mask.size()); |
45 | | |
46 | | // 2. If input’s length is less than pattern’s length, return false. |
47 | 0 | if (input.size() < pattern.size()) |
48 | 0 | return false; |
49 | | |
50 | | // 3. Let s be 0. |
51 | 0 | size_t s = 0; |
52 | | |
53 | | // 4. While s < input’s length: |
54 | 0 | while (s < input.size()) { |
55 | | // 1. If ignored does not contain input[s], break. |
56 | 0 | if (!ignored.contains_slow(input[s])) |
57 | 0 | break; |
58 | | |
59 | | // 2. Set s to s + 1. |
60 | 0 | s++; |
61 | 0 | } |
62 | | |
63 | | // 5. Let p be 0. |
64 | 0 | size_t p = 0; |
65 | | |
66 | | // 6. While p < pattern’s length: |
67 | 0 | while (p < pattern.size()) { |
68 | | // 1. Let maskedData be the result of applying the bitwise AND operator to input[s] and mask[p]. |
69 | 0 | u8 masked_data = input[s] & mask[p]; |
70 | | |
71 | | // NOTE: This non-standard branch exists to avoid having to create 2 byte patterns just so that |
72 | | // they can only differ by their tag-terminating byte (which could be a 0x20 or 0x3E byte). |
73 | 0 | if (is_tag_terminated && p + 1 == pattern.size()) |
74 | 0 | return is_tag_terminating_byte(masked_data); |
75 | | |
76 | | // 2. If maskedData is not equal to pattern[p], return false. |
77 | 0 | if (masked_data != pattern[p]) |
78 | 0 | return false; |
79 | | |
80 | | // 3. Set s to s + 1. |
81 | 0 | s++; |
82 | | |
83 | | // 4. Set p to p + 1. |
84 | 0 | p++; |
85 | 0 | } |
86 | | |
87 | | // 7. Return true. |
88 | 0 | return true; |
89 | 0 | } |
90 | | |
91 | | ReadonlyBytes constexpr no_ignored_bytes; |
92 | | |
93 | | // https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern |
94 | | Optional<MimeType> match_an_image_type_pattern(ReadonlyBytes input) |
95 | 0 | { |
96 | | // 1. Execute the following steps for each row row in the following table: |
97 | 0 | static Array<BytePatternTableRow, 8> constexpr pattern_table { |
98 | | // A Windows Icon signature. |
99 | 0 | BytePatternTableRow { "\x00\x00\x01\x00"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "image/x-icon"sv }, |
100 | | |
101 | | // A Windows Cursor signature. |
102 | 0 | BytePatternTableRow { "\x00\x00\x02\x00"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "image/x-icon"sv }, |
103 | | |
104 | | // The string "BM", a BMP signature. |
105 | 0 | BytePatternTableRow { "\x42\x4D"sv, "\xFF\xFF"sv, no_ignored_bytes, "image/bmp"sv }, |
106 | | |
107 | | // The string "GIF87a", a GIF signature. |
108 | 0 | BytePatternTableRow { "\x47\x49\x46\x38\x37\x61"sv, "\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "image/gif"sv }, |
109 | | |
110 | | // The string "GIF89a", a GIF signature. |
111 | 0 | BytePatternTableRow { "\x47\x49\x46\x38\x39\x61"sv, "\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "image/gif"sv }, |
112 | | |
113 | | // The string "RIFF" followed by four bytes followed by the string "WEBPVP". |
114 | 0 | BytePatternTableRow { "\x52\x49\x46\x46\x00\x00\x00\x00\x57\x45\x42\x50\x56\x50"sv, |
115 | 0 | "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "image/webp"sv }, |
116 | | |
117 | | // An error-checking byte followed by the string "PNG" followed by CR LF SUB LF, the PNG signature. |
118 | 0 | BytePatternTableRow { "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A"sv, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "image/png"sv }, |
119 | | |
120 | | // The JPEG Start of Image marker followed by the indicator byte of another marker. |
121 | 0 | BytePatternTableRow { "\xFF\xD8\xFF"sv, "\xFF\xFF\xFF"sv, no_ignored_bytes, "image/jpeg"sv }, |
122 | 0 | }; |
123 | |
|
124 | 0 | for (auto const& row : pattern_table) { |
125 | | // 1. Let patternMatched be the result of the pattern matching algorithm given input, the value in |
126 | | // the first column of row, the value in the second column of row, and the value in the third |
127 | | // column of row. |
128 | 0 | auto pattern_matched = pattern_matching_algorithm(input, row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes); |
129 | | |
130 | | // 2. If patternMatched is true, return the value in the fourth column of row. |
131 | 0 | if (pattern_matched) |
132 | 0 | return MimeType::parse(row.mime_type); |
133 | 0 | } |
134 | | |
135 | | // 2. Return undefined. |
136 | 0 | return OptionalNone {}; |
137 | 0 | } |
138 | | |
139 | | // https://mimesniff.spec.whatwg.org/#signature-for-mp4 |
140 | | bool matches_mp4_signature(ReadonlyBytes sequence) |
141 | 0 | { |
142 | | // 1. Let sequence be the byte sequence to be matched, where sequence[s] is byte s in sequence and sequence[0] is the first byte in sequence. |
143 | | |
144 | | // 2. Let length be the number of bytes in sequence. |
145 | 0 | auto length = sequence.size(); |
146 | | |
147 | | // 3. If length is less than 12, return false. |
148 | 0 | if (length < 12) |
149 | 0 | return false; |
150 | | |
151 | | // 4. Let box-size be the four bytes from sequence[0] to sequence[3], interpreted as a 32-bit unsigned big-endian integer. |
152 | 0 | u32 box_size = 0; |
153 | 0 | box_size |= static_cast<u32>(sequence[0] << 24); |
154 | 0 | box_size |= static_cast<u32>(sequence[1] << 16); |
155 | 0 | box_size |= static_cast<u32>(sequence[2] << 8); |
156 | 0 | box_size |= sequence[3]; |
157 | | |
158 | | // 5. If length is less than box-size or if box-size modulo 4 is not equal to 0, return false. |
159 | 0 | if ((length < box_size) || (box_size % 4 != 0)) |
160 | 0 | return false; |
161 | | |
162 | | // 6. If the four bytes from sequence[4] to sequence[7] are not equal to 0x66 0x74 0x79 0x70 ("ftyp"), return false. |
163 | 0 | if (sequence.slice(4, 4) != "\x66\x74\x79\x70"sv.bytes()) |
164 | 0 | return false; |
165 | | |
166 | | // 7. If the three bytes from sequence[8] to sequence[10] are equal to 0x6D 0x70 0x34 ("mp4"), return true. |
167 | 0 | if (sequence.slice(8, 3) == "\x6D\x70\x34"sv.bytes()) |
168 | 0 | return true; |
169 | | |
170 | | // 8. Let bytes-read be 16. |
171 | 0 | u32 bytes_read = 16; |
172 | | |
173 | | // 9. While bytes-read is less than box-size, continuously loop through these steps: |
174 | | // 1. If the three bytes from sequence[bytes-read] to sequence[bytes-read + 2] are equal to 0x6D 0x70 0x34 ("mp4"), return true. |
175 | | // 2. Increment bytes-read by 4. |
176 | 0 | while (bytes_read < box_size) { |
177 | 0 | if (sequence.slice(bytes_read, 3) == "\x6D\x70\x34"sv.bytes()) |
178 | 0 | return true; |
179 | 0 | bytes_read += 4; |
180 | 0 | } |
181 | | |
182 | | // 10. Return false. |
183 | 0 | return false; |
184 | 0 | } |
185 | | |
186 | | // https://mimesniff.spec.whatwg.org/#matching-an-audio-or-video-type-pattern |
187 | | Optional<MimeType> match_an_audio_or_video_type_pattern(ReadonlyBytes input) |
188 | 0 | { |
189 | | // 1. Execute the following steps for each row row in the following table: |
190 | 0 | static Array<BytePatternTableRow, 6> constexpr pattern_table { |
191 | | // The string "FORM" followed by four bytes followed by the string "AIFF", the AIFF signature. |
192 | 0 | BytePatternTableRow { "\x46\x4F\x52\x4D\x00\x00\x00\x00\x41\x49\x46\x46"sv, |
193 | 0 | "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "audio/aiff"sv }, |
194 | | |
195 | | // The string "ID3", the ID3v2-tagged MP3 signature. |
196 | 0 | BytePatternTableRow { "\x49\x44\x33"sv, "\xFF\xFF\xFF"sv, no_ignored_bytes, "audio/mpeg"sv }, |
197 | | |
198 | | // The string "OggS" followed by NUL, the Ogg container signature. |
199 | 0 | BytePatternTableRow { "\x4F\x67\x67\x53\x00"sv, "\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "application/ogg"sv }, |
200 | | |
201 | | // The string "MThd" followed by four bytes representing the number 6 in 32 bits (big-endian), the MIDI signature. |
202 | 0 | BytePatternTableRow { "\x4D\x54\x68\x64\x00\x00\x00\x06"sv, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "audio/midi"sv }, |
203 | | |
204 | | // The string "RIFF" followed by four bytes followed by the string "AVI ", the AVI signature. |
205 | 0 | BytePatternTableRow { "\x52\x49\x46\x46\x00\x00\x00\x00\x41\x56\x49\x20"sv, |
206 | 0 | "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "video/avi"sv }, |
207 | | |
208 | | // The string "RIFF" followed by four bytes followed by the string "WAVE", the WAVE signature. |
209 | 0 | BytePatternTableRow { "\x52\x49\x46\x46\x00\x00\x00\x00\x57\x41\x56\x45"sv, |
210 | 0 | "\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "audio/wave"sv } |
211 | 0 | }; |
212 | |
|
213 | 0 | for (auto const& row : pattern_table) { |
214 | | // 1. Let patternMatched be the result of the pattern matching algorithm given input, the |
215 | | // value in the first column of row, the value in the second column of row, and the |
216 | | // value in the third column of row. |
217 | 0 | auto pattern_matched = pattern_matching_algorithm(input, row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes); |
218 | | |
219 | | // 2. If patternMatched is true, return the value in the fourth column of row. |
220 | 0 | if (pattern_matched) |
221 | 0 | return MimeType::parse(row.mime_type); |
222 | 0 | } |
223 | | |
224 | | // 2. If input matches the signature for MP4, return "video/mp4". |
225 | 0 | if (matches_mp4_signature(input)) |
226 | 0 | return MimeType::create("video"_string, "mp4"_string); |
227 | | |
228 | | // FIXME: 3. If input matches the signature for WebM, return "video/webm". |
229 | | // FIXME: 4. If input matches the signature for MP3 without ID3, return "audio/mpeg". |
230 | | |
231 | | // 5. Return undefined. |
232 | 0 | return OptionalNone {}; |
233 | 0 | } |
234 | | |
235 | | // https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern |
236 | | Optional<MimeType> match_a_font_type_pattern(ReadonlyBytes input) |
237 | 0 | { |
238 | | // 1. Execute the following steps for each row row in the following table: |
239 | 0 | static Array<BytePatternTableRow, 6> constexpr pattern_table { |
240 | | // 34 bytes followed by the string "LP", the Embedded OpenType signature. |
241 | 0 | BytePatternTableRow { |
242 | 0 | .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, |
243 | 0 | .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, |
244 | 0 | .ignored_leading_bytes = no_ignored_bytes, |
245 | 0 | .mime_type = "application/vnd.ms-fontobject"sv, |
246 | 0 | }, |
247 | | |
248 | | // 4 bytes representing the version number 1.0, a TrueType signature. |
249 | 0 | BytePatternTableRow { "\x00\x01\x00\x00"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/ttf"sv }, |
250 | | |
251 | | // The string "OTTO", the OpenType signature. |
252 | 0 | BytePatternTableRow { "\x4F\x54\x54\x4F"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/otf"sv }, |
253 | | |
254 | | // The string "ttcf", the TrueType Collection signature. |
255 | 0 | BytePatternTableRow { "\x74\x74\x63\x66"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/collection"sv }, |
256 | | |
257 | | // The string "wOFF", the Web Open Font Format 1.0 signature. |
258 | 0 | BytePatternTableRow { "\x77\x4F\x46\x46"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/woff"sv }, |
259 | | |
260 | | // The string "wOF2", the Web Open Font Format 2.0 signature. |
261 | 0 | BytePatternTableRow { "\x77\x4F\x46\x32"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "font/woff2"sv }, |
262 | 0 | }; |
263 | |
|
264 | 0 | for (auto const& row : pattern_table) { |
265 | | // 1. Let patternMatched be the result of the pattern matching algorithm given input, the |
266 | | // value in the first column of row, the value in the second column of row, and the |
267 | | // value in the third column of row. |
268 | 0 | auto pattern_matched = pattern_matching_algorithm(input, row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes); |
269 | | |
270 | | // 2. If patternMatched is true, return the value in the fourth column of row. |
271 | 0 | if (pattern_matched) |
272 | 0 | return MimeType::parse(row.mime_type); |
273 | 0 | } |
274 | | |
275 | | // 2. Return undefined. |
276 | 0 | return OptionalNone {}; |
277 | 0 | } |
278 | | |
279 | | // https://mimesniff.spec.whatwg.org/#matching-an-archive-type-pattern |
280 | | Optional<MimeType> match_an_archive_type_pattern(ReadonlyBytes input) |
281 | 0 | { |
282 | | // 1. Execute the following steps for each row row in the following table: |
283 | 0 | static Array<BytePatternTableRow, 3> constexpr pattern_table { |
284 | | // The GZIP archive signature. |
285 | 0 | BytePatternTableRow { "\x1F\x8B\x08"sv, "\xFF\xFF\xFF"sv, no_ignored_bytes, "application/x-gzip"sv }, |
286 | | |
287 | | // The string "PK" followed by ETX EOT, the ZIP archive signature. |
288 | 0 | BytePatternTableRow { "\x50\x4B\x03\x04"sv, "\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "application/zip"sv }, |
289 | | |
290 | | // The string "Rar " followed by SUB BEL NUL, the RAR archive signature. |
291 | 0 | BytePatternTableRow { "\x52\x61\x72\x20\x1A\x07\x00"sv, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "application/x-rar-compressed"sv }, |
292 | 0 | }; |
293 | |
|
294 | 0 | for (auto const& row : pattern_table) { |
295 | | // 1. Let patternMatched be the result of the pattern matching algorithm given input, the |
296 | | // value in the first column of row, the value in the second column of row, and the |
297 | | // value in the third column of row. |
298 | 0 | auto pattern_matched = pattern_matching_algorithm(input, row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes); |
299 | | |
300 | | // 2. If patternMatched is true, return the value in the fourth column of row. |
301 | 0 | if (pattern_matched) |
302 | 0 | return MimeType::parse(row.mime_type); |
303 | 0 | } |
304 | | |
305 | | // 2. Return undefined. |
306 | 0 | return OptionalNone {}; |
307 | 0 | } |
308 | | |
309 | | // https://mimesniff.spec.whatwg.org/#rules-for-identifying-an-unknown-mime-type |
310 | | MimeType rules_for_identifying_an_unknown_mime_type(Resource const& resource, bool sniff_scriptable = false) |
311 | 0 | { |
312 | | // 1. If the sniff-scriptable flag is set, execute the following steps for each row row in the following table: |
313 | 0 | if (sniff_scriptable) { |
314 | 0 | static auto constexpr text_html_mime_type = "text/html"sv; |
315 | | |
316 | | // https://mimesniff.spec.whatwg.org/#whitespace-byte |
317 | | // A whitespace byte (abbreviated 0xWS) is any one of the following bytes: 0x09 (HT), 0x0A (LF), 0x0C (FF), 0x0D (CR), 0x20 (SP). |
318 | 0 | static Array<u8, 5> constexpr ignored_whitespace_bytes { 0x09, 0x0A, 0x0C, 0x0D, 0x20 }; |
319 | 0 | static Array<BytePatternTableRow, 19> constexpr pattern_table { |
320 | | // The case-insensitive string "<!DOCTYPE HTML" followed by a tag-terminating byte. |
321 | 0 | BytePatternTableRow { "\x3C\x21\x44\x4F\x43\x54\x59\x50\x45\x20\x48\x54\x4D\x4C\x00"sv, |
322 | 0 | "\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
323 | | |
324 | | // The case-insensitive string "<HTML" followed by a tag-terminating byte. |
325 | 0 | BytePatternTableRow { "\x3C\x48\x54\x4D\x4C\x00"sv, "\xFF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
326 | | |
327 | | // The case-insensitive string "<HEAD" followed by a tag-terminating byte. |
328 | 0 | BytePatternTableRow { "\x3C\x48\x45\x41\x44\x00"sv, "\xFF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
329 | | |
330 | | // The case-insensitive string "<SCRIPT" followed by a tag-terminating byte. |
331 | 0 | BytePatternTableRow { "\x3C\x53\x43\x52\x49\x50\x54\x00"sv, |
332 | 0 | "\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
333 | | |
334 | | // The case-insensitive string "<IFRAME" followed by a tag-terminating byte. |
335 | 0 | BytePatternTableRow { "\x3C\x49\x46\x52\x41\x4D\x45\x00"sv, |
336 | 0 | "\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
337 | | |
338 | | // The case-insensitive string "<H1" followed by a tag-terminating byte. |
339 | 0 | BytePatternTableRow { "\x3C\x48\x31\x00"sv, "\xFF\xDF\xFF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
340 | | |
341 | | // The case-insensitive string "<DIV" followed by a tag-terminating byte. |
342 | 0 | BytePatternTableRow { "\x3C\x44\x49\x56\x00"sv, "\xFF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
343 | | |
344 | | // The case-insensitive string "<FONT" followed by a tag-terminating byte. |
345 | 0 | BytePatternTableRow { "\x3C\x46\x4F\x4E\x54\x00"sv, "\xFF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
346 | | |
347 | | // The case-insensitive string "<TABLE" followed by a tag-terminating byte. |
348 | 0 | BytePatternTableRow { "\x3C\x54\x41\x42\x4C\x45\x00"sv, "\xFF\xDF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
349 | | |
350 | | // The case-insensitive string "<A" followed by a tag-terminating byte. |
351 | 0 | BytePatternTableRow { "\x3C\x41\x00"sv, "\xFF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
352 | | |
353 | | // The case-insensitive string "<STYLE" followed by a tag-terminating byte. |
354 | 0 | BytePatternTableRow { "\x3C\x53\x54\x59\x4C\x45\x00"sv, |
355 | 0 | "\xFF\xDF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
356 | | |
357 | | // The case-insensitive string "<TITLE" followed by a tag-terminating byte. |
358 | 0 | BytePatternTableRow { "\x3C\x54\x49\x54\x4C\x45\x00"sv, |
359 | 0 | "\xFF\xDF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
360 | | |
361 | | // The case-insensitive string "<B" followed by a tag-terminating byte. |
362 | 0 | BytePatternTableRow { "\x3C\x42\x00"sv, "\xFF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
363 | | |
364 | | // The case-insensitive string "<BODY" followed by a tag-terminating byte. |
365 | 0 | BytePatternTableRow { "\x3C\x42\x4F\x44\x59\x00"sv, "\xFF\xDF\xDF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
366 | | |
367 | | // The case-insensitive string "<BR" followed by a tag-terminating byte. |
368 | 0 | BytePatternTableRow { "\x3C\x42\x52\x00"sv, "\xFF\xDF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
369 | | |
370 | | // The case-insensitive string "<P" followed by a tag-terminating byte. |
371 | 0 | BytePatternTableRow { "\x3C\x50\x00"sv, "\xFF\xDF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
372 | | |
373 | | // The string "<!--" followed by a tag-terminating byte. |
374 | 0 | BytePatternTableRow { "\x3C\x21\x2D\x2D\x00"sv, "\xFF\xFF\xFF\xFF\xFF"sv, ignored_whitespace_bytes, text_html_mime_type, true }, |
375 | | |
376 | | // The string "<?xml". |
377 | 0 | BytePatternTableRow { "\x3C\x3F\x78\x6D\x6C"sv, "\xFF\xFF\xFF\xFF\xFF"sv, ignored_whitespace_bytes, "text/xml"sv }, |
378 | | |
379 | | // The string "%PDF-", the PDF signature. |
380 | 0 | BytePatternTableRow { "\x25\x50\x44\x46\x2D"sv, "\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "application/pdf"sv }, |
381 | 0 | }; |
382 | |
|
383 | 0 | for (auto const& row : pattern_table) { |
384 | | // 1. Let patternMatched be the result of the pattern matching algorithm given resource’s resource header, |
385 | | // the value in the first column of row, the value in the second column of row, and the value in the |
386 | | // third column of row. |
387 | 0 | auto pattern_matched = pattern_matching_algorithm(resource.resource_header(), row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes, row.is_tag_terminated); |
388 | | |
389 | | // 2. If patternMatched is true, return the value in the fourth column of row. |
390 | 0 | if (pattern_matched) { |
391 | 0 | if (auto maybe_type = MimeType::parse(row.mime_type); maybe_type.has_value()) |
392 | 0 | return maybe_type.release_value(); |
393 | 0 | } |
394 | 0 | } |
395 | 0 | } |
396 | | |
397 | | // 2. Execute the following steps for each row row in the following table: |
398 | 0 | static auto constexpr text_plain_mime_type = "text/plain"sv; |
399 | 0 | static Array<BytePatternTableRow, 4> constexpr pattern_table { |
400 | | // The string "%!PS-Adobe-", the PostScript signature. |
401 | 0 | BytePatternTableRow { "\x25\x21\x50\x53\x2D\x41\x64\x6F\x62\x65\x2D"sv, |
402 | 0 | "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"sv, no_ignored_bytes, "application/postscript"sv }, |
403 | | |
404 | | // UTF-16BE BOM |
405 | 0 | BytePatternTableRow { "\xFE\xFF\x00\x00"sv, "\xFF\xFF\x00\x00"sv, no_ignored_bytes, text_plain_mime_type }, |
406 | | |
407 | | // UTF-16LE BOM |
408 | 0 | BytePatternTableRow { "\xFF\xFE\x00\x00"sv, "\xFF\xFF\x00\x00"sv, no_ignored_bytes, text_plain_mime_type }, |
409 | | |
410 | | // UTF-8 BOM |
411 | 0 | BytePatternTableRow { "\xEF\xBB\xBF\x00"sv, "\xFF\xFF\xFF\x00"sv, no_ignored_bytes, text_plain_mime_type }, |
412 | 0 | }; |
413 | |
|
414 | 0 | for (auto const& row : pattern_table) { |
415 | | // 1. Let patternMatched be the result of the pattern matching algorithm given resource’s resource header, |
416 | | // the value in the first column of row, the value in the second column of row, and the value in the |
417 | | // third column of row. |
418 | 0 | auto pattern_matched = pattern_matching_algorithm(resource.resource_header(), row.byte_pattern.bytes(), row.pattern_mask.bytes(), row.ignored_leading_bytes); |
419 | | |
420 | | // 2. If patternMatched is true, return the value in the fourth column of row. |
421 | 0 | if (pattern_matched) { |
422 | 0 | if (auto maybe_type = MimeType::parse(row.mime_type); maybe_type.has_value()) |
423 | 0 | return maybe_type.release_value(); |
424 | 0 | } |
425 | 0 | } |
426 | | |
427 | | // 3. Let matchedType be the result of executing the image type pattern matching algorithm given resource’s resource header. |
428 | 0 | auto matched_type = match_an_image_type_pattern(resource.resource_header()); |
429 | | |
430 | | // 4. If matchedType is not undefined, return matchedType. |
431 | 0 | if (matched_type.has_value()) |
432 | 0 | return matched_type.release_value(); |
433 | | |
434 | | // 5. Set matchedType to the result of executing the audio or video type pattern matching algorithm given resource’s resource header. |
435 | 0 | matched_type = match_an_audio_or_video_type_pattern(resource.resource_header()); |
436 | | |
437 | | // 6. If matchedType is not undefined, return matchedType. |
438 | 0 | if (matched_type.has_value()) |
439 | 0 | return matched_type.release_value(); |
440 | | |
441 | | // 7. Set matchedType to the result of executing the archive type pattern matching algorithm given resource’s resource header. |
442 | 0 | matched_type = match_an_archive_type_pattern(resource.resource_header()); |
443 | | |
444 | | // 8. If matchedType is not undefined, return matchedType. |
445 | 0 | if (matched_type.has_value()) |
446 | 0 | return matched_type.release_value(); |
447 | | |
448 | | // 9. If resource’s resource header contains no binary data bytes, return "text/plain". |
449 | 0 | if (!any_of(resource.resource_header(), is_binary_data_byte)) |
450 | 0 | return MimeType::create("text"_string, "plain"_string); |
451 | | |
452 | | // 10. Return "application/octet-stream". |
453 | 0 | return MimeType::create("application"_string, "octet-stream"_string); |
454 | 0 | } |
455 | | |
456 | | } |
457 | | |
458 | | namespace Web::MimeSniff { |
459 | | |
460 | | Resource Resource::create(ReadonlyBytes data, SniffingConfiguration configuration) |
461 | 0 | { |
462 | | // NOTE: Non-standard but for cases where pattern matching fails, let's fall back to the safest MIME type. |
463 | 0 | auto default_computed_mime_type = MimeType::create("application"_string, "octet-stream"_string); |
464 | 0 | auto resource = Resource { data, configuration.no_sniff, move(default_computed_mime_type) }; |
465 | |
|
466 | 0 | resource.supplied_mime_type_detection_algorithm(configuration.scheme, move(configuration.supplied_type)); |
467 | 0 | resource.context_specific_sniffing_algorithm(configuration.sniffing_context); |
468 | |
|
469 | 0 | return resource; |
470 | 0 | } |
471 | | |
472 | | MimeType Resource::sniff(ReadonlyBytes data, SniffingConfiguration configuration) |
473 | 0 | { |
474 | 0 | auto resource = create(data, move(configuration)); |
475 | 0 | return move(resource.m_computed_mime_type); |
476 | 0 | } |
477 | | |
478 | | Resource::Resource(ReadonlyBytes data, bool no_sniff, MimeType&& default_computed_mime_type) |
479 | 0 | : m_no_sniff(no_sniff) |
480 | 0 | , m_computed_mime_type(move(default_computed_mime_type)) |
481 | 0 | { |
482 | 0 | read_the_resource_header(data); |
483 | 0 | } |
484 | | |
485 | 0 | Resource::~Resource() = default; |
486 | | |
487 | | // https://mimesniff.spec.whatwg.org/#supplied-mime-type-detection-algorithm |
488 | | // NOTE: Parameters are non-standard. |
489 | | void Resource::supplied_mime_type_detection_algorithm(StringView scheme, Optional<MimeType> supplied_type) |
490 | 0 | { |
491 | | // 1. Let supplied-type be null. |
492 | | // 2. If the resource is retrieved via HTTP, execute the following steps: |
493 | | // 1. If one or more Content-Type headers are associated with the resource, execute the following steps: |
494 | | // 1. Set supplied-type to the value of the last Content-Type header associated with the resource. |
495 | | // 2. Set the check-for-apache-bug flag if supplied-type is exactly equal to one of the values in the following table: |
496 | | // NOTE: Non-standard but this algorithm expects the caller to handle step 2.1.1. |
497 | 0 | if (supplied_type.has_value()) { |
498 | 0 | if (Fetch::Infrastructure::is_http_or_https_scheme(scheme)) { |
499 | | // NOTE: The spec expects a space between the semicolon and the start of the charset parameter. However, we will lose this |
500 | | // space because MimeType::parse() ignores any spaces found there. |
501 | 0 | static Array<StringView, 4> constexpr apache_bug_mime_types = { |
502 | 0 | "text/plain"sv, |
503 | 0 | "text/plain;charset=ISO-8859-1"sv, |
504 | 0 | "text/plain;charset=iso-8859-1"sv, |
505 | 0 | "text/plain;charset=UTF-8"sv |
506 | 0 | }; |
507 | |
|
508 | 0 | auto serialized_supplied_type = supplied_type->serialized(); |
509 | 0 | for (auto apache_bug_mime_type : apache_bug_mime_types) { |
510 | 0 | if (serialized_supplied_type == apache_bug_mime_type) { |
511 | 0 | m_check_for_apache_bug_flag = true; |
512 | 0 | break; |
513 | 0 | } |
514 | 0 | } |
515 | 0 | } |
516 | 0 | } |
517 | | |
518 | | // 3. If the resource is retrieved directly from the file system, set supplied-type |
519 | | // to the MIME type provided by the file system. |
520 | | // 4. If the resource is retrieved via another protocol (such as FTP), set |
521 | | // supplied-type to the MIME type as determined by that protocol, if any. |
522 | | // 5. If supplied-type is not a MIME type, the supplied MIME type is undefined. |
523 | | // Abort these steps. |
524 | | // 6. The supplied MIME type is supplied-type. |
525 | | // NOTE: The expectation is for the caller to handle these spec steps. |
526 | 0 | m_supplied_mime_type = supplied_type; |
527 | 0 | } |
528 | | |
529 | | // https://mimesniff.spec.whatwg.org/#read-the-resource-header |
530 | | void Resource::read_the_resource_header(ReadonlyBytes data) |
531 | 0 | { |
532 | | // 1. Let buffer be a byte sequence. |
533 | 0 | ByteBuffer buffer; |
534 | | |
535 | | // 2. Read bytes of the resource into buffer until one of the following conditions is met: |
536 | | // - the end of the resource is reached. |
537 | | // - the number of bytes in buffer is greater than or equal to 1445. |
538 | | // - a reasonable amount of time has elapsed, as determined by the user agent. |
539 | | // FIXME: The spec expects us to be reading from a stream. Reimplement this spec step once |
540 | | // we have greater support for streaming in areas that calls on this API. |
541 | 0 | static size_t constexpr MAX_SNIFF_SIZE = 1445; |
542 | 0 | buffer.append(data.slice(0, min(data.size(), MAX_SNIFF_SIZE))); |
543 | | |
544 | | // 3. The resource header is buffer. |
545 | 0 | m_resource_header = move(buffer); |
546 | 0 | } |
547 | | |
548 | | // https://mimesniff.spec.whatwg.org/#mime-type-sniffing-algorithm |
549 | | void Resource::mime_type_sniffing_algorithm() |
550 | 0 | { |
551 | | // 1. If the supplied MIME type is an XML MIME type or HTML MIME type, the computed MIME type is the supplied MIME type. |
552 | | // Abort these steps. |
553 | 0 | if (m_supplied_mime_type.has_value() && (m_supplied_mime_type->is_xml() || m_supplied_mime_type->is_html())) { |
554 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
555 | 0 | return; |
556 | 0 | } |
557 | | |
558 | | // 2. If the supplied MIME type is undefined or if the supplied MIME type’s essence |
559 | | // is "unknown/unknown", "application/unknown", or "*/*", execute the rules for |
560 | | // identifying an unknown MIME type with the sniff-scriptable flag equal to the |
561 | | // inverse of the no-sniff flag and abort these steps. |
562 | 0 | if (!m_supplied_mime_type.has_value() || m_supplied_mime_type->essence().is_one_of("unknown/unknown", "application/unknown", "*/*")) { |
563 | 0 | m_computed_mime_type = rules_for_identifying_an_unknown_mime_type(*this, !m_no_sniff); |
564 | 0 | return; |
565 | 0 | } |
566 | | |
567 | | // 3. If the no-sniff flag is set, the computed MIME type is the supplied MIME type. |
568 | | // Abort these steps. |
569 | 0 | if (m_no_sniff) { |
570 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
571 | 0 | return; |
572 | 0 | } |
573 | | |
574 | | // 4. If the check-for-apache-bug flag is set, execute the rules for distinguishing |
575 | | // if a resource is text or binary and abort these steps. |
576 | 0 | if (m_check_for_apache_bug_flag) { |
577 | 0 | rules_for_distinguishing_if_a_resource_is_text_or_binary(); |
578 | 0 | return; |
579 | 0 | } |
580 | | |
581 | | // FIXME: 5. If the supplied MIME type is an image MIME type supported by the user agent, let matched-type be |
582 | | // the result of executing the image type pattern matching algorithm with the resource header as |
583 | | // the byte sequence to be matched. |
584 | 0 | Optional<MimeType> matched_type; |
585 | | |
586 | | // 6. If matched-type is not undefined, the computed MIME type is matched-type. |
587 | | // Abort these steps. |
588 | 0 | if (matched_type.has_value()) { |
589 | 0 | m_computed_mime_type = matched_type.release_value(); |
590 | 0 | return; |
591 | 0 | } |
592 | | |
593 | | // FIXME: 7. If the supplied MIME type is an audio or video MIME type supported by the user agent, let matched-type be |
594 | | // the result of executing the audio or video type pattern matching algorithm with the resource header as |
595 | | // the byte sequence to be matched. |
596 | | |
597 | | // 8. If matched-type is not undefined, the computed MIME type is matched-type. |
598 | | // Abort these steps. |
599 | 0 | if (matched_type.has_value()) { |
600 | 0 | m_computed_mime_type = matched_type.release_value(); |
601 | 0 | return; |
602 | 0 | } |
603 | | |
604 | | // 9. The computed MIME type is the supplied MIME type. |
605 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
606 | |
|
607 | 0 | return; |
608 | 0 | } |
609 | | |
610 | | // https://mimesniff.spec.whatwg.org/#sniffing-a-mislabeled-binary-resource |
611 | | void Resource::rules_for_distinguishing_if_a_resource_is_text_or_binary() |
612 | 0 | { |
613 | | // 1. Let length be the number of bytes in the resource header. |
614 | 0 | auto length = m_resource_header.size(); |
615 | | |
616 | | // 2. If length is greater than or equal to 2 and the first 2 bytes of the |
617 | | // resource header are equal to 0xFE 0xFF (UTF-16BE BOM) or 0xFF 0xFE (UTF-16LE BOM), the computed MIME type is "text/plain". |
618 | | // Abort these steps. |
619 | 0 | auto resource_header_span = m_resource_header.span(); |
620 | 0 | auto utf_16_be_bom = "\xFE\xFF"sv.bytes(); |
621 | 0 | auto utf_16_le_bom = "\xFF\xFE"sv.bytes(); |
622 | 0 | if (length >= 2 |
623 | 0 | && (resource_header_span.starts_with(utf_16_be_bom) |
624 | 0 | || resource_header_span.starts_with(utf_16_le_bom))) { |
625 | 0 | m_computed_mime_type = MimeType::create("text"_string, "plain"_string); |
626 | 0 | return; |
627 | 0 | } |
628 | | |
629 | | // 3. If length is greater than or equal to 3 and the first 3 bytes of the resource header are equal to 0xEF 0xBB 0xBF (UTF-8 BOM), |
630 | | // the computed MIME type is "text/plain". |
631 | | // Abort these steps. |
632 | 0 | auto utf_8_bom = "\xEF\xBB\xBF"sv.bytes(); |
633 | 0 | if (length >= 3 && resource_header_span.starts_with(utf_8_bom)) { |
634 | 0 | m_computed_mime_type = MimeType::create("text"_string, "plain"_string); |
635 | 0 | return; |
636 | 0 | } |
637 | | |
638 | | // 4. If the resource header contains no binary data bytes, the computed MIME type is "text/plain". |
639 | | // Abort these steps. |
640 | 0 | if (!any_of(resource_header(), is_binary_data_byte)) { |
641 | 0 | m_computed_mime_type = MimeType::create("text"_string, "plain"_string); |
642 | 0 | return; |
643 | 0 | } |
644 | | |
645 | | // 5. The computed MIME type is "application/octet-stream". |
646 | | // NOTE: This is the default MIME type of the computed MIME type. |
647 | 0 | return; |
648 | 0 | } |
649 | | |
650 | | // https://mimesniff.spec.whatwg.org/#context-specific-sniffing-algorithm |
651 | | void Resource::context_specific_sniffing_algorithm(SniffingContext sniffing_context) |
652 | 0 | { |
653 | | // A context-specific sniffing algorithm determines the computed MIME type of a resource only if |
654 | | // the resource is a MIME type relevant to a particular context. |
655 | 0 | if (sniffing_context == SniffingContext::None || sniffing_context == SniffingContext::Browsing) { |
656 | | // https://mimesniff.spec.whatwg.org/#sniffing-in-a-browsing-context |
657 | | // Use the MIME type sniffing algorithm. |
658 | 0 | return mime_type_sniffing_algorithm(); |
659 | 0 | } |
660 | | |
661 | | // NOTE: Non-standard but if the client expects us to not sniff, we shouldn't be doing any |
662 | | // context-specific sniffing if we don't have to. |
663 | 0 | if (m_no_sniff && m_supplied_mime_type.has_value()) { |
664 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
665 | 0 | return; |
666 | 0 | } |
667 | | |
668 | 0 | if (sniffing_context == SniffingContext::Image) |
669 | 0 | return rules_for_sniffing_images_specifically(); |
670 | 0 | if (sniffing_context == SniffingContext::AudioOrVideo) |
671 | 0 | return rules_for_sniffing_audio_or_video_specifically(); |
672 | 0 | if (sniffing_context == SniffingContext::Font) |
673 | 0 | return rules_for_sniffing_fonts_specifically(); |
674 | 0 | if (sniffing_context == SniffingContext::TextOrBinary) |
675 | 0 | return rules_for_distinguishing_if_a_resource_is_text_or_binary(); |
676 | | |
677 | 0 | return; |
678 | 0 | } |
679 | | |
680 | | // https://mimesniff.spec.whatwg.org/#sniffing-in-an-image-context |
681 | | void Resource::rules_for_sniffing_images_specifically() |
682 | 0 | { |
683 | | // 1. If the supplied MIME type is an XML MIME type, the computed MIME type is the supplied MIME type. |
684 | | // Abort these steps. |
685 | | // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. |
686 | 0 | if (m_supplied_mime_type.has_value() && m_supplied_mime_type->is_xml()) { |
687 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
688 | 0 | return; |
689 | 0 | } |
690 | | |
691 | | // 2. Let image-type-matched be the result of executing the image type pattern matching algorithm with |
692 | | // the resource header as the byte sequence to be matched. |
693 | 0 | auto image_type_matched = match_an_image_type_pattern(resource_header()); |
694 | | |
695 | | // 3. If image-type-matched is not undefined, the computed MIME type is image-type-matched. |
696 | | // Abort these steps. |
697 | 0 | if (image_type_matched.has_value()) { |
698 | 0 | m_computed_mime_type = image_type_matched.release_value(); |
699 | 0 | return; |
700 | 0 | } |
701 | | |
702 | | // 4. The computed MIME type is the supplied MIME type. |
703 | | // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. |
704 | 0 | if (m_supplied_mime_type.has_value()) { |
705 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
706 | 0 | } |
707 | | |
708 | | // NOTE: Non-standard but if the supplied mime type is undefined, we use computed mime type's default value. |
709 | 0 | return; |
710 | 0 | } |
711 | | |
712 | | // https://mimesniff.spec.whatwg.org/#sniffing-in-an-audio-or-video-context |
713 | | void Resource::rules_for_sniffing_audio_or_video_specifically() |
714 | 0 | { |
715 | | // 1. If the supplied MIME type is an XML MIME type, the computed MIME type is the supplied MIME type. |
716 | | // Abort these steps. |
717 | | // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. |
718 | 0 | if (m_supplied_mime_type.has_value() && m_supplied_mime_type->is_xml()) { |
719 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
720 | 0 | return; |
721 | 0 | } |
722 | | |
723 | | // 2. Let audio-or-video-type-matched be the result of executing the audio or video type pattern matching |
724 | | // algorithm with the resource header as the byte sequence to be matched. |
725 | 0 | auto audio_or_video_type_matched = match_an_audio_or_video_type_pattern(resource_header()); |
726 | | |
727 | | // 3. If audio-or-video-type-matched is not undefined, the computed MIME type is audio-or-video-type-matched. |
728 | | // Abort these steps. |
729 | 0 | if (audio_or_video_type_matched.has_value()) { |
730 | 0 | m_computed_mime_type = audio_or_video_type_matched.release_value(); |
731 | 0 | return; |
732 | 0 | } |
733 | | |
734 | | // 4. The computed MIME type is the supplied MIME type. |
735 | | // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. |
736 | 0 | if (m_supplied_mime_type.has_value()) { |
737 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
738 | 0 | } |
739 | | |
740 | | // NOTE: Non-standard but if the supplied mime type is undefined, we use computed mime type's default value. |
741 | 0 | return; |
742 | 0 | } |
743 | | |
744 | | // https://mimesniff.spec.whatwg.org/#sniffing-in-a-font-context |
745 | | void Resource::rules_for_sniffing_fonts_specifically() |
746 | 0 | { |
747 | | // 1. If the supplied MIME type is an XML MIME type, the computed MIME type is the supplied MIME type. |
748 | | // Abort these steps. |
749 | | // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. |
750 | 0 | if (m_supplied_mime_type.has_value() && m_supplied_mime_type->is_xml()) { |
751 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
752 | 0 | return; |
753 | 0 | } |
754 | | |
755 | | // 2. Let font-type-matched be the result of executing the font type pattern matching algorithm with the |
756 | | // resource header as the byte sequence to be matched. |
757 | 0 | auto font_type_matched = match_a_font_type_pattern(resource_header()); |
758 | | |
759 | | // 3. If font-type-matched is not undefined, the computed MIME type is font-type-matched. |
760 | | // Abort these steps. |
761 | 0 | if (font_type_matched.has_value()) { |
762 | 0 | m_computed_mime_type = font_type_matched.release_value(); |
763 | 0 | return; |
764 | 0 | } |
765 | | |
766 | | // 4. The computed MIME type is the supplied MIME type. |
767 | | // NOTE: Non-standard but due to the mime type detection algorithm we need this sanity check. |
768 | 0 | if (m_supplied_mime_type.has_value()) { |
769 | 0 | m_computed_mime_type = m_supplied_mime_type.value(); |
770 | 0 | } |
771 | | |
772 | | // NOTE: Non-standard but if the supplied mime type is undefined, we use computed mime type's default value. |
773 | 0 | return; |
774 | 0 | } |
775 | | |
776 | | } |