Bug 781569 - Implement the value sanitizing algorithm for <input type=time>. r=smaug

This commit is contained in:
Mounir Lamouri 2013-01-11 15:00:52 +00:00
parent 01dc822c0d
commit cc8d25757d
4 changed files with 213 additions and 7 deletions

View File

@ -2995,6 +2995,13 @@ nsHTMLInputElement::SanitizeValue(nsAString& aValue)
}
}
break;
case NS_FORM_INPUT_TIME:
{
if (!aValue.IsEmpty() && !IsValidTime(aValue)) {
aValue.Truncate();
}
}
break;
}
}
@ -3133,6 +3140,88 @@ nsHTMLInputElement::NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const
return (aYear % 400 == 0 || (aYear % 100 != 0 && aYear % 4 == 0))
? 29 : 28;
}
/* static */ bool
nsHTMLInputElement::DigitSubStringToNumber(const nsAString& aStr,
uint32_t aStart, uint32_t aLen,
uint32_t* aRetVal)
{
MOZ_ASSERT(aStr.Length() > (aStart + aLen - 1));
for (uint32_t offset = 0; offset < aLen; ++offset) {
if (!NS_IsAsciiDigit(aStr[aStart + offset])) {
return false;
}
}
nsresult ec;
*aRetVal = static_cast<uint32_t>(PromiseFlatString(Substring(aStr, aStart, aLen)).ToInteger(&ec));
return NS_SUCCEEDED(ec);
}
bool
nsHTMLInputElement::IsValidTime(const nsAString& aValue) const
{
/* The string must have the following parts:
* - HOURS: two digits, value being in [0, 23];
* - Colon (:);
* - MINUTES: two digits, value being in [0, 59];
* - Optional:
* - Colon (:);
* - SECONDS: two digits, value being in [0, 59];
* - Optional:
* - DOT (.);
* - FRACTIONAL SECONDS: one to three digits, no value range.
*/
// The following format is the shorter one allowed: "HH:MM".
if (aValue.Length() < 5) {
return false;
}
uint32_t hours;
if (!DigitSubStringToNumber(aValue, 0, 2, &hours) || hours > 23) {
return false;
}
// Hours/minutes separator.
if (aValue[2] != ':') {
return false;
}
uint32_t minutes;
if (!DigitSubStringToNumber(aValue, 3, 2, &minutes) || minutes > 59) {
return false;
}
if (aValue.Length() == 5) {
return true;
}
// The following format is the next shorter one: "HH:MM:SS".
if (aValue.Length() < 8 || aValue[5] != ':') {
return false;
}
uint32_t seconds;
if (!DigitSubStringToNumber(aValue, 6, 2, &seconds) || seconds > 59) {
return false;
}
if (aValue.Length() == 8) {
return true;
}
// The string must follow this format now: "HH:MM:SS.{s,ss,sss}".
// There can be 1 to 3 digits for the fractions of seconds.
if (aValue.Length() == 9 || aValue.Length() > 12 || aValue[8] != '.') {
return false;
}
uint32_t fractionsSeconds;
return DigitSubStringToNumber(aValue, 9, aValue.Length() - 9, &fractionsSeconds);
}
bool
nsHTMLInputElement::ParseAttribute(int32_t aNamespaceID,

View File

@ -334,6 +334,23 @@ protected:
*/
static bool IsValidEmailAddressList(const nsAString& aValue);
/**
* This helper method convert a sub-string that contains only digits to a
* number (unsigned int given that it can't contain a minus sign).
* This method will return whether the sub-string is correctly formatted
* (ie. contains only digit) and it can be successfuly parsed to generate a
* number).
* If the method returns true, |aResult| will contained the parsed number.
*
* @param aValue the string on which the sub-string will be extracted and parsed.
* @param aStart the beginning of the sub-string in aValue.
* @param aLen the length of the sub-string.
* @param aResult the parsed number.
* @return whether the sub-string has been parsed successfully.
*/
static bool DigitSubStringToNumber(const nsAString& aValue, uint32_t aStart,
uint32_t aLen, uint32_t* aResult);
// Helper method
nsresult SetValueInternal(const nsAString& aValue,
bool aUserInput,
@ -609,6 +626,15 @@ protected:
*/
uint32_t NumberOfDaysInMonth(uint32_t aMonth, uint32_t aYear) const;
/**
* Returns whether aValue is a valid time as described by HTML specifications:
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
*
* @param aValue the string to be tested.
* @return Whether the string is a valid time per HTML specifications.
*/
bool IsValidTime(const nsAString& aValue) const;
/**
* Sets the value of the element to the string representation of the double.
*

View File

@ -56,6 +56,7 @@ function sanitizeValue(aType, aValue)
case "number":
return (parseFloat(aValue) + "" === aValue) ? aValue : "";
case "date":
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
function getNumbersOfDaysInMonth(aMonth, aYear) {
if (aMonth === 2) {
return (aYear % 400 === 0 || (aYear % 100 != 0 && aYear % 4 === 0)) ? 29 : 28;
@ -79,7 +80,40 @@ function sanitizeValue(aType, aValue)
var day = Number(match[3]);
return 1 <= day && day <= getNumbersOfDaysInMonth(month, year) ? aValue : "";
case "time":
return "";
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
var match = /^([0-9]{2}):([0-9]{2})(.*)$/.exec(aValue);
if (!match) {
return "";
}
var hours = match[1];
if (hours < 0 || hours > 23) {
return "";
}
var minutes = match[2];
if (minutes < 0 || minutes > 59) {
return "";
}
var other = match[3];
if (other == "") {
return aValue;
}
match = /^:([0-9]{2})(.*)$/.exec(other);
if (!match) {
return "";
}
var seconds = match[1];
if (seconds < 0 || seconds > 59) {
return "";
}
var other = match[2];
if (other == "") {
return aValue;
}
match = /^.([0-9]{1,3})$/.exec(other);
if (!match) {
return "";
}
return aValue;
case "month":
case "week":
case "datetime":
@ -102,10 +136,6 @@ function sanitizeValue(aType, aValue)
function checkSanitizing(element)
{
if (element.type === 'time') {
return;
}
var testData =
[
// For text, password, search, tel, email:
@ -168,6 +198,63 @@ function checkSanitizing(element)
"1234-12 12",
"1234 12-12",
"1234 12 12",
// For time:
"1",
"10",
"10:",
"10:1",
"21:21",
":21:21",
"-21:21",
" 21:21",
"21-21",
"21:21:",
"21:211",
"121:211",
"21:21 ",
"00:00",
"-1:00",
"24:00",
"00:60",
"01:01",
"23:59",
"99:99",
"8:30",
"19:2",
"19:a2",
"4c:19",
"10:.1",
"1.:10",
"13:37:42",
"13:37.42",
"13:37:42 ",
"13:37:42.",
"13:37:61.",
"13:37:00",
"13:37:99",
"13:37:b5",
"13:37:-1",
"13:37:.1",
"13:37:1.",
"13:37:42.001",
"13:37:42.001",
"13:37:42.abc",
"13:37:42.00c",
"13:37:42.a23",
"13:37:42.12e",
"13:37:42.1e1",
"13:37:42.e11",
"13:37:42.1",
"13:37:42.99",
"13:37:42.0",
"13:37:42.00",
"13:37:42.000",
"13:37:42.-1",
"13:37:42.1.1",
"13:37:42.1,1",
"13:37:42.",
"foo12:12",
"13:37:42.100000000000",
];
for (value of testData) {

View File

@ -44,6 +44,8 @@ var todoTypes = [
"datetime", "month", "week", "datetime-local", "range", "color"
];
var nonTrivialSanitizing = [ 'number', 'date', 'time' ];
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({'set': [["dom.experimental_forms", true]]}, function() {
@ -54,13 +56,15 @@ for (var i=0; i<length; ++i) {
e.type = testData[i][0];
var expectedValue;
if ((testData[i][0] == 'number' && testData[j][0] == 'date') ||
(testData[i][0] == 'date' && testData[j][0] == 'number')) {
if (nonTrivialSanitizing.indexOf(testData[i][0]) != -1 &&
nonTrivialSanitizing.indexOf(testData[j][0]) != -1) {
expectedValue = '';
} else if (testData[i][0] == 'number' || testData[j][0] == 'number') {
expectedValue = '42';
} else if (testData[i][0] == 'date' || testData[j][0] == 'date') {
expectedValue = '2012-12-21';
} else if (testData[i][0] == 'time' || testData[j][0] == 'time') {
expectedValue = '21:21';
} else {
expectedValue = "foo";
}