1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
|
/*
* Copyright (c) 2021, Max Wipfli <mail@maxwipfli.ch>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/CharacterTypes.h>
#include <AK/GenericLexer.h>
#include <AK/StringView.h>
#include <AK/Utf8View.h>
#include <LibTextCodec/Decoder.h>
#include <LibWeb/DOM/Attr.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/Parser/HTMLEncodingDetection.h>
#include <LibWeb/Infra/CharacterTypes.h>
#include <ctype.h>
namespace Web::HTML {
bool prescan_should_abort(ByteBuffer const& input, size_t const& position)
{
return position >= input.size() || position >= 1024;
}
bool prescan_is_whitespace_or_slash(u8 const& byte)
{
return byte == '\t' || byte == '\n' || byte == '\f' || byte == '\r' || byte == ' ' || byte == '/';
}
bool prescan_skip_whitespace_and_slashes(ByteBuffer const& input, size_t& position)
{
while (!prescan_should_abort(input, position) && (input[position] == '\t' || input[position] == '\n' || input[position] == '\f' || input[position] == '\r' || input[position] == ' ' || input[position] == '/'))
++position;
return !prescan_should_abort(input, position);
}
// https://html.spec.whatwg.org/multipage/urls-and-fetching.html#algorithm-for-extracting-a-character-encoding-from-a-meta-element
Optional<StringView> extract_character_encoding_from_meta_element(DeprecatedString const& string)
{
// Checking for "charset" is case insensitive, as is getting an encoding.
// Therefore, stick to lowercase from the start for simplicity.
auto lowercase_string = string.to_lowercase();
GenericLexer lexer(lowercase_string);
for (;;) {
auto charset_index = lexer.remaining().find("charset"sv);
if (!charset_index.has_value())
return {};
// 7 is the length of "charset".
lexer.ignore(charset_index.value() + 7);
lexer.ignore_while([](char c) {
return Infra::is_ascii_whitespace(c);
});
if (lexer.peek() != '=')
continue;
break;
}
// Ignore the '='.
lexer.ignore();
lexer.ignore_while([](char c) {
return Infra::is_ascii_whitespace(c);
});
if (lexer.is_eof())
return {};
if (lexer.consume_specific('"')) {
auto matching_double_quote = lexer.remaining().find('"');
if (!matching_double_quote.has_value())
return {};
auto encoding = lexer.remaining().substring_view(0, matching_double_quote.value());
return TextCodec::get_standardized_encoding(encoding);
}
if (lexer.consume_specific('\'')) {
auto matching_single_quote = lexer.remaining().find('\'');
if (!matching_single_quote.has_value())
return {};
auto encoding = lexer.remaining().substring_view(0, matching_single_quote.value());
return TextCodec::get_standardized_encoding(encoding);
}
auto encoding = lexer.consume_until([](char c) {
return Infra::is_ascii_whitespace(c) || c == ';';
});
return TextCodec::get_standardized_encoding(encoding);
}
JS::GCPtr<DOM::Attr> prescan_get_attribute(DOM::Document& document, ByteBuffer const& input, size_t& position)
{
if (!prescan_skip_whitespace_and_slashes(input, position))
return {};
if (input[position] == '>')
return {};
StringBuilder attribute_name;
while (true) {
if (input[position] == '=' && !attribute_name.is_empty()) {
++position;
goto value;
} else if (input[position] == '\t' || input[position] == '\n' || input[position] == '\f' || input[position] == '\r' || input[position] == ' ')
goto spaces;
else if (input[position] == '/' || input[position] == '>')
return *DOM::Attr::create(document, attribute_name.to_string(), "");
else
attribute_name.append_as_lowercase(input[position]);
++position;
if (prescan_should_abort(input, position))
return {};
}
spaces:
if (!prescan_skip_whitespace_and_slashes(input, position))
return {};
if (input[position] != '=')
return DOM::Attr::create(document, attribute_name.to_string(), "");
++position;
value:
if (!prescan_skip_whitespace_and_slashes(input, position))
return {};
StringBuilder attribute_value;
if (input[position] == '"' || input[position] == '\'') {
u8 quote_character = input[position];
++position;
for (; !prescan_should_abort(input, position); ++position) {
if (input[position] == quote_character)
return DOM::Attr::create(document, attribute_name.to_string(), attribute_value.to_string());
else
attribute_value.append_as_lowercase(input[position]);
}
return {};
} else if (input[position] == '>')
return DOM::Attr::create(document, attribute_name.to_string(), "");
else
attribute_value.append_as_lowercase(input[position]);
++position;
if (prescan_should_abort(input, position))
return {};
for (; !prescan_should_abort(input, position); ++position) {
if (input[position] == '\t' || input[position] == '\n' || input[position] == '\f' || input[position] == '\r' || input[position] == ' ' || input[position] == '>')
return DOM::Attr::create(document, attribute_name.to_string(), attribute_value.to_string());
else
attribute_value.append_as_lowercase(input[position]);
}
return {};
}
// https://html.spec.whatwg.org/multipage/parsing.html#prescan-a-byte-stream-to-determine-its-encoding
Optional<DeprecatedString> run_prescan_byte_stream_algorithm(DOM::Document& document, ByteBuffer const& input)
{
// https://html.spec.whatwg.org/multipage/parsing.html#prescan-a-byte-stream-to-determine-its-encoding
// Detects '<?x'
if (!prescan_should_abort(input, 6)) {
if (input[0] == 0x3C && input[1] == 0x00 && input[2] == 0x3F && input[3] == 0x00 && input[4] == 0x78 && input[5] == 0x00)
return "utf-16le";
if (input[0] == 0x00 && input[1] == 0x3C && input[2] == 0x00 && input[4] == 0x3F && input[5] == 0x00 && input[6] == 0x78)
return "utf-16be";
}
for (size_t position = 0; !prescan_should_abort(input, position); ++position) {
if (!prescan_should_abort(input, position + 5) && input[position] == '<' && input[position + 1] == '!'
&& input[position + 2] == '-' && input[position + 3] == '-') {
position += 2;
for (; !prescan_should_abort(input, position + 3); ++position) {
if (input[position] == '-' && input[position + 1] == '-' && input[position + 2] == '>') {
position += 2;
break;
}
}
} else if (!prescan_should_abort(input, position + 6)
&& input[position] == '<'
&& (input[position + 1] == 'M' || input[position + 1] == 'm')
&& (input[position + 2] == 'E' || input[position + 2] == 'e')
&& (input[position + 3] == 'T' || input[position + 3] == 't')
&& (input[position + 4] == 'A' || input[position + 4] == 'a')
&& prescan_is_whitespace_or_slash(input[position + 5])) {
position += 6;
Vector<DeprecatedString> attribute_list {};
bool got_pragma = false;
Optional<bool> need_pragma {};
Optional<DeprecatedString> charset {};
while (true) {
auto attribute = prescan_get_attribute(document, input, position);
if (!attribute)
break;
if (attribute_list.contains_slow(attribute->name()))
continue;
auto& attribute_name = attribute->name();
attribute_list.append(attribute->name());
if (attribute_name == "http-equiv") {
got_pragma = attribute->value() == "content-type";
} else if (attribute_name == "content") {
auto encoding = extract_character_encoding_from_meta_element(attribute->value());
if (encoding.has_value() && !charset.has_value()) {
charset = encoding.value();
need_pragma = true;
}
} else if (attribute_name == "charset") {
auto maybe_charset = TextCodec::get_standardized_encoding(attribute->value());
if (maybe_charset.has_value()) {
charset = Optional<DeprecatedString> { maybe_charset };
need_pragma = { false };
}
}
}
if (!need_pragma.has_value() || (need_pragma.value() && !got_pragma) || !charset.has_value())
continue;
if (charset.value() == "UTF-16BE/LE")
return "UTF-8";
else if (charset.value() == "x-user-defined")
return "windows-1252";
else
return charset.value();
} else if (!prescan_should_abort(input, position + 3) && input[position] == '<'
&& ((input[position + 1] == '/' && isalpha(input[position + 2])) || isalpha(input[position + 1]))) {
position += 2;
prescan_skip_whitespace_and_slashes(input, position);
while (prescan_get_attribute(document, input, position)) { };
} else if (!prescan_should_abort(input, position + 1) && input[position] == '<' && (input[position + 1] == '!' || input[position + 1] == '/' || input[position + 1] == '?')) {
position += 2;
while (input[position] != '>') {
++position;
if (prescan_should_abort(input, position))
return {};
}
} else {
// Do nothing.
}
}
return {};
}
// https://html.spec.whatwg.org/multipage/parsing.html#determining-the-character-encoding
DeprecatedString run_encoding_sniffing_algorithm(DOM::Document& document, ByteBuffer const& input)
{
if (input.size() >= 2) {
if (input[0] == 0xFE && input[1] == 0xFF) {
return "UTF-16BE";
} else if (input[0] == 0xFF && input[1] == 0xFE) {
return "UTF-16LE";
} else if (input.size() >= 3 && input[0] == 0xEF && input[1] == 0xBB && input[2] == 0xBF) {
return "UTF-8";
}
}
// FIXME: If the user has explicitly instructed the user agent to override the document's character
// encoding with a specific encoding.
// FIXME: The user agent may wait for more bytes of the resource to be available, either in this step or
// at any later step in this algorithm.
// FIXME: If the transport layer specifies a character encoding, and it is supported.
auto optional_encoding = run_prescan_byte_stream_algorithm(document, input);
if (optional_encoding.has_value()) {
return optional_encoding.value();
}
// FIXME: If the HTML parser for which this algorithm is being run is associated with a Document whose browsing context
// is non-null and a child browsing context.
// FIXME: If the user agent has information on the likely encoding for this page, e.g. based on the encoding of the page
// when it was last visited.
if (!Utf8View(StringView(input)).validate()) {
// FIXME: As soon as Locale is supported, this should sometimes return a different encoding based on the locale.
return "windows-1252";
}
// NOTE: This is the authoritative place to actually decide on using the default encoding as per the HTML specification.
// "Otherwise, return an implementation-defined or user-specified default character encoding, [...]."
return "UTF-8";
}
}
|