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