sanitizer-config.tentative.html (17722B)
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 </head> 7 <body> 8 <script> 9 test(t => { 10 let s = new Sanitizer(); 11 assert_true(s instanceof Sanitizer); 12 }, "Sanitizer constructor without config."); 13 14 test(t => { 15 let s = new Sanitizer({}); 16 assert_true(s instanceof Sanitizer); 17 }, "Sanitizer constructor with empty config."); 18 19 test(t => { 20 let s = new Sanitizer(null); 21 assert_true(s instanceof Sanitizer); 22 }, "Sanitizer constructor with null as config."); 23 24 test(t => { 25 let s = new Sanitizer(undefined); 26 assert_true(s instanceof Sanitizer); 27 }, "Sanitizer constructor with undefined as config."); 28 29 test(t => { 30 let s = new Sanitizer({testConfig: [1,2,3], attr: ["test", "i", "am"]}); 31 assert_true(s instanceof Sanitizer); 32 }, "Sanitizer constructor with config ignore unknown values."); 33 34 test(t => { 35 assert_false(new Sanitizer().get().comments); 36 assert_true(new Sanitizer({}).get().comments); 37 assert_true(new Sanitizer({comments: true}).get().comments); 38 assert_false(new Sanitizer({comments: false}).get().comments); 39 40 let s = new Sanitizer(); 41 s.setComments(true); 42 assert_true(s.get().comments); 43 s.setComments(false); 44 assert_false(s.get().comments); 45 s.setComments("abc"); 46 assert_true(s.get().comments); 47 }, "SanitizerConfig comments field."); 48 49 test(t => { 50 assert_false(new Sanitizer().get().dataAttributes); 51 assert_true(new Sanitizer({ attributes: [] }).get().dataAttributes); 52 assert_false('dataAttributes' in new Sanitizer({}).get()); 53 assert_false('dataAttributes' in new Sanitizer({ removeAttributes: [] }).get()); 54 assert_true(new Sanitizer({ attributes: [], dataAttributes: true}).get().dataAttributes); 55 assert_false(new Sanitizer({ attributes: [], dataAttributes: false}).get().dataAttributes); 56 57 let s = new Sanitizer(); 58 s.setDataAttributes(true); 59 assert_true(s.get().dataAttributes); 60 s.setDataAttributes(false); 61 assert_false(s.get().dataAttributes); 62 s.setDataAttributes("abc"); 63 assert_true(s.get().dataAttributes); 64 }, "SanitizerConfig dataAttributes field."); 65 66 function assert_object_equals(a, b, description) { 67 assert_equals(JSON.stringify(a), JSON.stringify(b), description); 68 } 69 70 function test_normalization(key, value, expected) { 71 test(t => { 72 let config = Object.fromEntries([[key, [value]]]); 73 let s = new Sanitizer(config); 74 assert_equals(s.get()[key].length, 1); 75 assert_object_equals(s.get()[key][0], expected); 76 }, `SanitizerConfig, normalization: ${key}: [${JSON.stringify(value)}]`); 77 } 78 79 for (key of ["elements", "removeElements", "replaceWithChildrenElements"]) { 80 // The canonical form of elements always includes an empty removeAttributes list. 81 let extra = key == "elements" ? {removeAttributes: []} : {}; 82 83 test_normalization(key, 84 "div", 85 {name: "div", namespace: "http://www.w3.org/1999/xhtml", ...extra}); 86 test_normalization(key, 87 {name: "b"}, 88 {name: "b", namespace: "http://www.w3.org/1999/xhtml", ...extra}); 89 test_normalization(key, 90 {name: "b", namespace: null}, 91 {name: "b", namespace: null, ...extra}); 92 test_normalization(key, 93 {name: "b", namespace: ""}, 94 {name: "b", namespace: null, ...extra}); 95 test_normalization(key, 96 {name: "p", namespace: "http://www.w3.org/1999/xhtml"}, 97 {name: "p", namespace: "http://www.w3.org/1999/xhtml", ...extra}); 98 test_normalization(key, 99 {name: "bla", namespace: "http://fantasy.org/namespace"}, 100 {name: "bla", namespace: "http://fantasy.org/namespace", ...extra}); 101 } 102 for (key of ["attributes", "removeAttributes"]) { 103 test_normalization(key, 104 "href", 105 {name: "href", namespace: null}); 106 test_normalization(key, 107 {name: "href", namespace: null}, 108 {name: "href", namespace: null}); 109 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name, step 5 110 test_normalization(key, 111 {name: "href", namespace: ""}, 112 {name: "href", namespace: null}); 113 test_normalization(key, 114 {name: "href", namespace: "https://www.w3.org/1999/xlink"}, 115 {name: "href", namespace: "https://www.w3.org/1999/xlink"}); 116 } 117 118 test(t => { 119 let s = new Sanitizer({elements: ["div", "p"]}); 120 assert_equals(s.get().elements.length, 2); 121 s.allowElement("bla"); 122 assert_equals(s.get().elements.length, 3); 123 s.removeElement({name: "div"}); 124 assert_equals(s.get().elements.length, 2); 125 s.replaceElementWithChildren({name: "p", namespace: "http://www.w3.org/1999/xhtml"}); 126 assert_equals(s.get().elements.length, 1); 127 assert_object_equals(s.get().elements[0], 128 {name: "bla", namespace: "http://www.w3.org/1999/xhtml", removeAttributes: []}); 129 }, "Test elements addition."); 130 131 test(t => { 132 let s = new Sanitizer({removeElements: ["div", "p"]}); 133 assert_equals(s.get().removeElements.length, 2); 134 s.removeElement("bla"); 135 assert_equals(s.get().removeElements.length, 3); 136 s.replaceElementWithChildren({name: "div"}); 137 assert_equals(s.get().removeElements.length, 2); 138 s.allowElement({name: "p", namespace: "http://www.w3.org/1999/xhtml"}); 139 assert_equals(s.get().removeElements.length, 1); 140 assert_object_equals(s.get().removeElements[0], 141 {name: "bla", namespace: "http://www.w3.org/1999/xhtml"}); 142 }, "Test elements removal."); 143 144 test(t => { 145 let s = new Sanitizer({replaceWithChildrenElements: ["div", "p"]}); 146 assert_equals(s.get().replaceWithChildrenElements.length, 2); 147 s.replaceElementWithChildren("bla"); 148 assert_equals(s.get().replaceWithChildrenElements.length, 3); 149 s.allowElement({name: "div"}); 150 assert_equals(s.get().replaceWithChildrenElements.length, 2); 151 s.removeElement({name: "p", namespace: "http://www.w3.org/1999/xhtml"}); 152 assert_equals(s.get().replaceWithChildrenElements.length, 1); 153 assert_object_equals(s.get().replaceWithChildrenElements[0], 154 {name: "bla", namespace: "http://www.w3.org/1999/xhtml"}); 155 }, "Test elements replacewithchildren."); 156 157 test(t => { 158 let s = new Sanitizer({attributes: ["href", "src"]}); 159 assert_equals(s.get().attributes.length, 2); 160 s.allowAttribute("id"); 161 assert_equals(s.get().attributes.length, 3); 162 s.removeAttribute({name: "href", namespace: "https://www.w3.org/1999/xlink" }); 163 assert_equals(s.get().attributes.length, 3); 164 s.removeAttribute({name: "href"}); 165 assert_equals(s.get().attributes.length, 2); 166 s.removeAttribute({name: "src", namespace: null}); 167 assert_equals(s.get().attributes.length, 1); 168 assert_object_equals(s.get().attributes[0], 169 {name: "id", namespace: null}); 170 }, "Test attribute addition."); 171 172 test(t => { 173 let s = new Sanitizer({removeAttributes: ["href", "src"]}); 174 assert_equals(s.get().removeAttributes.length, 2); 175 s.removeAttribute("id"); 176 assert_equals(s.get().removeAttributes.length, 3); 177 s.allowAttribute({name: "href", namespace: "https://www.w3.org/1999/xlink" }); 178 assert_equals(s.get().removeAttributes.length, 3); 179 s.allowAttribute({name: "href"}); 180 assert_equals(s.get().removeAttributes.length, 2); 181 s.allowAttribute({name: "src", namespace: null}); 182 assert_equals(s.get().removeAttributes.length, 1); 183 assert_object_equals(s.get().removeAttributes[0], 184 {name: "id", namespace: null}); 185 }, "Test attribute removal."); 186 187 test(t => { 188 let s = new Sanitizer({elements: [{name: "div", attributes: ["href", "src"]}]}); 189 assert_equals(s.get().elements.length, 1); 190 assert_true("attributes" in s.get().elements[0]); 191 assert_false("removeAttributes" in s.get().elements[0]); 192 assert_equals(s.get().elements[0].attributes.length, 2); 193 194 s.allowElement({name: "div", namespace: "http://www.w3.org/1999/xhtml", 195 attributes: ["class"]}); 196 assert_equals(s.get().elements[0].attributes.length, 1); 197 assert_object_equals(s.get().elements[0].attributes[0], 198 { name: "class", namespace: null }); 199 }, "Test attribute-per-element sets (i.e. overwrites)."); 200 201 test(t => { 202 let s = new Sanitizer({elements: [{name: "div", removeAttributes: ["href", "src"]}]}); 203 assert_equals(s.get().elements.length, 1); 204 assert_false("attributes" in s.get().elements[0]); 205 assert_true("removeAttributes" in s.get().elements[0]); 206 assert_equals(s.get().elements[0].removeAttributes.length, 2); 207 208 s.allowElement({name: "div", namespace: "http://www.w3.org/1999/xhtml", 209 removeAttributes: ["class"]}); 210 assert_equals(s.get().elements[0].removeAttributes.length, 1); 211 assert_object_equals(s.get().elements[0].removeAttributes[0], 212 { name: "class", namespace: null }); 213 }, "Test removeAttribute-per-element sets (i.e. overwrites)."); 214 215 // Tests for valid/invalid config parameter combinations. 216 // 1. The config has either an elements or a removeElements key, but not both. 217 test(() => { 218 assert_throws_js(TypeError, () => { 219 new Sanitizer({ elements: [], removeElements: [] }); 220 }); 221 }, "Both elements and removeElements should not be allowed."); 222 223 // 2. The config has either an attributes or a removeAttributes key, but not both. 224 test(() => { 225 assert_throws_js(TypeError, () => { 226 new Sanitizer({ attributes: [], removeAttributes: [] }); 227 }); 228 }, "Both attributes and removeAttributes should not be allowed."); 229 230 // 3. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace, and SanitizerAttributeNamespace items in config are canonical, meaning they have been run through canonicalize a sanitizer element or canonicalize a sanitizer attribute, as appropriate. 231 // This is tested in the sanitizer-config test file. 232 233 // 4. None of config[elements], config[removeElements], config[replaceWithChildrenElements], config[attributes], or config[removeAttributes], if they exist, has duplicates. 234 const DUPLICATE_NAMES = [ 235 ["", ""], 236 ["abc", "abc"], 237 ["data-xyz", "data-xyz"], 238 ["abc", {name: "abc"}], 239 [{name: "abc", namespace: "xyz"}, {name: "abc", namespace: "xyz"}], 240 [{name: "abc", namespace: ""}, {name: "abc", namespace: null}] 241 ]; 242 243 // NOTE: Elements and attributes have different default namespaces. 244 const DUPLICATE_ELEMENT_NAMES = DUPLICATE_NAMES.concat([ 245 ["abc", {name: "abc", namespace: "http://www.w3.org/1999/xhtml"}], 246 [{name: "abc"}, {name: "abc", namespace: "http://www.w3.org/1999/xhtml"}] 247 ]); 248 249 const DUPLICATE_ATTRIBUTE_NAMES = DUPLICATE_NAMES.concat([ 250 ["abc", {name: "abc", namespace: null}], 251 [{name: "abc"}, {name: "abc", namespace: null}] 252 ]); 253 254 test(() => { 255 for (let names of DUPLICATE_ELEMENT_NAMES) { 256 assert_throws_js(TypeError, () => { 257 new Sanitizer({ elements: names }); 258 }); 259 } 260 }, "config[elements] should not allow duplicates"); 261 262 test(() => { 263 for (let names of DUPLICATE_ELEMENT_NAMES) { 264 assert_throws_js(TypeError, () => { 265 new Sanitizer({ removeElements: names }); 266 }); 267 } 268 }, "config[removeElements] should not allow duplicates"); 269 270 test(() => { 271 for (let names of DUPLICATE_ELEMENT_NAMES) { 272 assert_throws_js(TypeError, () => { 273 new Sanitizer({ replaceWithChildrenElements: names }); 274 }); 275 } 276 }, "config[replaceWithChildrenElements] should not allow duplicates"); 277 278 test(() => { 279 for (let names of DUPLICATE_ATTRIBUTE_NAMES) { 280 assert_throws_js(TypeError, () => { 281 new Sanitizer({ attributes: names }); 282 }); 283 } 284 }, "config[attributes] should not allow duplicates"); 285 286 test(() => { 287 for (let names of DUPLICATE_ATTRIBUTE_NAMES) { 288 assert_throws_js(TypeError, () => { 289 new Sanitizer({ removeAttributes: names }); 290 }); 291 } 292 }, "config[removeAttributes] should not allow duplicates"); 293 294 // 5. If both config[elements] and config[replaceWithChildrenElements] exist, then the intersection of config[elements] and config[replaceWithChildrenElements] is empty. 295 test(() => { 296 for (let [a, b] of DUPLICATE_ELEMENT_NAMES) { 297 assert_throws_js(TypeError, () => { 298 new Sanitizer({ 299 elements: [a], 300 replaceWithChildrenElements: [b], 301 }); 302 }); 303 } 304 }, "config[elements] and config[replaceWithChildrenElements] should not intersect"); 305 306 // 6. If both config[removeElements] and config[replaceWithChildrenElements] exist, then the intersection of config[removeElements] and config[replaceWithChildrenElements] is empty. 307 test(() => { 308 for (let [a, b] of DUPLICATE_ELEMENT_NAMES) { 309 assert_throws_js(TypeError, () => { 310 new Sanitizer({ 311 removeElements: [a], 312 replaceWithChildrenElements: [b], 313 }); 314 }); 315 } 316 }, "config[removeElements] and config[replaceWithChildrenElements] should not intersect"); 317 318 // 7. If config[attributes] exists: 319 // 7.1. If config[elements] exists: 320 // 7.1.1. For each element of config[elements]: 321 // 7.1.1.1. Neither element[attributes] nor element[removeAttributes], if they exist, has duplicates. 322 test(() => { 323 for (let names of DUPLICATE_ATTRIBUTE_NAMES) { 324 assert_throws_js(TypeError, () => { 325 new Sanitizer({ 326 attributes: [], 327 elements: [{ name: "div", attributes: names }], 328 }); 329 }); 330 } 331 }, "Duplicates in element[attributes] with config[attributes] should not be allowed."); 332 333 test(() => { 334 for (let names of DUPLICATE_ATTRIBUTE_NAMES) { 335 assert_throws_js(TypeError, () => { 336 new Sanitizer({ 337 attributes: [], 338 elements: [{ name: "div", removeAttributes: names }], 339 }); 340 }); 341 } 342 }, "Duplicates in element[removeAttributes] with config[attributes] should not be allowed."); 343 344 // 7.1.1.2. The intersection of config[attributes] and element[attributes] with default « » is empty. 345 test(() => { 346 for (let [a, b] of DUPLICATE_ATTRIBUTE_NAMES) { 347 assert_throws_js(TypeError, () => { 348 new Sanitizer({ 349 attributes: [a], 350 elements: [{ name: "div", attributes: [b] }], 351 }); 352 }); 353 } 354 }, "config[attributes] and element[attributes] should not intersect."); 355 356 // 7.1.1.3. element[removeAttributes] with default « » is a subset of config[attributes]. 357 test(() => { 358 assert_throws_js(TypeError, () => { 359 new Sanitizer({ 360 attributes: ["class"], 361 elements: [{ name: "div", removeAttributes: ["title"] }], 362 }); 363 }); 364 }, "element[removeAttributes] should be a subset of config[attributes]"); 365 366 // 7.1.1.4. If dataAttributes exists and dataAttributes is true: 367 // 7.1.1.4.1. element[attributes] does not contain a custom data attribute. 368 test(() => { 369 assert_throws_js(TypeError, () => { 370 new Sanitizer({ 371 attributes: [], 372 dataAttributes: true, 373 elements: [{ name: "div", attributes: ["data-foo"] }], 374 }); 375 }); 376 377 assert_throws_js(TypeError, () => { 378 new Sanitizer({ 379 attributes: [], 380 dataAttributes: true, 381 elements: [{ name: "div", attributes: [{ name: "data-bar", namespace: null }] }], 382 }); 383 }); 384 }, "element[attributes] with a data attribute must not co-exist with config[dataAttributes] set to true."); 385 386 // 7.2. If dataAttributes is true: 387 // 7.2.1. config[attributes] does not contain a custom data attribute. 388 test(() => { 389 assert_throws_js(TypeError, () => { 390 new Sanitizer({ 391 attributes: ["data-bar"], 392 dataAttributes: true, 393 }); 394 }); 395 396 assert_throws_js(TypeError, () => { 397 new Sanitizer({ 398 attributes: [{ name: "data-foo", namespace: null }], 399 dataAttributes: true, 400 }); 401 }); 402 }, "config[attributes] with a data attribute must not co-exist with config[dataAttributes] set to true."); 403 404 // 8. If config[removeAttributes] exists: 405 // 8.1. If config[elements] exists, then for each element of config[elements]: 406 // 8.1.1. Not both element[attributes] and element[removeAttributes] exist. 407 test(() => { 408 assert_throws_js(TypeError, () => { 409 new Sanitizer({ 410 removeAttributes: [], 411 elements: [{ name: "div", attributes: [], removeAttributes: [] }], 412 }); 413 }); 414 }, "element[attributes] and element[removeAttributes] should not both exist with config[removeAttributes]."); 415 416 // 8.1.2. Neither element[attributes] nor element[removeAttributes], if they exist, has duplicates. 417 test(() => { 418 for (let names of DUPLICATE_ATTRIBUTE_NAMES) { 419 assert_throws_js(TypeError, () => { 420 new Sanitizer({ 421 removeAttributes: [], 422 elements: [{ name: "div", attributes: names }], 423 }); 424 }); 425 } 426 }, "Duplicates in element[attributes] with config[removeAttributes] should not be allowed."); 427 428 test(() => { 429 for (let names of DUPLICATE_ATTRIBUTE_NAMES) { 430 assert_throws_js(TypeError, () => { 431 new Sanitizer({ 432 removeAttributes: [], 433 elements: [{ name: "div", removeAttributes: names }], 434 }); 435 }); 436 } 437 }, "Duplicates in element[removeAttributes] with config[removeAttributes] should not be allowed."); 438 439 // 8.1.3. The intersection of config[removeAttributes] and element[attributes] with default « » is empty. 440 test(() => { 441 for (let [a, b] of DUPLICATE_ATTRIBUTE_NAMES) { 442 assert_throws_js(TypeError, () => { 443 new Sanitizer({ 444 removeAttributes: [a], 445 elements: [{ name: "div", attributes: [b] }], 446 }); 447 }); 448 } 449 }, "config[removeAttributes] and element[attributes] should not intersect."); 450 451 // 8.1.4. The intersection of config[removeAttributes] and element[removeAttributes] with default « » is empty. 452 test(() => { 453 for (let [a, b] of DUPLICATE_ATTRIBUTE_NAMES) { 454 assert_throws_js(TypeError, () => { 455 new Sanitizer({ 456 removeAttributes: [a], 457 elements: [{ name: "div", removeAttributes: [b] }], 458 }); 459 }); 460 } 461 }, "config[removeAttributes] and element[removeAttributes] should not intersect."); 462 463 // 8.2. config[dataAttributes] does not exist. 464 test(() => { 465 assert_throws_js(TypeError, () => { 466 new Sanitizer({ removeAttributes: [], dataAttributes: true }); 467 }); 468 469 assert_throws_js(TypeError, () => { 470 new Sanitizer({ removeAttributes: [], dataAttributes: false }); 471 }); 472 }, "Can not use config[dataAttributes] and config[removeAttributes] together."); 473 </script> 474 </body> 475 </html>