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