Improve use of NeverDestroyed
[WebKit-https.git] / Source / WebCore / html / parser / HTMLParserIdioms.cpp
1 /*
2  * Copyright (C) 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1.  Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  * 2.  Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16  * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
17  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
20  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "config.h"
26 #include "HTMLParserIdioms.h"
27
28 #include "Decimal.h"
29 #include "QualifiedName.h"
30 #include "URL.h"
31 #include <limits>
32 #include <wtf/MathExtras.h>
33 #include <wtf/dtoa.h>
34
35 namespace WebCore {
36
37 template <typename CharType>
38 static String stripLeadingAndTrailingHTMLSpaces(String string, CharType characters, unsigned length)
39 {
40     unsigned numLeadingSpaces = 0;
41     unsigned numTrailingSpaces = 0;
42
43     for (; numLeadingSpaces < length; ++numLeadingSpaces) {
44         if (isNotHTMLSpace(characters[numLeadingSpaces]))
45             break;
46     }
47
48     if (numLeadingSpaces == length)
49         return string.isNull() ? string : emptyAtom().string();
50
51     for (; numTrailingSpaces < length; ++numTrailingSpaces) {
52         if (isNotHTMLSpace(characters[length - numTrailingSpaces - 1]))
53             break;
54     }
55
56     ASSERT(numLeadingSpaces + numTrailingSpaces < length);
57
58     if (!(numLeadingSpaces | numTrailingSpaces))
59         return string;
60
61     return string.substring(numLeadingSpaces, length - (numLeadingSpaces + numTrailingSpaces));
62 }
63
64 String stripLeadingAndTrailingHTMLSpaces(const String& string)
65 {
66     unsigned length = string.length();
67
68     if (!length)
69         return string.isNull() ? string : emptyAtom().string();
70
71     if (string.is8Bit())
72         return stripLeadingAndTrailingHTMLSpaces(string, string.characters8(), length);
73
74     return stripLeadingAndTrailingHTMLSpaces(string, string.characters16(), length);
75 }
76
77 String serializeForNumberType(const Decimal& number)
78 {
79     if (number.isZero()) {
80         // Decimal::toString appends exponent, e.g. "0e-18"
81         return number.isNegative() ? "-0" : "0";
82     }
83     return number.toString();
84 }
85
86 String serializeForNumberType(double number)
87 {
88     // According to HTML5, "the best representation of the number n as a floating
89     // point number" is a string produced by applying ToString() to n.
90     return String::numberToStringECMAScript(number);
91 }
92
93 Decimal parseToDecimalForNumberType(const String& string, const Decimal& fallbackValue)
94 {
95     // See HTML5 2.5.4.3 `Real numbers.' and parseToDoubleForNumberType
96
97     // String::toDouble() accepts leading + and whitespace characters, which are not valid here.
98     const UChar firstCharacter = string[0];
99     if (firstCharacter != '-' && firstCharacter != '.' && !isASCIIDigit(firstCharacter))
100         return fallbackValue;
101
102     const Decimal value = Decimal::fromString(string);
103     if (!value.isFinite())
104         return fallbackValue;
105
106     // Numbers are considered finite IEEE 754 single-precision floating point values.
107     // See HTML5 2.5.4.3 `Real numbers.'
108     // FIXME: We should use numeric_limits<double>::max for number input type.
109     const Decimal floatMax = Decimal::fromDouble(std::numeric_limits<float>::max());
110     if (value < -floatMax || value > floatMax)
111         return fallbackValue;
112
113     // We return +0 for -0 case.
114     return value.isZero() ? Decimal(0) : value;
115 }
116
117 Decimal parseToDecimalForNumberType(const String& string)
118 {
119     return parseToDecimalForNumberType(string, Decimal::nan());
120 }
121
122 double parseToDoubleForNumberType(const String& string, double fallbackValue)
123 {
124     // See HTML5 2.5.4.3 `Real numbers.'
125
126     // String::toDouble() accepts leading + and whitespace characters, which are not valid here.
127     UChar firstCharacter = string[0];
128     if (firstCharacter != '-' && firstCharacter != '.' && !isASCIIDigit(firstCharacter))
129         return fallbackValue;
130
131     bool valid = false;
132     double value = string.toDouble(&valid);
133     if (!valid)
134         return fallbackValue;
135
136     // NaN and infinity are considered valid by String::toDouble, but not valid here.
137     if (!std::isfinite(value))
138         return fallbackValue;
139
140     // Numbers are considered finite IEEE 754 single-precision floating point values.
141     // See HTML5 2.5.4.3 `Real numbers.'
142     if (-std::numeric_limits<float>::max() > value || value > std::numeric_limits<float>::max())
143         return fallbackValue;
144
145     // The following expression converts -0 to +0.
146     return value ? value : 0;
147 }
148
149 double parseToDoubleForNumberType(const String& string)
150 {
151     return parseToDoubleForNumberType(string, std::numeric_limits<double>::quiet_NaN());
152 }
153
154 template <typename CharacterType>
155 static Expected<int, HTMLIntegerParsingError> parseHTMLIntegerInternal(const CharacterType* position, const CharacterType* end)
156 {
157     while (position < end && isHTMLSpace(*position))
158         ++position;
159
160     if (position == end)
161         return makeUnexpected(HTMLIntegerParsingError::Other);
162
163     bool isNegative = false;
164     if (*position == '-') {
165         isNegative = true;
166         ++position;
167     } else if (*position == '+')
168         ++position;
169
170     if (position == end || !isASCIIDigit(*position))
171         return makeUnexpected(HTMLIntegerParsingError::Other);
172
173     constexpr int intMax = std::numeric_limits<int>::max();
174     constexpr int base = 10;
175     constexpr int maxMultiplier = intMax / base;
176
177     unsigned result = 0;
178     do {
179         int digitValue = *position - '0';
180
181         if (result > maxMultiplier || (result == maxMultiplier && digitValue > (intMax % base) + isNegative))
182             return makeUnexpected(isNegative ? HTMLIntegerParsingError::NegativeOverflow : HTMLIntegerParsingError::PositiveOverflow);
183
184         result = base * result + digitValue;
185         ++position;
186     } while (position < end && isASCIIDigit(*position));
187
188     return isNegative ? -result : result;
189 }
190
191 // https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-integers
192 Expected<int, HTMLIntegerParsingError> parseHTMLInteger(StringView input)
193 {
194     unsigned length = input.length();
195     if (!length)
196         return makeUnexpected(HTMLIntegerParsingError::Other);
197
198     if (LIKELY(input.is8Bit())) {
199         auto* start = input.characters8();
200         return parseHTMLIntegerInternal(start, start + length);
201     }
202
203     auto* start = input.characters16();
204     return parseHTMLIntegerInternal(start, start + length);
205 }
206
207 // https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-non-negative-integers
208 Expected<unsigned, HTMLIntegerParsingError> parseHTMLNonNegativeInteger(StringView input)
209 {
210     auto optionalSignedResult = parseHTMLInteger(input);
211     if (!optionalSignedResult)
212         return optionalSignedResult.getUnexpected();
213
214     if (optionalSignedResult.value() < 0)
215         return makeUnexpected(HTMLIntegerParsingError::NegativeOverflow);
216
217     return static_cast<unsigned>(optionalSignedResult.value());
218 }
219
220 template <typename CharacterType>
221 static std::optional<int> parseValidHTMLNonNegativeIntegerInternal(const CharacterType* position, const CharacterType* end)
222 {
223     // A string is a valid non-negative integer if it consists of one or more ASCII digits.
224     for (auto* c = position; c < end; ++c) {
225         if (!isASCIIDigit(*c))
226             return std::nullopt;
227     }
228
229     auto optionalSignedValue = parseHTMLIntegerInternal(position, end);
230     if (!optionalSignedValue || optionalSignedValue.value() < 0)
231         return std::nullopt;
232
233     return optionalSignedValue.value();
234 }
235
236 // https://html.spec.whatwg.org/#valid-non-negative-integer
237 std::optional<int> parseValidHTMLNonNegativeInteger(StringView input)
238 {
239     if (input.isEmpty())
240         return std::nullopt;
241
242     if (LIKELY(input.is8Bit())) {
243         auto* start = input.characters8();
244         return parseValidHTMLNonNegativeIntegerInternal(start, start + input.length());
245     }
246
247     auto* start = input.characters16();
248     return parseValidHTMLNonNegativeIntegerInternal(start, start + input.length());
249 }
250
251 template <typename CharacterType>
252 static std::optional<double> parseValidHTMLFloatingPointNumberInternal(const CharacterType* position, size_t length)
253 {
254     ASSERT(length > 0);
255
256     // parseDouble() allows the string to start with a '+' or to end with a '.' but those
257     // are not valid floating point numbers as per HTML.
258     if (*position == '+' || *(position + length - 1) == '.')
259         return std::nullopt;
260
261     size_t parsedLength = 0;
262     double number = parseDouble(position, length, parsedLength);
263     return parsedLength == length && std::isfinite(number) ? number : std::optional<double>();
264 }
265
266 // https://html.spec.whatwg.org/#valid-floating-point-number
267 std::optional<double> parseValidHTMLFloatingPointNumber(StringView input)
268 {
269     if (input.isEmpty())
270         return std::nullopt;
271
272     if (LIKELY(input.is8Bit())) {
273         auto* start = input.characters8();
274         return parseValidHTMLFloatingPointNumberInternal(start, input.length());
275     }
276
277     auto* start = input.characters16();
278     return parseValidHTMLFloatingPointNumberInternal(start, input.length());
279 }
280
281 static inline bool isHTMLSpaceOrDelimiter(UChar character)
282 {
283     return isHTMLSpace(character) || character == ',' || character == ';';
284 }
285
286 static inline bool isNumberStart(UChar character)
287 {
288     return isASCIIDigit(character) || character == '.' || character == '-';
289 }
290
291 // https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-floating-point-number-values
292 template <typename CharacterType>
293 static Vector<double> parseHTMLListOfOfFloatingPointNumberValuesInternal(const CharacterType* position, const CharacterType* end)
294 {
295     Vector<double> numbers;
296
297     // This skips past any leading delimiters.
298     while (position < end && isHTMLSpaceOrDelimiter(*position))
299         ++position;
300
301     while (position < end) {
302         // This skips past leading garbage.
303         while (position < end && !(isHTMLSpaceOrDelimiter(*position) || isNumberStart(*position)))
304             ++position;
305
306         const CharacterType* numberStart = position;
307         while (position < end && !isHTMLSpaceOrDelimiter(*position))
308             ++position;
309
310         size_t parsedLength = 0;
311         double number = parseDouble(numberStart, position - numberStart, parsedLength);
312         numbers.append(parsedLength > 0 && std::isfinite(number) ? number : 0);
313
314         // This skips past the delimiter.
315         while (position < end && isHTMLSpaceOrDelimiter(*position))
316             ++position;
317     }
318
319     return numbers;
320 }
321
322 Vector<double> parseHTMLListOfOfFloatingPointNumberValues(StringView input)
323 {
324     if (LIKELY(input.is8Bit())) {
325         auto* start = input.characters8();
326         return parseHTMLListOfOfFloatingPointNumberValuesInternal(start, start + input.length());
327     }
328
329     auto* start = input.characters16();
330     return parseHTMLListOfOfFloatingPointNumberValuesInternal(start, start + input.length());
331 }
332
333 static bool threadSafeEqual(const StringImpl& a, const StringImpl& b)
334 {
335     if (&a == &b)
336         return true;
337     if (a.hash() != b.hash())
338         return false;
339     return equal(a, b);
340 }
341
342 bool threadSafeMatch(const QualifiedName& a, const QualifiedName& b)
343 {
344     return threadSafeEqual(*a.localName().impl(), *b.localName().impl());
345 }
346
347 String parseCORSSettingsAttribute(const AtomicString& value)
348 {
349     if (value.isNull())
350         return String();
351     if (equalIgnoringASCIICase(value, "use-credentials"))
352         return ASCIILiteral("use-credentials");
353     return ASCIILiteral("anonymous");
354 }
355
356 // https://html.spec.whatwg.org/multipage/semantics.html#attr-meta-http-equiv-refresh
357 template <typename CharacterType>
358 static bool parseHTTPRefreshInternal(const CharacterType* position, const CharacterType* end, double& parsedDelay, String& parsedURL)
359 {
360     while (position < end && isHTMLSpace(*position))
361         ++position;
362
363     const CharacterType* numberStart = position;
364     while (position < end && isASCIIDigit(*position))
365         ++position;
366
367     auto optionalNumber = parseHTMLNonNegativeInteger(StringView(numberStart, position - numberStart));
368     if (!optionalNumber)
369         return false;
370
371     while (position < end && (isASCIIDigit(*position) || *position == '.'))
372         ++position;
373
374     if (position == end) {
375         parsedDelay = optionalNumber.value();
376         return true;
377     }
378
379     if (*position != ';' && *position != ',' && !isHTMLSpace(*position))
380         return false;
381
382     parsedDelay = optionalNumber.value();
383
384     while (position < end && isHTMLSpace(*position))
385         ++position;
386
387     if (position < end && (*position == ';' || *position == ','))
388         ++position;
389
390     while (position < end && isHTMLSpace(*position))
391         ++position;
392
393     if (position == end)
394         return true;
395
396     if (*position == 'U' || *position == 'u') {
397         StringView url(position, end - position);
398
399         ++position;
400
401         if (position < end && (*position == 'R' || *position == 'r'))
402             ++position;
403         else {
404             parsedURL = url.toString();
405             return true;
406         }
407
408         if (position < end && (*position == 'L' || *position == 'l'))
409             ++position;
410         else {
411             parsedURL = url.toString();
412             return true;
413         }
414
415         while (position < end && isHTMLSpace(*position))
416             ++position;
417
418         if (position < end && *position == '=')
419             ++position;
420         else {
421             parsedURL = url.toString();
422             return true;
423         }
424
425         while (position < end && isHTMLSpace(*position))
426             ++position;
427     }
428
429     CharacterType quote;
430     if (position < end && (*position == '\'' || *position == '"')) {
431         quote = *position;
432         ++position;
433     } else
434         quote = '\0';
435
436     StringView url(position, end - position);
437
438     if (quote != '\0') {
439         size_t index = url.find(quote);
440         if (index != notFound)
441             url = url.substring(0, index);
442     }
443
444     parsedURL = url.toString();
445     return true;
446 }
447
448 bool parseMetaHTTPEquivRefresh(const StringView& input, double& delay, String& url)
449 {
450     if (LIKELY(input.is8Bit())) {
451         auto* start = input.characters8();
452         return parseHTTPRefreshInternal(start, start + input.length(), delay, url);
453     }
454
455     auto* start = input.characters16();
456     return parseHTTPRefreshInternal(start, start + input.length(), delay, url);
457 }
458
459 // https://html.spec.whatwg.org/#rules-for-parsing-a-hash-name-reference
460 AtomicString parseHTMLHashNameReference(StringView usemap)
461 {
462     size_t numberSignIndex = usemap.find('#');
463     if (numberSignIndex == notFound)
464         return nullAtom();
465     return usemap.substring(numberSignIndex + 1).toAtomicString();
466 }
467
468 }