diff options
Diffstat (limited to 'dom/html/test/forms/test_input_sanitization.html')
-rw-r--r-- | dom/html/test/forms/test_input_sanitization.html | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/dom/html/test/forms/test_input_sanitization.html b/dom/html/test/forms/test_input_sanitization.html new file mode 100644 index 000000000..cd9877fdf --- /dev/null +++ b/dom/html/test/forms/test_input_sanitization.html @@ -0,0 +1,565 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=549475 +--> +<head> + <title>Test for Bug 549475</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=549475">Mozilla Bug 549475</a> +<p id="display"></p> +<pre id="test"> +<div id='content'> + <form> + </form> +</div> +<script type="application/javascript"> + +SimpleTest.requestLongerTimeout(2); + +/** + * This files tests the 'value sanitization algorithm' for the various input + * types. Note that an input's value is affected by more than just its type's + * value sanitization algorithm; e.g. some type=range has actions that the user + * agent must perform to change the element's value to avoid underflow/overflow + * and step mismatch (when possible). We specifically avoid triggering these + * other actions here so that this test only tests the value sanitization + * algorithm for the various input types. + * + * XXXjwatt splitting out testing of the value sanitization algorithm and + * "other things" that affect .value makes it harder to know what we're testing + * and what we've missed, because what's included in the value sanitization + * algorithm and what's not is different from input type to input type. It + * seems to me it would be better to have a test (maybe one per type) focused + * on testing .value for permutations of all other inputs that can affect it. + * The value sanitization algorithm is just an internal spec concept after all. + */ + +// We buffer up the results of sets of sub-tests, and avoid outputting log +// entries for them all if they all pass. Otherwise, we have an enormous amount +// of test output. + +var delayedTests = []; +var anyFailedDelayedTests = false; + +function delayed_is(actual, expected, description) +{ + var result = actual == expected; + delayedTests.push({ actual: actual, expected: expected, description: description }); + if (!result) { + anyFailedDelayedTests = true; + } +} + +function flushDelayedTests(description) +{ + if (anyFailedDelayedTests) { + info("Outputting individual results for \"" + description + "\" due to failures in subtests"); + for (var test of delayedTests) { + is(test.actual, test.expected, test.description); + } + } else { + ok(true, description + " (" + delayedTests.length + " subtests)"); + } + delayedTests = []; + anyFailedDelayedTests = false; +} + +// We are excluding "file" because it's too different from the other types. +// And it has no sanitizing algorithm. +var inputTypes = +[ + "text", "password", "search", "tel", "hidden", "checkbox", "radio", + "submit", "image", "reset", "button", "email", "url", "number", "date", + "time", "range", "color", "month", "week", "datetime-local" +]; + +var valueModeValue = +[ + "text", "search", "url", "tel", "email", "password", "date", "datetime", + "month", "week", "time", "datetime-local", "number", "range", "color", +]; + +function sanitizeDate(aValue) +{ + // 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; + } + return (aMonth === 1 || aMonth === 3 || aMonth === 5 || aMonth === 7 || + aMonth === 8 || aMonth === 10 || aMonth === 12) ? 31 : 30; + } + + var match = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})$/.exec(aValue); + if (!match) { + return ""; + } + var year = Number(match[1]); + if (year === 0) { + return ""; + } + var month = Number(match[2]); + if (month > 12 || month < 1) { + return ""; + } + var day = Number(match[3]); + return 1 <= day && day <= getNumbersOfDaysInMonth(month, year) ? aValue : ""; +} + +function sanitizeTime(aValue) +{ + // 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; +} + +function sanitizeDateTimeLocal(aValue) +{ + // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-local-date-and-time-string + if (aValue.length < 16) { + return ""; + } + + var separator = aValue[10]; + if (separator != "T" && separator != " ") { + return ""; + } + + var [date, time] = aValue.split(separator); + if (!sanitizeDate(date)) { + return ""; + } + + if (!sanitizeTime(time)) { + return ""; + } + + // Normalize datetime-local string. + // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-normalised-local-date-and-time-string + if (separator == " ") { + aValue = date + "T" + time; + } + + if (aValue.length == 16) { + return aValue; + } + + if (aValue.length > 19) { + var milliseconds = aValue.substring(20); + if (Number(milliseconds) != 0) { + return aValue; + } + aValue = aValue.slice(0, 19); + } + + var seconds = aValue.substring(17); + if (Number(seconds) != 0) { + return aValue; + } + aValue = aValue.slice(0, 16); + + return aValue; +} + +function sanitizeValue(aType, aValue) +{ + // http://www.whatwg.org/html/#value-sanitization-algorithm + switch (aType) { + case "text": + case "password": + case "search": + case "tel": + return aValue.replace(/[\n\r]/g, ""); + case "url": + case "email": + return aValue.replace(/[\n\r]/g, "").replace(/^[\u0020\u0009\t\u000a\u000c\u000d]+|[\u0020\u0009\t\u000a\u000c\u000d]+$/g, ""); + case "number": + return isNaN(Number(aValue)) ? "" : aValue; + case "range": + var defaultMinimum = 0; + var defaultMaximum = 100; + var value = Number(aValue); + if (isNaN(value)) { + return ((defaultMaximum - defaultMinimum)/2).toString(); // "50" + } + if (value < defaultMinimum) { + return defaultMinimum.toString(); + } + if (value > defaultMaximum) { + return defaultMaximum.toString(); + } + return aValue; + case "date": + return sanitizeDate(aValue); + case "time": + return sanitizeTime(aValue); + case "month": + // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-month-string + var match = /^([0-9]{4,})-([0-9]{2})$/.exec(aValue); + if (!match) { + return ""; + } + var year = Number(match[1]); + if (year === 0) { + return ""; + } + var month = Number(match[2]); + if (month > 12 || month < 1) { + return ""; + } + return aValue; + case "week": + // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-week-string + function isLeapYear(aYear) { + return ((aYear % 4 == 0) && (aYear % 100 != 0)) || (aYear % 400 == 0); + } + function getDayofWeek(aYear, aMonth, aDay) { /* 0 = Sunday */ + // Tomohiko Sakamoto algorithm. + var monthTable = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]; + aYear -= Number(aMonth < 3); + + return (aYear + parseInt(aYear / 4) - parseInt(aYear / 100) + + parseInt(aYear / 400) + monthTable[aMonth - 1] + aDay) % 7; + } + function getMaximumWeekInYear(aYear) { + var day = getDayofWeek(aYear, 1, 1); + return day == 4 || (day == 3 && isLeapYear(aYear)) ? 53 : 52; + } + + var match = /^([0-9]{4,})-W([0-9]{2})$/.exec(aValue); + if (!match) { + return ""; + } + var year = Number(match[1]); + if (year === 0) { + return ""; + } + var week = Number(match[2]); + if (week > 53 || month < 1) { + return ""; + } + return 1 <= week && week <= getMaximumWeekInYear(year) ? aValue : ""; + case "datetime-local": + return sanitizeDateTimeLocal(aValue); + case "color": + return /^#[0-9A-Fa-f]{6}$/.exec(aValue) ? aValue.toLowerCase() : "#000000"; + default: + return aValue; + } +} + +function checkSanitizing(element, inputTypeDescription) +{ + var testData = + [ + // For text, password, search, tel, email: + "\n\rfoo\n\r", + "foo\n\rbar", + " foo ", + " foo\n\r bar ", + // For url: + "\r\n foobar \n\r", + "\u000B foo \u000B", + "\u000A foo \u000A", + "\u000C foo \u000C", + "\u000d foo \u000d", + "\u0020 foo \u0020", + " \u0009 foo \u0009 ", + // For number and range: + "42", + "13.37", + "1.234567898765432", + "12foo", + "1e2", + "3E42", + // For date: + "1970-01-01", + "1234-12-12", + "1234567890-01-02", + "2012-12-31", + "2012-02-29", + "2000-02-29", + "1234", + "1234-", + "12345", + "1234-01", + "1234-012", + "1234-01-", + "12-12", + "999-01-01", + "1234-56-78-91", + "1234-567-78", + "1234--7-78", + "abcd-12-12", + "thisinotadate", + "2012-13-01", + "1234-12-42", + " 2012-13-01", + " 123-01-01", + "2012- 3-01", + "12- 10- 01", + " 12-0-1", + "2012-3-001", + "2012-12-00", + "2012-12-1r", + "2012-11-31", + "2011-02-29", + "2100-02-29", + "a2000-01-01", + "2000a-01-0'", + "20aa00-01-01", + "2000a2000-01-01", + "2000-1-1", + "2000-1-01", + "2000-01-1", + "2000-01-01 ", + "2000- 01-01", + "-1970-01-01", + "0000-00-00", + "0001-00-00", + "0000-01-01", + "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 color + "#00ff00", + "#000000", + "red", + "#0f0", + "#FFFFAA", + "FFAABB", + "fFAaBb", + "FFAAZZ", + "ABCDEF", + "#7654321", + // For month + "1970-01", + "1234-12", + "123456789-01", + "2013-13", + "0000-00", + "2015-00", + "0001-01", + "1-1", + "888-05", + "2013-3", + "2013-may", + "2000-1a", + "2013-03-13", + "december", + "abcdef", + "12", + " 2013-03", + "2013 - 03", + "2013 03", + "2013/03", + // For week + "1970-W01", + "1970-W53", + "1964-W53", + "1900-W10", + "2004-W53", + "2065-W53", + "2099-W53", + "2010-W53", + "2016-W30", + "1900-W3", + "2016-w30", + "2016-30", + "16-W30", + "2016-Week30", + "2000-100", + "0000-W01", + "00-W01", + "123456-W05", + "1985-W100", + "week", + // For datetime-local + "1970-01-01T00:00", + "1970-01-01Z12:00", + "1970-01-01 00:00:00", + "1970-01-01T00:00:00.0", + "1970-01-01T00:00:00.00", + "1970-01-01T00:00:00.000", + "1970-01-01 00:00:00.20", + "1234567-01-01T12:00", + "2016-13-01T12:00", + "2016-12-32T12:00", + "2016-11-08 15:40:30.0", + "2016-11-08T15:40:30.00", + "2016-11-07T17:30:10", + "2016-12-1T12:45", + "2016-12-01T12:45:30.123456", + "2016-12-01T24:00", + "2016-12-01T12:88:30", + "2016-12-01T12:30:99", + "2016-12-01T12:30:100", + "2016-12-01", + "2016-12-01T", + "2016-Dec-01T00:00", + "12-05-2016T00:00", + "datetime-local" + ]; + + for (value of testData) { + element.setAttribute('value', value); + delayed_is(element.value, sanitizeValue(type, value), + "The value has not been correctly sanitized for type=" + type); + delayed_is(element.getAttribute('value'), value, + "The content value should not have been sanitized"); + + if (type in valueModeValue) { + element.setAttribute('value', 'tulip'); + element.value = value; + delayed_is(element.value, sanitizeValue(type, value), + "The value has not been correctly sanitized for type=" + type); + delayed_is(element.getAttribute('value'), 'tulip', + "The content value should not have been sanitized"); + } + + element.setAttribute('value', ''); + form.reset(); + element.type = 'checkbox'; // We know this type has no sanitizing algorithm. + element.setAttribute('value', value); + delayed_is(element.value, value, "The value should not have been sanitized"); + element.type = type; + delayed_is(element.value, sanitizeValue(type, value), + "The value has not been correctly sanitized for type=" + type); + delayed_is(element.getAttribute('value'), value, + "The content value should not have been sanitized"); + + element.setAttribute('value', ''); + form.reset(); + element.setAttribute('value', value); + form.reset(); + delayed_is(element.value, sanitizeValue(type, value), + "The value has not been correctly sanitized for type=" + type); + delayed_is(element.getAttribute('value'), value, + "The content value should not have been sanitized"); + + // Cleaning-up. + element.setAttribute('value', ''); + form.reset(); + } + + flushDelayedTests(inputTypeDescription); +} + +for (type of inputTypes) { + var form = document.forms[0]; + var element = document.createElement("input"); + element.style.display = "none"; + element.type = type; + form.appendChild(element); + + checkSanitizing(element, "type=" + type + ", no frame, no editor"); + + element.style.display = ""; + checkSanitizing(element, "type=" + type + ", frame, no editor"); + + element.focus(); + element.blur(); + checkSanitizing(element, "type=" + type + ", frame, editor"); + + element.style.display = "none"; + checkSanitizing(element, "type=" + type + ", no frame, editor"); + + form.removeChild(element); +} + +</script> +</pre> +</body> +</html> |