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