| <!DOCTYPE html> | |
| <title>CSS Values and Units Test: attr() security limitations</title> | |
| <link rel="help" href="https://drafts.csswg.org/css-values-5/#attr-security"> | |
| <script src="/resources/testharness.js"></script> | |
| <script src="/resources/testharnessreport.js"></script> | |
| <style> | |
| @property --some-string { | |
| syntax: "<string>"; | |
| inherits: false; | |
| initial-value: "empty"; | |
| } | |
| @property --some-string-list { | |
| syntax: "<string>+"; | |
| inherits: false; | |
| initial-value: "empty"; | |
| } | |
| div { | |
| --some-string: attr(data-foo); | |
| --some-string-list: "https://does-not-exist-2.test/404.png" attr(data-foo); | |
| --some-other-url: attr(data-foo); | |
| --image-set-valid: url("https://does-not-exist.test/404.png") type(attr(data-foo)); | |
| --image-set-invalid: attr(data-foo type(<url>)) 1x; | |
| --url-after-gradients: linear-gradient(black, white), linear-gradient(black, white), image-set(attr(data-foo)); | |
| } | |
| </style> | |
| <html> | |
| <body> | |
| <div id="attr"></div> | |
| </body> | |
| </html> | |
| <script> | |
| function test_attr(property, attrString, attrValue, expectedValue) { | |
| var elem = document.getElementById("attr"); | |
| elem.setAttribute("data-foo", attrValue); | |
| elem.style.setProperty("--unregistered", attrString); | |
| let value = window.getComputedStyle(elem).getPropertyValue("--unregistered"); | |
| test(() => { | |
| // Skip tests that include unsupported functions, since they are not violation of attr() security. | |
| if (value == "" || CSS.supports(`${property} : ${value}`)) { | |
| elem.style.setProperty(property, attrString); | |
| assert_equals(window.getComputedStyle(elem).getPropertyValue(property), | |
| expectedValue); | |
| } | |
| }, `'${property}: ${attrString}' with data-foo="${attrValue}"`); | |
| elem.style.setProperty(property, null); | |
| } | |
| function test_attr_shorthand(shorthand, longhand, attrString, attrValue, expectedValue) { | |
| var elem = document.getElementById("attr"); | |
| elem.setAttribute("data-foo", attrValue); | |
| elem.style.setProperty(shorthand, attrString); | |
| test(() => { | |
| assert_equals(window.getComputedStyle(elem).getPropertyValue(longhand), | |
| expectedValue); | |
| }, `'${shorthand}: ${attrString}' with data-foo="${attrValue}"`); | |
| elem.style.setProperty(shorthand, null); | |
| } | |
| function test_registered_custom_property(customPropertyName, customPropertySyntax, customPropertyInitialValue, | |
| attrValue, expectedValue) { | |
| window.CSS.registerProperty({ | |
| name: customPropertyName, | |
| syntax: customPropertySyntax, | |
| inherits: false, | |
| initialValue: customPropertyInitialValue, | |
| }); | |
| var elem = document.getElementById("attr"); | |
| elem.setAttribute("data-foo", attrValue); | |
| var attrString = "attr(data-foo type(" + customPropertySyntax + "))"; | |
| elem.style.setProperty(customPropertyName, attrString); | |
| test(() => { | |
| assert_equals(window.getComputedStyle(elem).getPropertyValue(customPropertyName), | |
| expectedValue); | |
| }, `'${customPropertyName}: ${attrString}' with data-foo="${attrValue}"`); | |
| elem.style.setProperty(customPropertyName, null); | |
| } | |
| const url = "https://does-not-exist.test/404.png"; | |
| const url2 = "https://does-not-exist-2.test/404.png"; | |
| // Direct use. | |
| test_attr('--x', | |
| `image-set(attr(data-foo))`, | |
| url, | |
| `image-set("${url}")`); | |
| test_attr('background-image', | |
| `image-set(attr(data-foo))`, | |
| url, | |
| 'none'); | |
| test_attr_shorthand('background', | |
| 'background-image', | |
| `image-set(attr(data-foo))`, | |
| url, | |
| 'none'); | |
| test_attr('background-image', | |
| `image-set("${url}")`, | |
| url, | |
| `image-set(url("${url}") 1dppx)`); | |
| test_attr('--x', | |
| `src(attr(data-foo))`, | |
| url, | |
| `src("${url}")`); | |
| test_attr('background-image', | |
| `src(attr(data-foo))`, | |
| url, | |
| `none`); | |
| test_attr('background-image', | |
| `src("${url}")`, | |
| url, | |
| `src(url("${url}"))`); | |
| // The following string() function is under discussion in the working group and does not exist yet. | |
| test_attr('--x', | |
| `src(string("https://does-not-exist.test" attr(data-foo)))`, | |
| '/404.png', | |
| `src(string("https://does-not-exist.test" "/404.png"))`); | |
| test_attr('background-image', | |
| 'src(string("https://does-not-exist.test" attr(data-foo)))', | |
| '/404.png', | |
| 'none'); | |
| test_attr('background-image', | |
| 'src(string("https://does-not-exist.test/""404.png"))', | |
| '/404.png', | |
| `src(url("${url}"))`); | |
| test_attr('background-image', | |
| `attr(data-foo type(<url>))`, | |
| `url(${url})`, | |
| `none`); | |
| test_attr('background-image', | |
| `url("${url}")`, | |
| `url(${url})`, | |
| `url("${url}")`); | |
| test_attr('--x', | |
| `image(attr(data-foo))`, | |
| url, | |
| `image("${url}")`); | |
| test_attr('background-image', | |
| `image(attr(data-foo))`, | |
| url, | |
| 'none'); | |
| test_attr('background-image', | |
| `image("${url}")`, | |
| url, | |
| `image(url("${url}"))`); | |
| test_attr('background-image', | |
| `url(${url}), attr(data-foo type(<image>))`, | |
| `linear-gradient(#000000, #ffffff)`, | |
| `url("${url}"), linear-gradient(rgb(0, 0, 0), rgb(255, 255, 255))`); | |
| // The remaining tests use image-set(), but should be equivalent for image() etc. | |
| // Test in a fallback. | |
| test_attr('--x', | |
| `image-set(var(--y, attr(data-foo)))`, | |
| url, | |
| `image-set("${url}")`); | |
| test_attr('background-image', | |
| `image-set(var(--y, attr(data-foo)))`, | |
| url, | |
| 'none'); | |
| // Test via a registered custom property. | |
| test_attr('--x', | |
| `image-set(var(--some-string))`, | |
| url, | |
| `image-set("${url}")`); | |
| test_attr('background-image', | |
| `image-set(var(--some-string))`, | |
| url, | |
| 'none'); | |
| // Test via a registered custom property (list). | |
| test_attr('--x', | |
| `image-set(var(--some-string-list))`, | |
| url, | |
| `image-set("${url2}" "${url}")`); | |
| test_attr('background-image', | |
| `image-set(var(--some-string-list))`, | |
| url, | |
| 'none'); | |
| test_registered_custom_property('--registered-url', '<url>', `url("${url2}")`, '${url}', `url("${url2}")`); | |
| test_registered_custom_property('--registered-color', '<color>', 'red', 'blue', 'rgb(0, 0, 255)'); | |
| // Test via a non-registered custom property. | |
| test_attr('--x', | |
| `image-set(var(--some-other-url))`, | |
| url, | |
| `image-set("${url}")`); | |
| test_attr('background-image', | |
| `image-set(var(--some-other-url))`, | |
| url, | |
| 'none'); | |
| // Test multiple token substitution | |
| test_attr('background-image', | |
| `attr(data-foo type(*))`, | |
| `url(${url}), linear-gradient(black, white)`, | |
| 'none'); | |
| // Test total attr()-tainting for substitution values | |
| test_attr('background-image', | |
| `image-set(var(--image-set-valid))`, | |
| 'image/jpeg', | |
| 'none'); | |
| test_attr('background-image', | |
| `image-set(var(--image-set-invalid))`, | |
| url, | |
| 'none'); | |
| // Verify attr()-tainting is relative to the substituted value. | |
| test_attr('background-image', | |
| `attr(data-foo type(*))`, | |
| `linear-gradient(black, white), url(${url})`, | |
| 'none'); | |
| test_attr('background-image', | |
| `var(--url-after-gradients)`, | |
| url, | |
| 'none'); | |
| test_attr('background-image', | |
| `var(--undefined, var(--url-after-gradients))`, | |
| url, | |
| 'none'); | |
| </script> |