Commit 54241866 authored by Kent Tamura's avatar Kent Tamura Committed by Commit Bot

Parse clamped unsigned integer attribute values correctly.

https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#clamped-to-the-range

We had a bug that an attribute value greater than 2^32-1 was handled as a parse
error, instead of range overflow. We need to clamp such values to a maximum
value defined for a attribute.

Implementation:
The main change is to introduce blink::ParseHTMLClampedNonNegativeInteger(), and
use it in HTMLTableCellElement and HTMLTableColElement.

This CL introduces WTF::NumberParsingState in order to pass "fail by overflow"
information from platform/wtf/StringToNumber code.

Bug: 745376
Change-Id: Ie57a0538816f0f508324573cdcda6d96ad51afb2
Reviewed-on: https://chromium-review.googlesource.com/577428Reviewed-by: default avatarTakayoshi Kochi <kochi@chromium.org>
Commit-Queue: Kent Tamura <tkent@chromium.org>
Cr-Commit-Position: refs/heads/master@{#488143}
parent c5ab5e8c
...@@ -30,9 +30,7 @@ PASS colgroup.className (<colgroup class>): 32 tests ...@@ -30,9 +30,7 @@ PASS colgroup.className (<colgroup class>): 32 tests
PASS colgroup.hidden: 33 tests PASS colgroup.hidden: 33 tests
PASS colgroup.accessKey: 32 tests PASS colgroup.accessKey: 32 tests
PASS colgroup.tabIndex: 24 tests PASS colgroup.tabIndex: 24 tests
PASS colgroup.span: 11 tests PASS colgroup.span: 65 tests
FAIL colgroup.span: setAttribute() to 4294967296 assert_equals: IDL get expected 1000 but got 1
PASS colgroup.span: 53 tests
PASS colgroup.align: 32 tests PASS colgroup.align: 32 tests
PASS colgroup.ch (<colgroup char>): 32 tests PASS colgroup.ch (<colgroup char>): 32 tests
PASS colgroup.chOff (<colgroup charoff>): 32 tests PASS colgroup.chOff (<colgroup charoff>): 32 tests
...@@ -45,9 +43,7 @@ PASS col.className (<col class>): 32 tests ...@@ -45,9 +43,7 @@ PASS col.className (<col class>): 32 tests
PASS col.hidden: 33 tests PASS col.hidden: 33 tests
PASS col.accessKey: 32 tests PASS col.accessKey: 32 tests
PASS col.tabIndex: 24 tests PASS col.tabIndex: 24 tests
PASS col.span: 11 tests PASS col.span: 65 tests
FAIL col.span: setAttribute() to 4294967296 assert_equals: IDL get expected 1000 but got 1
PASS col.span: 53 tests
PASS col.align: 32 tests PASS col.align: 32 tests
PASS col.ch (<col char>): 32 tests PASS col.ch (<col char>): 32 tests
PASS col.chOff (<col charoff>): 32 tests PASS col.chOff (<col charoff>): 32 tests
...@@ -105,14 +101,10 @@ PASS td.className (<td class>): 32 tests ...@@ -105,14 +101,10 @@ PASS td.className (<td class>): 32 tests
PASS td.hidden: 33 tests PASS td.hidden: 33 tests
PASS td.accessKey: 32 tests PASS td.accessKey: 32 tests
PASS td.tabIndex: 24 tests PASS td.tabIndex: 24 tests
PASS td.colSpan: 11 tests PASS td.colSpan: 65 tests
FAIL td.colSpan: setAttribute() to 4294967296 assert_equals: IDL get expected 1000 but got 1
PASS td.colSpan: 53 tests
PASS td.rowSpan: 6 tests PASS td.rowSpan: 6 tests
FAIL td.rowSpan: setAttribute() to 0 assert_equals: IDL get expected 0 but got 1 FAIL td.rowSpan: setAttribute() to 0 assert_equals: IDL get expected 0 but got 1
PASS td.rowSpan: 4 tests PASS td.rowSpan: 7 tests
FAIL td.rowSpan: setAttribute() to 4294967296 assert_equals: IDL get expected 65534 but got 1
PASS td.rowSpan: 2 tests
FAIL td.rowSpan: setAttribute() to "-0" assert_equals: IDL get expected 0 but got 1 FAIL td.rowSpan: setAttribute() to "-0" assert_equals: IDL get expected 0 but got 1
FAIL td.rowSpan: setAttribute() to "0" assert_equals: IDL get expected 0 but got 1 FAIL td.rowSpan: setAttribute() to "0" assert_equals: IDL get expected 0 but got 1
PASS td.rowSpan: 40 tests PASS td.rowSpan: 40 tests
...@@ -138,14 +130,10 @@ PASS th.className (<th class>): 32 tests ...@@ -138,14 +130,10 @@ PASS th.className (<th class>): 32 tests
PASS th.hidden: 33 tests PASS th.hidden: 33 tests
PASS th.accessKey: 32 tests PASS th.accessKey: 32 tests
PASS th.tabIndex: 24 tests PASS th.tabIndex: 24 tests
PASS th.colSpan: 11 tests PASS th.colSpan: 65 tests
FAIL th.colSpan: setAttribute() to 4294967296 assert_equals: IDL get expected 1000 but got 1
PASS th.colSpan: 53 tests
PASS th.rowSpan: 6 tests PASS th.rowSpan: 6 tests
FAIL th.rowSpan: setAttribute() to 0 assert_equals: IDL get expected 0 but got 1 FAIL th.rowSpan: setAttribute() to 0 assert_equals: IDL get expected 0 but got 1
PASS th.rowSpan: 4 tests PASS th.rowSpan: 7 tests
FAIL th.rowSpan: setAttribute() to 4294967296 assert_equals: IDL get expected 65534 but got 1
PASS th.rowSpan: 2 tests
FAIL th.rowSpan: setAttribute() to "-0" assert_equals: IDL get expected 0 but got 1 FAIL th.rowSpan: setAttribute() to "-0" assert_equals: IDL get expected 0 but got 1
FAIL th.rowSpan: setAttribute() to "0" assert_equals: IDL get expected 0 but got 1 FAIL th.rowSpan: setAttribute() to "0" assert_equals: IDL get expected 0 but got 1
PASS th.rowSpan: 40 tests PASS th.rowSpan: 40 tests
......
...@@ -5,7 +5,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE ...@@ -5,7 +5,7 @@ On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE
PASS document.getElementById('case1').span is 1 PASS document.getElementById('case1').span is 1
PASS document.getElementById('case2').span is 1 PASS document.getElementById('case2').span is 1
PASS document.getElementById('case3').span is 1 PASS document.getElementById('case3').span is 1000
PASS document.getElementById('case4').span is 1 PASS document.getElementById('case4').span is 1
PASS document.getElementById('case5').span is 1 PASS document.getElementById('case5').span is 1
PASS document.getElementById('case6').span is 1 PASS document.getElementById('case6').span is 1
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
shouldBe("document.getElementById('case1').span", "1"); shouldBe("document.getElementById('case1').span", "1");
shouldBe("document.getElementById('case2').span", "1"); shouldBe("document.getElementById('case2').span", "1");
shouldBe("document.getElementById('case3').span", "1"); shouldBe("document.getElementById('case3').span", "1000");
shouldBe("document.getElementById('case4').span", "1"); shouldBe("document.getElementById('case4').span", "1");
shouldBe("document.getElementById('case5').span", "1"); shouldBe("document.getElementById('case5').span", "1");
shouldBe("document.getElementById('case6').span", "1"); shouldBe("document.getElementById('case6').span", "1");
...@@ -57,4 +57,4 @@ ...@@ -57,4 +57,4 @@
test(); test();
</script> </script>
</body> </body>
</html> </html>
\ No newline at end of file
...@@ -24,7 +24,7 @@ PASS spanAttributeEffect("2" + arabicIndicDigitOne) is 2 ...@@ -24,7 +24,7 @@ PASS spanAttributeEffect("2" + arabicIndicDigitOne) is 2
PASS spanAttributeEffect("2147483647") is 1000 PASS spanAttributeEffect("2147483647") is 1000
PASS spanAttributeEffect("2147483648") is 1000 PASS spanAttributeEffect("2147483648") is 1000
PASS spanAttributeEffect("4294967295") is 1000 PASS spanAttributeEffect("4294967295") is 1000
PASS spanAttributeEffect("4294967296") is 1 PASS spanAttributeEffect("4294967296") is 1000
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
......
...@@ -44,7 +44,7 @@ shouldBe('spanAttributeEffect("2" + arabicIndicDigitOne)', '2'); ...@@ -44,7 +44,7 @@ shouldBe('spanAttributeEffect("2" + arabicIndicDigitOne)', '2');
shouldBe('spanAttributeEffect("2147483647")', '1000'); shouldBe('spanAttributeEffect("2147483647")', '1000');
shouldBe('spanAttributeEffect("2147483648")', '1000'); shouldBe('spanAttributeEffect("2147483648")', '1000');
shouldBe('spanAttributeEffect("4294967295")', '1000'); shouldBe('spanAttributeEffect("4294967295")', '1000');
shouldBe('spanAttributeEffect("4294967296")', '1'); shouldBe('spanAttributeEffect("4294967296")', '1000');
</script> </script>
</body> </body>
</html> </html>
...@@ -23,7 +23,7 @@ PASS colspanAttributeEffect(arabicIndicDigitOne) is 1 ...@@ -23,7 +23,7 @@ PASS colspanAttributeEffect(arabicIndicDigitOne) is 1
PASS colspanAttributeEffect("2" + arabicIndicDigitOne) is 2 PASS colspanAttributeEffect("2" + arabicIndicDigitOne) is 2
PASS colspanAttributeEffect("2147483647") is 1000 PASS colspanAttributeEffect("2147483647") is 1000
PASS colspanAttributeEffect("4294967295") is 1000 PASS colspanAttributeEffect("4294967295") is 1000
PASS colspanAttributeEffect("4294967296") is 1 PASS colspanAttributeEffect("4294967296") is 1000
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
......
...@@ -43,7 +43,7 @@ shouldBe('colspanAttributeEffect("2" + arabicIndicDigitOne)', '2'); ...@@ -43,7 +43,7 @@ shouldBe('colspanAttributeEffect("2" + arabicIndicDigitOne)', '2');
shouldBe('colspanAttributeEffect("2147483647")', '1000'); shouldBe('colspanAttributeEffect("2147483647")', '1000');
shouldBe('colspanAttributeEffect("4294967295")', '1000'); shouldBe('colspanAttributeEffect("4294967295")', '1000');
shouldBe('colspanAttributeEffect("4294967296")', '1'); shouldBe('colspanAttributeEffect("4294967296")', '1000');
</script> </script>
</body> </body>
</html> </html>
...@@ -23,7 +23,7 @@ PASS rowspanAttributeEffect(arabicIndicDigitOne) is 1 ...@@ -23,7 +23,7 @@ PASS rowspanAttributeEffect(arabicIndicDigitOne) is 1
PASS rowspanAttributeEffect("2" + arabicIndicDigitOne) is 2 PASS rowspanAttributeEffect("2" + arabicIndicDigitOne) is 2
PASS rowspanAttributeEffect("2147483647") is 65534 PASS rowspanAttributeEffect("2147483647") is 65534
PASS rowspanAttributeEffect("4294967295") is 65534 PASS rowspanAttributeEffect("4294967295") is 65534
PASS rowspanAttributeEffect("4294967296") is 1 PASS rowspanAttributeEffect("4294967296") is 65534
PASS successfullyParsed is true PASS successfullyParsed is true
TEST COMPLETE TEST COMPLETE
......
...@@ -43,7 +43,7 @@ shouldBe('rowspanAttributeEffect("2" + arabicIndicDigitOne)', '2'); ...@@ -43,7 +43,7 @@ shouldBe('rowspanAttributeEffect("2" + arabicIndicDigitOne)', '2');
shouldBe('rowspanAttributeEffect("2147483647")', '65534'); shouldBe('rowspanAttributeEffect("2147483647")', '65534');
shouldBe('rowspanAttributeEffect("4294967295")', '65534'); shouldBe('rowspanAttributeEffect("4294967295")', '65534');
shouldBe('rowspanAttributeEffect("4294967296")', '1'); shouldBe('rowspanAttributeEffect("4294967296")', '65534');
</script> </script>
</body> </body>
</html> </html>
...@@ -240,6 +240,7 @@ blink_core_sources("html") { ...@@ -240,6 +240,7 @@ blink_core_sources("html") {
"RadioNodeList.h", "RadioNodeList.h",
"RelList.cpp", "RelList.cpp",
"RelList.h", "RelList.h",
"TableConstants.h",
"TextControlElement.cpp", "TextControlElement.cpp",
"TextControlElement.h", "TextControlElement.h",
"TextDocument.cpp", "TextDocument.cpp",
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
#include "core/dom/ElementTraversal.h" #include "core/dom/ElementTraversal.h"
#include "core/frame/UseCounter.h" #include "core/frame/UseCounter.h"
#include "core/html/HTMLTableElement.h" #include "core/html/HTMLTableElement.h"
#include "core/html/TableConstants.h"
#include "core/html/parser/HTMLParserIdioms.h" #include "core/html/parser/HTMLParserIdioms.h"
#include "core/layout/LayoutTableCell.h" #include "core/layout/LayoutTableCell.h"
...@@ -38,11 +39,6 @@ namespace blink { ...@@ -38,11 +39,6 @@ namespace blink {
using namespace HTMLNames; using namespace HTMLNames;
namespace {
const unsigned kDefaultColSpan = 1;
const unsigned kDefaultRowSpan = 1;
} // namespace
inline HTMLTableCellElement::HTMLTableCellElement(const QualifiedName& tag_name, inline HTMLTableCellElement::HTMLTableCellElement(const QualifiedName& tag_name,
Document& document) Document& document)
: HTMLTablePartElement(tag_name, document) {} : HTMLTablePartElement(tag_name, document) {}
...@@ -52,8 +48,8 @@ DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLTableCellElement) ...@@ -52,8 +48,8 @@ DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLTableCellElement)
unsigned HTMLTableCellElement::colSpan() const { unsigned HTMLTableCellElement::colSpan() const {
const AtomicString& col_span_value = FastGetAttribute(colspanAttr); const AtomicString& col_span_value = FastGetAttribute(colspanAttr);
unsigned value = 0; unsigned value = 0;
if (col_span_value.IsEmpty() || if (!ParseHTMLClampedNonNegativeInteger(col_span_value, kMinColSpan,
!ParseHTMLNonNegativeInteger(col_span_value, value)) kMaxColSpan, value))
return kDefaultColSpan; return kDefaultColSpan;
// Counting for https://github.com/whatwg/html/issues/1198 // Counting for https://github.com/whatwg/html/issues/1198
UseCounter::Count(GetDocument(), WebFeature::kHTMLTableCellElementColspan); UseCounter::Count(GetDocument(), WebFeature::kHTMLTableCellElementColspan);
...@@ -64,16 +60,16 @@ unsigned HTMLTableCellElement::colSpan() const { ...@@ -64,16 +60,16 @@ unsigned HTMLTableCellElement::colSpan() const {
UseCounter::Count(GetDocument(), UseCounter::Count(GetDocument(),
WebFeature::kHTMLTableCellElementColspanGreaterThan1000); WebFeature::kHTMLTableCellElementColspanGreaterThan1000);
} }
return std::max(1u, std::min(value, MaxColSpan())); return value;
} }
unsigned HTMLTableCellElement::rowSpan() const { unsigned HTMLTableCellElement::rowSpan() const {
const AtomicString& row_span_value = FastGetAttribute(rowspanAttr); const AtomicString& row_span_value = FastGetAttribute(rowspanAttr);
unsigned value = 0; unsigned value = 0;
if (row_span_value.IsEmpty() || if (!ParseHTMLClampedNonNegativeInteger(row_span_value, kMinRowSpan,
!ParseHTMLNonNegativeInteger(row_span_value, value)) kMaxRowSpan, value))
return kDefaultRowSpan; return kDefaultRowSpan;
return std::max(1u, std::min(value, MaxRowSpan())); return value;
} }
int HTMLTableCellElement::cellIndex() const { int HTMLTableCellElement::cellIndex() const {
......
...@@ -50,13 +50,6 @@ class CORE_EXPORT HTMLTableCellElement final : public HTMLTablePartElement { ...@@ -50,13 +50,6 @@ class CORE_EXPORT HTMLTableCellElement final : public HTMLTablePartElement {
const AtomicString& Headers() const; const AtomicString& Headers() const;
void setRowSpan(unsigned); void setRowSpan(unsigned);
// Public so that HTMLTableColElement can use MaxColSpan. MaxRowSpan is only
// used by this class but keeping them together seems desirable.
// https://html.spec.whatwg.org/#dom-tdth-colspan
static unsigned MaxColSpan() { return 1000u; }
// https://html.spec.whatwg.org/#dom-tdth-rowspan
static unsigned MaxRowSpan() { return 65534u; }
private: private:
HTMLTableCellElement(const QualifiedName&, Document&); HTMLTableCellElement(const QualifiedName&, Document&);
......
...@@ -24,25 +24,22 @@ ...@@ -24,25 +24,22 @@
#include "core/html/HTMLTableColElement.h" #include "core/html/HTMLTableColElement.h"
#include <algorithm>
#include "core/CSSPropertyNames.h" #include "core/CSSPropertyNames.h"
#include "core/HTMLNames.h" #include "core/HTMLNames.h"
#include "core/html/HTMLTableCellElement.h" #include "core/html/HTMLTableCellElement.h"
#include "core/html/HTMLTableElement.h" #include "core/html/HTMLTableElement.h"
#include "core/html/TableConstants.h"
#include "core/html/parser/HTMLParserIdioms.h" #include "core/html/parser/HTMLParserIdioms.h"
#include "core/layout/LayoutTableCol.h" #include "core/layout/LayoutTableCol.h"
#include <algorithm>
namespace blink { namespace blink {
using namespace HTMLNames; using namespace HTMLNames;
namespace {
const unsigned kDefaultSpan = 1;
} // namespace
inline HTMLTableColElement::HTMLTableColElement(const QualifiedName& tag_name, inline HTMLTableColElement::HTMLTableColElement(const QualifiedName& tag_name,
Document& document) Document& document)
: HTMLTablePartElement(tag_name, document), span_(1) {} : HTMLTablePartElement(tag_name, document), span_(kDefaultColSpan) {}
DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLTableColElement) DEFINE_ELEMENT_FACTORY_WITH_TAGNAME(HTMLTableColElement)
...@@ -68,14 +65,10 @@ void HTMLTableColElement::ParseAttribute( ...@@ -68,14 +65,10 @@ void HTMLTableColElement::ParseAttribute(
const AttributeModificationParams& params) { const AttributeModificationParams& params) {
if (params.name == spanAttr) { if (params.name == spanAttr) {
unsigned new_span = 0; unsigned new_span = 0;
if (params.new_value.IsEmpty() || if (!ParseHTMLClampedNonNegativeInteger(params.new_value, kMinColSpan,
!ParseHTMLNonNegativeInteger(params.new_value, new_span) || kMaxColSpan, new_span)) {
new_span < 1) { new_span = kDefaultColSpan;
// If the value of span is not a valid non-negative integer greater than
// zero, set it to 1.
new_span = kDefaultSpan;
} }
new_span = std::min(new_span, HTMLTableCellElement::MaxColSpan());
span_ = new_span; span_ = new_span;
if (GetLayoutObject() && GetLayoutObject()->IsLayoutTableCol()) if (GetLayoutObject() && GetLayoutObject()->IsLayoutTableCol())
GetLayoutObject()->UpdateFromElement(); GetLayoutObject()->UpdateFromElement();
...@@ -104,7 +97,7 @@ HTMLTableColElement::AdditionalPresentationAttributeStyle() { ...@@ -104,7 +97,7 @@ HTMLTableColElement::AdditionalPresentationAttributeStyle() {
} }
void HTMLTableColElement::setSpan(unsigned n) { void HTMLTableColElement::setSpan(unsigned n) {
SetUnsignedIntegralAttribute(spanAttr, n, kDefaultSpan); SetUnsignedIntegralAttribute(spanAttr, n, kDefaultColSpan);
} }
const AtomicString& HTMLTableColElement::Width() const { const AtomicString& HTMLTableColElement::Width() const {
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef TableConstants_h
#define TableConstants_h
namespace blink {
// https://html.spec.whatwg.org/multipage/tables.html#dom-colgroup-span
// https://html.spec.whatwg.org/multipage/tables.html#dom-col-span
// https://html.spec.whatwg.org/multipage/tables.html#dom-tdth-colspan
constexpr unsigned kDefaultColSpan = 1u;
constexpr unsigned kMinColSpan = 1u;
constexpr unsigned kMaxColSpan = 1000u;
// https://html.spec.whatwg.org/multipage/tables.html#dom-tdth-rowspan
constexpr unsigned kDefaultRowSpan = 1u;
constexpr unsigned kMaxRowSpan = 65534u;
// The minimum value is 1 though the standard says it's 0. It's intentional
// because we don't implement rowSpan=0 behavior.
constexpr unsigned kMinRowSpan = 1;
} // namespace blink
#endif // TableConstants_h
...@@ -226,9 +226,10 @@ bool ParseHTMLInteger(const String& input, int& value) { ...@@ -226,9 +226,10 @@ bool ParseHTMLInteger(const String& input, int& value) {
} }
template <typename CharacterType> template <typename CharacterType>
static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position, static WTF::NumberParsingState ParseHTMLNonNegativeIntegerInternal(
const CharacterType* end, const CharacterType* position,
unsigned& value) { const CharacterType* end,
unsigned& value) {
// This function is an implementation of the following algorithm: // This function is an implementation of the following algorithm:
// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-non-negative-integers // https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-non-negative-integers
// However, in order to support integers >= 2^31, we fold [1] into this. // However, in order to support integers >= 2^31, we fold [1] into this.
...@@ -249,7 +250,7 @@ static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position, ...@@ -249,7 +250,7 @@ static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position,
// Step 5: If position is past the end of input, return an error. // Step 5: If position is past the end of input, return an error.
if (position == end) if (position == end)
return false; return WTF::NumberParsingState::kError;
DCHECK_LT(position, end); DCHECK_LT(position, end);
// Step 6: If the character indicated by position (the first character) is a // Step 6: If the character indicated by position (the first character) is a
...@@ -262,13 +263,13 @@ static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position, ...@@ -262,13 +263,13 @@ static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position,
} }
if (position == end) if (position == end)
return false; return WTF::NumberParsingState::kError;
DCHECK_LT(position, end); DCHECK_LT(position, end);
// Step 7: If the character indicated by position is not an ASCII digit, // Step 7: If the character indicated by position is not an ASCII digit,
// then return an error. // then return an error.
if (!IsASCIIDigit(*position)) if (!IsASCIIDigit(*position))
return false; return WTF::NumberParsingState::kError;
// Step 8: Collect a sequence of characters ... // Step 8: Collect a sequence of characters ...
StringBuilder digits; StringBuilder digits;
...@@ -278,26 +279,36 @@ static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position, ...@@ -278,26 +279,36 @@ static bool ParseHTMLNonNegativeIntegerInternal(const CharacterType* position,
digits.Append(*position++); digits.Append(*position++);
} }
bool ok; WTF::NumberParsingState state;
unsigned digits_value; unsigned digits_value;
if (digits.Is8Bit()) if (digits.Is8Bit()) {
digits_value = digits_value =
CharactersToUIntStrict(digits.Characters8(), digits.length(), &ok); CharactersToUIntStrict(digits.Characters8(), digits.length(), &state);
else } else {
digits_value = digits_value =
CharactersToUIntStrict(digits.Characters16(), digits.length(), &ok); CharactersToUIntStrict(digits.Characters16(), digits.length(), &state);
if (!ok) }
return false; // TODO(tkent): The following code to adjust NumberParsingState is not simple
if (sign < 0 && digits_value != 0) // due to "-0" behavior difference between CharactersToUIntStrict() and
return false; // ParseHTMLNonNegativeIntegerInternal(). Simplify the code by updating
value = digits_value; // CharactersToUIntStrict() to accept "-0".
return true; if (state == WTF::NumberParsingState::kOverflowMax && sign < 0)
return WTF::NumberParsingState::kError;
if (state == WTF::NumberParsingState::kSuccess) {
if (sign < 0 && digits_value != 0)
return WTF::NumberParsingState::kError;
value = digits_value;
}
return state;
} }
// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-non-negative-integers static WTF::NumberParsingState ParseHTMLNonNegativeIntegerInternal(
bool ParseHTMLNonNegativeInteger(const String& input, unsigned& value) { const String& input,
unsigned& value) {
unsigned length = input.length(); unsigned length = input.length();
if (length && input.Is8Bit()) { if (length == 0)
return WTF::NumberParsingState::kError;
if (input.Is8Bit()) {
const LChar* start = input.Characters8(); const LChar* start = input.Characters8();
return ParseHTMLNonNegativeIntegerInternal(start, start + length, value); return ParseHTMLNonNegativeIntegerInternal(start, start + length, value);
} }
...@@ -306,6 +317,33 @@ bool ParseHTMLNonNegativeInteger(const String& input, unsigned& value) { ...@@ -306,6 +317,33 @@ bool ParseHTMLNonNegativeInteger(const String& input, unsigned& value) {
return ParseHTMLNonNegativeIntegerInternal(start, start + length, value); return ParseHTMLNonNegativeIntegerInternal(start, start + length, value);
} }
// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-non-negative-integers
bool ParseHTMLNonNegativeInteger(const String& input, unsigned& value) {
return ParseHTMLNonNegativeIntegerInternal(input, value) ==
WTF::NumberParsingState::kSuccess;
}
bool ParseHTMLClampedNonNegativeInteger(const String& input,
unsigned min,
unsigned max,
unsigned& value) {
unsigned parsed_value;
switch (ParseHTMLNonNegativeIntegerInternal(input, parsed_value)) {
case WTF::NumberParsingState::kError:
return false;
case WTF::NumberParsingState::kOverflowMin:
NOTREACHED() << input;
return false;
case WTF::NumberParsingState::kOverflowMax:
value = max;
return true;
case WTF::NumberParsingState::kSuccess:
value = std::max(min, std::min(parsed_value, max));
return true;
}
return false;
}
template <typename CharacterType> template <typename CharacterType>
static bool IsSpaceOrDelimiter(CharacterType c) { static bool IsSpaceOrDelimiter(CharacterType c) {
return IsHTMLSpace(c) || c == ',' || c == ';'; return IsHTMLSpace(c) || c == ',' || c == ';';
......
...@@ -63,6 +63,13 @@ CORE_EXPORT bool ParseHTMLInteger(const String&, int&); ...@@ -63,6 +63,13 @@ CORE_EXPORT bool ParseHTMLInteger(const String&, int&);
// http://www.whatwg.org/specs/web-apps/current-work/#rules-for-parsing-non-negative-integers // http://www.whatwg.org/specs/web-apps/current-work/#rules-for-parsing-non-negative-integers
CORE_EXPORT bool ParseHTMLNonNegativeInteger(const String&, unsigned&); CORE_EXPORT bool ParseHTMLNonNegativeInteger(const String&, unsigned&);
// https://html.spec.whatwg.org/multipage/common-dom-interfaces.html#clamped-to-the-range
// without default value processing.
bool ParseHTMLClampedNonNegativeInteger(const String&,
unsigned min,
unsigned max,
unsigned&);
// https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-a-list-of-floating-point-numbers // https://html.spec.whatwg.org/multipage/infrastructure.html#rules-for-parsing-a-list-of-floating-point-numbers
CORE_EXPORT Vector<double> ParseHTMLListOfFloatingPointNumbers(const String&); CORE_EXPORT Vector<double> ParseHTMLListOfFloatingPointNumbers(const String&);
......
...@@ -28,7 +28,7 @@ static bool IsCharacterAllowedInBase(UChar c, int base) { ...@@ -28,7 +28,7 @@ static bool IsCharacterAllowedInBase(UChar c, int base) {
template <typename IntegralType, typename CharType> template <typename IntegralType, typename CharType>
static inline IntegralType ToIntegralType(const CharType* data, static inline IntegralType ToIntegralType(const CharType* data,
size_t length, size_t length,
bool* ok, NumberParsingState* parsing_state,
int base) { int base) {
static_assert(std::is_integral<IntegralType>::value, static_assert(std::is_integral<IntegralType>::value,
"IntegralType must be an integral type."); "IntegralType must be an integral type.");
...@@ -38,10 +38,12 @@ static inline IntegralType ToIntegralType(const CharType* data, ...@@ -38,10 +38,12 @@ static inline IntegralType ToIntegralType(const CharType* data,
std::numeric_limits<IntegralType>::min(); std::numeric_limits<IntegralType>::min();
static constexpr bool kIsSigned = static constexpr bool kIsSigned =
std::numeric_limits<IntegralType>::is_signed; std::numeric_limits<IntegralType>::is_signed;
DCHECK(parsing_state);
IntegralType value = 0; IntegralType value = 0;
bool is_ok = false; NumberParsingState state = NumberParsingState::kError;
bool is_negative = false; bool is_negative = false;
bool overflow = false;
if (!data) if (!data)
goto bye; goto bye;
...@@ -75,28 +77,33 @@ static inline IntegralType ToIntegralType(const CharType* data, ...@@ -75,28 +77,33 @@ static inline IntegralType ToIntegralType(const CharType* data,
else else
digit_value = c - 'A' + 10; digit_value = c - 'A' + 10;
bool overflow;
if (is_negative) { if (is_negative) {
// Overflow condition: // Overflow condition:
// value * base - digitValue < integralMin // value * base - digit_value < kIntegralMin
// <=> value < (integralMin + digitValue) / base // <=> value < (kIntegralMin + digit_value) / base
// We must be careful of rounding errors here, but the default rounding // We must be careful of rounding errors here, but the default rounding
// mode (round to zero) works well, so we can use this formula as-is. // mode (round to zero) works well, so we can use this formula as-is.
overflow = value < (kIntegralMin + digit_value) / base; if (value < (kIntegralMin + digit_value) / base) {
state = NumberParsingState::kOverflowMin;
overflow = true;
}
} else { } else {
// Overflow condition: // Overflow condition:
// value * base + digitValue > integralMax // value * base + digit_value > kIntegralMax
// <=> value > (integralMax + digitValue) / base // <=> value > (kIntegralMax + digit_value) / base
// Ditto regarding rounding errors. // Ditto regarding rounding errors.
overflow = value > (kIntegralMax - digit_value) / base; if (value > (kIntegralMax - digit_value) / base) {
state = NumberParsingState::kOverflowMax;
overflow = true;
}
} }
if (overflow)
goto bye;
if (is_negative) if (!overflow) {
value = base * value - digit_value; if (is_negative)
else value = base * value - digit_value;
value = base * value + digit_value; else
value = base * value + digit_value;
}
++data; ++data;
} }
...@@ -106,12 +113,29 @@ static inline IntegralType ToIntegralType(const CharType* data, ...@@ -106,12 +113,29 @@ static inline IntegralType ToIntegralType(const CharType* data,
++data; ++data;
} }
if (!length) if (length == 0) {
is_ok = true; if (!overflow)
state = NumberParsingState::kSuccess;
} else {
// Even if we detected overflow, we return kError for trailing garbage.
state = NumberParsingState::kError;
}
bye: bye:
*parsing_state = state;
return state == NumberParsingState::kSuccess ? value : 0;
}
template <typename IntegralType, typename CharType>
static inline IntegralType ToIntegralType(const CharType* data,
size_t length,
bool* ok,
int base) {
NumberParsingState state;
IntegralType value =
ToIntegralType<IntegralType, CharType>(data, length, &state, base);
if (ok) if (ok)
*ok = is_ok; *ok = state == NumberParsingState::kSuccess;
return is_ok ? value : 0; return value;
} }
template <typename CharType> template <typename CharType>
...@@ -138,6 +162,18 @@ static unsigned LengthOfCharactersAsInteger(const CharType* data, ...@@ -138,6 +162,18 @@ static unsigned LengthOfCharactersAsInteger(const CharType* data,
return i; return i;
} }
unsigned CharactersToUIntStrict(const LChar* data,
size_t length,
NumberParsingState* state) {
return ToIntegralType<unsigned, LChar>(data, length, state, 10);
}
unsigned CharactersToUIntStrict(const UChar* data,
size_t length,
NumberParsingState* state) {
return ToIntegralType<unsigned, UChar>(data, length, state, 10);
}
int CharactersToIntStrict(const LChar* data, int CharactersToIntStrict(const LChar* data,
size_t length, size_t length,
bool* ok, bool* ok,
......
...@@ -26,7 +26,18 @@ WTF_EXPORT int CharactersToInt(const UChar*, ...@@ -26,7 +26,18 @@ WTF_EXPORT int CharactersToInt(const UChar*,
size_t, size_t,
bool* ok = 0); // ignores trailing garbage bool* ok = 0); // ignores trailing garbage
enum class NumberParsingState {
kSuccess,
kError,
// For UInt functions, kOverflowMin never happens. Negative numbers are
// treated as kError. This behavior matches to the HTML standard.
// https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-non-negative-integers
kOverflowMin,
kOverflowMax,
};
// string -> unsigned. // string -> unsigned.
// These functions do not accept "-0".
WTF_EXPORT unsigned CharactersToUIntStrict(const LChar*, WTF_EXPORT unsigned CharactersToUIntStrict(const LChar*,
size_t, size_t,
bool* ok = 0, bool* ok = 0,
...@@ -42,6 +53,15 @@ WTF_EXPORT unsigned CharactersToUInt(const UChar*, ...@@ -42,6 +53,15 @@ WTF_EXPORT unsigned CharactersToUInt(const UChar*,
size_t, size_t,
bool* ok = 0); // ignores trailing garbage bool* ok = 0); // ignores trailing garbage
// NumberParsingState versions of CharactersToUIntStrict. They can detect
// overflow. |NumberParsingState*| should not be nullptr;
WTF_EXPORT unsigned CharactersToUIntStrict(const LChar*,
size_t,
NumberParsingState*);
WTF_EXPORT unsigned CharactersToUIntStrict(const UChar*,
size_t,
NumberParsingState*);
// string -> int64_t. // string -> int64_t.
WTF_EXPORT int64_t CharactersToInt64Strict(const LChar*, WTF_EXPORT int64_t CharactersToInt64Strict(const LChar*,
size_t, size_t,
...@@ -59,6 +79,7 @@ WTF_EXPORT int64_t CharactersToInt64(const UChar*, ...@@ -59,6 +79,7 @@ WTF_EXPORT int64_t CharactersToInt64(const UChar*,
bool* ok = 0); // ignores trailing garbage bool* ok = 0); // ignores trailing garbage
// string -> uint64_t. // string -> uint64_t.
// These functions do not accept "-0".
WTF_EXPORT uint64_t CharactersToUInt64Strict(const LChar*, WTF_EXPORT uint64_t CharactersToUInt64Strict(const LChar*,
size_t, size_t,
bool* ok = 0, bool* ok = 0,
......
...@@ -123,4 +123,20 @@ TEST(StringToNumberTest, TestCharactersToIntStrict) { ...@@ -123,4 +123,20 @@ TEST(StringToNumberTest, TestCharactersToIntStrict) {
#undef EXPECT_VALID #undef EXPECT_VALID
#undef EXPECT_INVALID #undef EXPECT_INVALID
} }
NumberParsingState ParseUInt(const char* str, unsigned* value) {
NumberParsingState state;
*value = CharactersToUIntStrict(reinterpret_cast<const LChar*>(str),
std::strlen(str), &state);
return state;
}
TEST(StringToNumberTest, NumberParsingState) {
unsigned value;
EXPECT_EQ(NumberParsingState::kOverflowMax, ParseUInt("10000000000", &value));
EXPECT_EQ(NumberParsingState::kError, ParseUInt("10000000000abc", &value));
EXPECT_EQ(NumberParsingState::kError, ParseUInt("-10000000000", &value));
EXPECT_EQ(NumberParsingState::kError, ParseUInt("-0", &value));
EXPECT_EQ(NumberParsingState::kSuccess, ParseUInt("10", &value));
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment