test_input_sanitization.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. <!DOCTYPE HTML>
  2. <html>
  3. <!--
  4. https://bugzilla.mozilla.org/show_bug.cgi?id=549475
  5. -->
  6. <head>
  7. <title>Test for Bug 549475</title>
  8. <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
  9. <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
  10. </head>
  11. <body>
  12. <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=549475">Mozilla Bug 549475</a>
  13. <p id="display"></p>
  14. <pre id="test">
  15. <div id='content'>
  16. <form>
  17. </form>
  18. </div>
  19. <script type="application/javascript">
  20. SimpleTest.requestLongerTimeout(2);
  21. /**
  22. * This files tests the 'value sanitization algorithm' for the various input
  23. * types. Note that an input's value is affected by more than just its type's
  24. * value sanitization algorithm; e.g. some type=range has actions that the user
  25. * agent must perform to change the element's value to avoid underflow/overflow
  26. * and step mismatch (when possible). We specifically avoid triggering these
  27. * other actions here so that this test only tests the value sanitization
  28. * algorithm for the various input types.
  29. *
  30. * XXXjwatt splitting out testing of the value sanitization algorithm and
  31. * "other things" that affect .value makes it harder to know what we're testing
  32. * and what we've missed, because what's included in the value sanitization
  33. * algorithm and what's not is different from input type to input type. It
  34. * seems to me it would be better to have a test (maybe one per type) focused
  35. * on testing .value for permutations of all other inputs that can affect it.
  36. * The value sanitization algorithm is just an internal spec concept after all.
  37. */
  38. // We buffer up the results of sets of sub-tests, and avoid outputting log
  39. // entries for them all if they all pass. Otherwise, we have an enormous amount
  40. // of test output.
  41. var delayedTests = [];
  42. var anyFailedDelayedTests = false;
  43. function delayed_is(actual, expected, description)
  44. {
  45. var result = actual == expected;
  46. delayedTests.push({ actual: actual, expected: expected, description: description });
  47. if (!result) {
  48. anyFailedDelayedTests = true;
  49. }
  50. }
  51. function flushDelayedTests(description)
  52. {
  53. if (anyFailedDelayedTests) {
  54. info("Outputting individual results for \"" + description + "\" due to failures in subtests");
  55. for (var test of delayedTests) {
  56. is(test.actual, test.expected, test.description);
  57. }
  58. } else {
  59. ok(true, description + " (" + delayedTests.length + " subtests)");
  60. }
  61. delayedTests = [];
  62. anyFailedDelayedTests = false;
  63. }
  64. // We are excluding "file" because it's too different from the other types.
  65. // And it has no sanitizing algorithm.
  66. var inputTypes =
  67. [
  68. "text", "password", "search", "tel", "hidden", "checkbox", "radio",
  69. "submit", "image", "reset", "button", "email", "url", "number", "date",
  70. "time", "range", "color", "month", "week", "datetime-local"
  71. ];
  72. var valueModeValue =
  73. [
  74. "text", "search", "url", "tel", "email", "password", "date", "datetime",
  75. "month", "week", "time", "datetime-local", "number", "range", "color",
  76. ];
  77. function sanitizeDate(aValue)
  78. {
  79. // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
  80. function getNumbersOfDaysInMonth(aMonth, aYear) {
  81. if (aMonth === 2) {
  82. return (aYear % 400 === 0 || (aYear % 100 != 0 && aYear % 4 === 0)) ? 29 : 28;
  83. }
  84. return (aMonth === 1 || aMonth === 3 || aMonth === 5 || aMonth === 7 ||
  85. aMonth === 8 || aMonth === 10 || aMonth === 12) ? 31 : 30;
  86. }
  87. var match = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})$/.exec(aValue);
  88. if (!match) {
  89. return "";
  90. }
  91. var year = Number(match[1]);
  92. if (year === 0) {
  93. return "";
  94. }
  95. var month = Number(match[2]);
  96. if (month > 12 || month < 1) {
  97. return "";
  98. }
  99. var day = Number(match[3]);
  100. return 1 <= day && day <= getNumbersOfDaysInMonth(month, year) ? aValue : "";
  101. }
  102. function sanitizeTime(aValue)
  103. {
  104. // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
  105. var match = /^([0-9]{2}):([0-9]{2})(.*)$/.exec(aValue);
  106. if (!match) {
  107. return "";
  108. }
  109. var hours = match[1];
  110. if (hours < 0 || hours > 23) {
  111. return "";
  112. }
  113. var minutes = match[2];
  114. if (minutes < 0 || minutes > 59) {
  115. return "";
  116. }
  117. var other = match[3];
  118. if (other == "") {
  119. return aValue;
  120. }
  121. match = /^:([0-9]{2})(.*)$/.exec(other);
  122. if (!match) {
  123. return "";
  124. }
  125. var seconds = match[1];
  126. if (seconds < 0 || seconds > 59) {
  127. return "";
  128. }
  129. var other = match[2];
  130. if (other == "") {
  131. return aValue;
  132. }
  133. match = /^.([0-9]{1,3})$/.exec(other);
  134. if (!match) {
  135. return "";
  136. }
  137. return aValue;
  138. }
  139. function sanitizeDateTimeLocal(aValue)
  140. {
  141. // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-local-date-and-time-string
  142. if (aValue.length < 16) {
  143. return "";
  144. }
  145. var separator = aValue[10];
  146. if (separator != "T" && separator != " ") {
  147. return "";
  148. }
  149. var [date, time] = aValue.split(separator);
  150. if (!sanitizeDate(date)) {
  151. return "";
  152. }
  153. if (!sanitizeTime(time)) {
  154. return "";
  155. }
  156. // Normalize datetime-local string.
  157. // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-normalised-local-date-and-time-string
  158. if (separator == " ") {
  159. aValue = date + "T" + time;
  160. }
  161. if (aValue.length == 16) {
  162. return aValue;
  163. }
  164. if (aValue.length > 19) {
  165. var milliseconds = aValue.substring(20);
  166. if (Number(milliseconds) != 0) {
  167. return aValue;
  168. }
  169. aValue = aValue.slice(0, 19);
  170. }
  171. var seconds = aValue.substring(17);
  172. if (Number(seconds) != 0) {
  173. return aValue;
  174. }
  175. aValue = aValue.slice(0, 16);
  176. return aValue;
  177. }
  178. function sanitizeValue(aType, aValue)
  179. {
  180. // http://www.whatwg.org/html/#value-sanitization-algorithm
  181. switch (aType) {
  182. case "text":
  183. case "password":
  184. case "search":
  185. case "tel":
  186. return aValue.replace(/[\n\r]/g, "");
  187. case "url":
  188. case "email":
  189. return aValue.replace(/[\n\r]/g, "").replace(/^[\u0020\u0009\t\u000a\u000c\u000d]+|[\u0020\u0009\t\u000a\u000c\u000d]+$/g, "");
  190. case "number":
  191. return isNaN(Number(aValue)) ? "" : aValue;
  192. case "range":
  193. var defaultMinimum = 0;
  194. var defaultMaximum = 100;
  195. var value = Number(aValue);
  196. if (isNaN(value)) {
  197. return ((defaultMaximum - defaultMinimum)/2).toString(); // "50"
  198. }
  199. if (value < defaultMinimum) {
  200. return defaultMinimum.toString();
  201. }
  202. if (value > defaultMaximum) {
  203. return defaultMaximum.toString();
  204. }
  205. return aValue;
  206. case "date":
  207. return sanitizeDate(aValue);
  208. case "time":
  209. return sanitizeTime(aValue);
  210. case "month":
  211. // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-month-string
  212. var match = /^([0-9]{4,})-([0-9]{2})$/.exec(aValue);
  213. if (!match) {
  214. return "";
  215. }
  216. var year = Number(match[1]);
  217. if (year === 0) {
  218. return "";
  219. }
  220. var month = Number(match[2]);
  221. if (month > 12 || month < 1) {
  222. return "";
  223. }
  224. return aValue;
  225. case "week":
  226. // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-week-string
  227. function isLeapYear(aYear) {
  228. return ((aYear % 4 == 0) && (aYear % 100 != 0)) || (aYear % 400 == 0);
  229. }
  230. function getDayofWeek(aYear, aMonth, aDay) { /* 0 = Sunday */
  231. // Tomohiko Sakamoto algorithm.
  232. var monthTable = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4];
  233. aYear -= Number(aMonth < 3);
  234. return (aYear + parseInt(aYear / 4) - parseInt(aYear / 100) +
  235. parseInt(aYear / 400) + monthTable[aMonth - 1] + aDay) % 7;
  236. }
  237. function getMaximumWeekInYear(aYear) {
  238. var day = getDayofWeek(aYear, 1, 1);
  239. return day == 4 || (day == 3 && isLeapYear(aYear)) ? 53 : 52;
  240. }
  241. var match = /^([0-9]{4,})-W([0-9]{2})$/.exec(aValue);
  242. if (!match) {
  243. return "";
  244. }
  245. var year = Number(match[1]);
  246. if (year === 0) {
  247. return "";
  248. }
  249. var week = Number(match[2]);
  250. if (week > 53 || month < 1) {
  251. return "";
  252. }
  253. return 1 <= week && week <= getMaximumWeekInYear(year) ? aValue : "";
  254. case "datetime-local":
  255. return sanitizeDateTimeLocal(aValue);
  256. case "color":
  257. return /^#[0-9A-Fa-f]{6}$/.exec(aValue) ? aValue.toLowerCase() : "#000000";
  258. default:
  259. return aValue;
  260. }
  261. }
  262. function checkSanitizing(element, inputTypeDescription)
  263. {
  264. var testData =
  265. [
  266. // For text, password, search, tel, email:
  267. "\n\rfoo\n\r",
  268. "foo\n\rbar",
  269. " foo ",
  270. " foo\n\r bar ",
  271. // For url:
  272. "\r\n foobar \n\r",
  273. "\u000B foo \u000B",
  274. "\u000A foo \u000A",
  275. "\u000C foo \u000C",
  276. "\u000d foo \u000d",
  277. "\u0020 foo \u0020",
  278. " \u0009 foo \u0009 ",
  279. // For number and range:
  280. "42",
  281. "13.37",
  282. "1.234567898765432",
  283. "12foo",
  284. "1e2",
  285. "3E42",
  286. // For date:
  287. "1970-01-01",
  288. "1234-12-12",
  289. "1234567890-01-02",
  290. "2012-12-31",
  291. "2012-02-29",
  292. "2000-02-29",
  293. "1234",
  294. "1234-",
  295. "12345",
  296. "1234-01",
  297. "1234-012",
  298. "1234-01-",
  299. "12-12",
  300. "999-01-01",
  301. "1234-56-78-91",
  302. "1234-567-78",
  303. "1234--7-78",
  304. "abcd-12-12",
  305. "thisinotadate",
  306. "2012-13-01",
  307. "1234-12-42",
  308. " 2012-13-01",
  309. " 123-01-01",
  310. "2012- 3-01",
  311. "12- 10- 01",
  312. " 12-0-1",
  313. "2012-3-001",
  314. "2012-12-00",
  315. "2012-12-1r",
  316. "2012-11-31",
  317. "2011-02-29",
  318. "2100-02-29",
  319. "a2000-01-01",
  320. "2000a-01-0'",
  321. "20aa00-01-01",
  322. "2000a2000-01-01",
  323. "2000-1-1",
  324. "2000-1-01",
  325. "2000-01-1",
  326. "2000-01-01 ",
  327. "2000- 01-01",
  328. "-1970-01-01",
  329. "0000-00-00",
  330. "0001-00-00",
  331. "0000-01-01",
  332. "1234-12 12",
  333. "1234 12-12",
  334. "1234 12 12",
  335. // For time:
  336. "1",
  337. "10",
  338. "10:",
  339. "10:1",
  340. "21:21",
  341. ":21:21",
  342. "-21:21",
  343. " 21:21",
  344. "21-21",
  345. "21:21:",
  346. "21:211",
  347. "121:211",
  348. "21:21 ",
  349. "00:00",
  350. "-1:00",
  351. "24:00",
  352. "00:60",
  353. "01:01",
  354. "23:59",
  355. "99:99",
  356. "8:30",
  357. "19:2",
  358. "19:a2",
  359. "4c:19",
  360. "10:.1",
  361. "1.:10",
  362. "13:37:42",
  363. "13:37.42",
  364. "13:37:42 ",
  365. "13:37:42.",
  366. "13:37:61.",
  367. "13:37:00",
  368. "13:37:99",
  369. "13:37:b5",
  370. "13:37:-1",
  371. "13:37:.1",
  372. "13:37:1.",
  373. "13:37:42.001",
  374. "13:37:42.001",
  375. "13:37:42.abc",
  376. "13:37:42.00c",
  377. "13:37:42.a23",
  378. "13:37:42.12e",
  379. "13:37:42.1e1",
  380. "13:37:42.e11",
  381. "13:37:42.1",
  382. "13:37:42.99",
  383. "13:37:42.0",
  384. "13:37:42.00",
  385. "13:37:42.000",
  386. "13:37:42.-1",
  387. "13:37:42.1.1",
  388. "13:37:42.1,1",
  389. "13:37:42.",
  390. "foo12:12",
  391. "13:37:42.100000000000",
  392. // For color
  393. "#00ff00",
  394. "#000000",
  395. "red",
  396. "#0f0",
  397. "#FFFFAA",
  398. "FFAABB",
  399. "fFAaBb",
  400. "FFAAZZ",
  401. "ABCDEF",
  402. "#7654321",
  403. // For month
  404. "1970-01",
  405. "1234-12",
  406. "123456789-01",
  407. "2013-13",
  408. "0000-00",
  409. "2015-00",
  410. "0001-01",
  411. "1-1",
  412. "888-05",
  413. "2013-3",
  414. "2013-may",
  415. "2000-1a",
  416. "2013-03-13",
  417. "december",
  418. "abcdef",
  419. "12",
  420. " 2013-03",
  421. "2013 - 03",
  422. "2013 03",
  423. "2013/03",
  424. // For week
  425. "1970-W01",
  426. "1970-W53",
  427. "1964-W53",
  428. "1900-W10",
  429. "2004-W53",
  430. "2065-W53",
  431. "2099-W53",
  432. "2010-W53",
  433. "2016-W30",
  434. "1900-W3",
  435. "2016-w30",
  436. "2016-30",
  437. "16-W30",
  438. "2016-Week30",
  439. "2000-100",
  440. "0000-W01",
  441. "00-W01",
  442. "123456-W05",
  443. "1985-W100",
  444. "week",
  445. // For datetime-local
  446. "1970-01-01T00:00",
  447. "1970-01-01Z12:00",
  448. "1970-01-01 00:00:00",
  449. "1970-01-01T00:00:00.0",
  450. "1970-01-01T00:00:00.00",
  451. "1970-01-01T00:00:00.000",
  452. "1970-01-01 00:00:00.20",
  453. "1234567-01-01T12:00",
  454. "2016-13-01T12:00",
  455. "2016-12-32T12:00",
  456. "2016-11-08 15:40:30.0",
  457. "2016-11-08T15:40:30.00",
  458. "2016-11-07T17:30:10",
  459. "2016-12-1T12:45",
  460. "2016-12-01T12:45:30.123456",
  461. "2016-12-01T24:00",
  462. "2016-12-01T12:88:30",
  463. "2016-12-01T12:30:99",
  464. "2016-12-01T12:30:100",
  465. "2016-12-01",
  466. "2016-12-01T",
  467. "2016-Dec-01T00:00",
  468. "12-05-2016T00:00",
  469. "datetime-local"
  470. ];
  471. for (value of testData) {
  472. element.setAttribute('value', value);
  473. delayed_is(element.value, sanitizeValue(type, value),
  474. "The value has not been correctly sanitized for type=" + type);
  475. delayed_is(element.getAttribute('value'), value,
  476. "The content value should not have been sanitized");
  477. if (type in valueModeValue) {
  478. element.setAttribute('value', 'tulip');
  479. element.value = value;
  480. delayed_is(element.value, sanitizeValue(type, value),
  481. "The value has not been correctly sanitized for type=" + type);
  482. delayed_is(element.getAttribute('value'), 'tulip',
  483. "The content value should not have been sanitized");
  484. }
  485. element.setAttribute('value', '');
  486. form.reset();
  487. element.type = 'checkbox'; // We know this type has no sanitizing algorithm.
  488. element.setAttribute('value', value);
  489. delayed_is(element.value, value, "The value should not have been sanitized");
  490. element.type = type;
  491. delayed_is(element.value, sanitizeValue(type, value),
  492. "The value has not been correctly sanitized for type=" + type);
  493. delayed_is(element.getAttribute('value'), value,
  494. "The content value should not have been sanitized");
  495. element.setAttribute('value', '');
  496. form.reset();
  497. element.setAttribute('value', value);
  498. form.reset();
  499. delayed_is(element.value, sanitizeValue(type, value),
  500. "The value has not been correctly sanitized for type=" + type);
  501. delayed_is(element.getAttribute('value'), value,
  502. "The content value should not have been sanitized");
  503. // Cleaning-up.
  504. element.setAttribute('value', '');
  505. form.reset();
  506. }
  507. flushDelayedTests(inputTypeDescription);
  508. }
  509. for (type of inputTypes) {
  510. var form = document.forms[0];
  511. var element = document.createElement("input");
  512. element.style.display = "none";
  513. element.type = type;
  514. form.appendChild(element);
  515. checkSanitizing(element, "type=" + type + ", no frame, no editor");
  516. element.style.display = "";
  517. checkSanitizing(element, "type=" + type + ", frame, no editor");
  518. element.focus();
  519. element.blur();
  520. checkSanitizing(element, "type=" + type + ", frame, editor");
  521. element.style.display = "none";
  522. checkSanitizing(element, "type=" + type + ", no frame, editor");
  523. form.removeChild(element);
  524. }
  525. </script>
  526. </pre>
  527. </body>
  528. </html>