| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of dart.dom.html; | 5 part of dart.dom.html; |
| 6 | 6 |
| 7 | 7 |
| 8 /** | 8 /** |
| 9 * Interface used to validate that only accepted elements and attributes are | 9 * Interface used to validate that only accepted elements and attributes are |
| 10 * allowed while parsing HTML strings into DOM nodes. | 10 * allowed while parsing HTML strings into DOM nodes. |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 } | 134 } |
| 135 | 135 |
| 136 | 136 |
| 137 class _ThrowsNodeValidator implements NodeValidator { | 137 class _ThrowsNodeValidator implements NodeValidator { |
| 138 final NodeValidator validator; | 138 final NodeValidator validator; |
| 139 | 139 |
| 140 _ThrowsNodeValidator(this.validator) {} | 140 _ThrowsNodeValidator(this.validator) {} |
| 141 | 141 |
| 142 bool allowsElement(Element element) { | 142 bool allowsElement(Element element) { |
| 143 if (!validator.allowsElement(element)) { | 143 if (!validator.allowsElement(element)) { |
| 144 throw new ArgumentError(element._safeTagName); | 144 throw new ArgumentError(Element._safeTagName(element)); |
| 145 } | 145 } |
| 146 return true; | 146 return true; |
| 147 } | 147 } |
| 148 | 148 |
| 149 bool allowsAttribute(Element element, String attributeName, String value) { | 149 bool allowsAttribute(Element element, String attributeName, String value) { |
| 150 if (!validator.allowsAttribute(element, attributeName, value)) { | 150 if (!validator.allowsAttribute(element, attributeName, value)) { |
| 151 throw new ArgumentError('${element._safeTagName}[$attributeName="$value"]'
); | 151 throw new ArgumentError('${Element._safeTagName(element)}[$attributeName="
$value"]'); |
| 152 } | 152 } |
| 153 } | 153 } |
| 154 } | 154 } |
| 155 | 155 |
| 156 | 156 |
| 157 /** | 157 /** |
| 158 * Standard tree sanitizer which validates a node tree against the provided | 158 * Standard tree sanitizer which validates a node tree against the provided |
| 159 * validator and removes any nodes or attributes which are not allowed. | 159 * validator and removes any nodes or attributes which are not allowed. |
| 160 */ | 160 */ |
| 161 class _ValidatingTreeSanitizer implements NodeTreeSanitizer { | 161 class _ValidatingTreeSanitizer implements NodeTreeSanitizer { |
| (...skipping 21 matching lines...) Expand all Loading... |
| 183 // or is the fragment, so ask it to remove the child. And if that fails | 183 // or is the fragment, so ask it to remove the child. And if that fails |
| 184 // try to set the outer html. | 184 // try to set the outer html. |
| 185 if (parent == null) { | 185 if (parent == null) { |
| 186 node.remove(); | 186 node.remove(); |
| 187 } else { | 187 } else { |
| 188 parent._removeChild(node); | 188 parent._removeChild(node); |
| 189 } | 189 } |
| 190 } | 190 } |
| 191 | 191 |
| 192 /// Sanitize the element, assuming we can't trust anything about it. | 192 /// Sanitize the element, assuming we can't trust anything about it. |
| 193 void _sanitizeUntrustedElement(Element element, Node parent) { | 193 void _sanitizeUntrustedElement(/* Element */ element, Node parent) { |
| 194 // If the _hasCorruptedAttributes does not successfully return false, | 194 // If the _hasCorruptedAttributes does not successfully return false, |
| 195 // then we consider it corrupted and remove. | 195 // then we consider it corrupted and remove. |
| 196 // TODO(alanknight): This is a workaround because on Firefox | 196 // TODO(alanknight): This is a workaround because on Firefox |
| 197 // embed/object | 197 // embed/object |
| 198 // tags typeof is "function", not "object". We don't recognize them, and | 198 // tags typeof is "function", not "object". We don't recognize them, and |
| 199 // can't call methods. This does mean that you can't explicitly allow an | 199 // can't call methods. This does mean that you can't explicitly allow an |
| 200 // embed tag. The only thing that will let it through is a null | 200 // embed tag. The only thing that will let it through is a null |
| 201 // sanitizer that doesn't traverse the tree at all. But sanitizing while | 201 // sanitizer that doesn't traverse the tree at all. But sanitizing while |
| 202 // allowing embeds seems quite unlikely. | 202 // allowing embeds seems quite unlikely. This is also the reason that we |
| 203 // can't declare the type of element, as an embed won't pass any type |
| 204 // check in dart2js. |
| 203 var corrupted = true; | 205 var corrupted = true; |
| 204 var attrs; | 206 var attrs; |
| 205 var isAttr; | 207 var isAttr; |
| 206 try { | 208 try { |
| 207 // If getting/indexing attributes throws, count that as corrupt. | 209 // If getting/indexing attributes throws, count that as corrupt. |
| 208 attrs = element.attributes; | 210 attrs = element.attributes; |
| 209 isAttr = attrs['is']; | 211 isAttr = attrs['is']; |
| 210 corrupted = Element._hasCorruptedAttributes(element); | 212 // On IE, erratically, the hasCorruptedAttributes test can return false, |
| 213 // even though it clearly is corrupted. A separate copy of the test |
| 214 // inlining just the basic check seems to help. |
| 215 var corruptedTest1 = Element._hasCorruptedAttributes(element); |
| 216 var corruptedTest2 = JS('bool', r'!(#.attributes instanceof NamedNodeMap)'
, element); |
| 217 corrupted = corruptedTest1 || corruptedTest2; |
| 211 } catch(e) {} | 218 } catch(e) {} |
| 212 var elementText = 'element unprintable'; | 219 var elementText = 'element unprintable'; |
| 213 try { | 220 try { |
| 214 elementText = element.toString(); | 221 elementText = element.toString(); |
| 215 } catch(e) {} | 222 } catch(e) {} |
| 216 var elementTagName = element._safeTagName; | 223 try { |
| 217 _sanitizeElement(element, parent, corrupted, elementText, elementTagName, | 224 var elementTagName = Element._safeTagName(element); |
| 218 attrs, isAttr); | 225 _sanitizeElement(element, parent, corrupted, elementText, elementTagName, |
| 226 attrs, isAttr); |
| 227 } on ArgumentError { // Thrown by _ThrowsNodeValidator |
| 228 rethrow; |
| 229 } catch(e) { // Unexpected exception sanitizing -> remove |
| 230 _removeNode(element, parent); |
| 231 window.console.warn('Removing corrupted element $elementText'); |
| 232 } |
| 219 } | 233 } |
| 220 | 234 |
| 221 /// Having done basic sanity checking on the element, and computed the | 235 /// Having done basic sanity checking on the element, and computed the |
| 222 /// important attributes we want to check, remove it if it's not valid | 236 /// important attributes we want to check, remove it if it's not valid |
| 223 /// or not allowed, either as a whole or particular attributes. | 237 /// or not allowed, either as a whole or particular attributes. |
| 224 void _sanitizeElement(Element element, Node parent, bool corrupted, | 238 void _sanitizeElement(Element element, Node parent, bool corrupted, |
| 225 String text, String tag, Map attrs, String isAttr) { | 239 String text, String tag, Map attrs, String isAttr) { |
| 226 if (false != corrupted) { | 240 if (false != corrupted) { |
| 241 _removeNode(element, parent); |
| 227 window.console.warn( | 242 window.console.warn( |
| 228 'Removing element due to corrupted attributes on <$text>'); | 243 'Removing element due to corrupted attributes on <$text>'); |
| 229 _removeNode(element, parent); | |
| 230 return; | 244 return; |
| 231 } | 245 } |
| 232 if (!validator.allowsElement(element)) { | 246 if (!validator.allowsElement(element)) { |
| 247 _removeNode(element, parent); |
| 233 window.console.warn( | 248 window.console.warn( |
| 234 'Removing disallowed element <$tag>'); | 249 'Removing disallowed element <$tag> from $parent'); |
| 235 _removeNode(element, parent); | |
| 236 return; | 250 return; |
| 237 } | 251 } |
| 238 | 252 |
| 239 if (isAttr != null) { | 253 if (isAttr != null) { |
| 240 if (!validator.allowsAttribute(element, 'is', isAttr)) { | 254 if (!validator.allowsAttribute(element, 'is', isAttr)) { |
| 255 _removeNode(element, parent); |
| 241 window.console.warn('Removing disallowed type extension ' | 256 window.console.warn('Removing disallowed type extension ' |
| 242 '<$tag is="$isAttr">'); | 257 '<$tag is="$isAttr">'); |
| 243 _removeNode(element, parent); | |
| 244 return; | 258 return; |
| 245 } | 259 } |
| 246 } | 260 } |
| 247 | 261 |
| 248 // TODO(blois): Need to be able to get all attributes, irrespective of | 262 // TODO(blois): Need to be able to get all attributes, irrespective of |
| 249 // XMLNS. | 263 // XMLNS. |
| 250 var keys = attrs.keys.toList(); | 264 var keys = attrs.keys.toList(); |
| 251 for (var i = attrs.length - 1; i >= 0; --i) { | 265 for (var i = attrs.length - 1; i >= 0; --i) { |
| 252 var name = keys[i]; | 266 var name = keys[i]; |
| 253 if (!validator.allowsAttribute(element, name.toLowerCase(), | 267 if (!validator.allowsAttribute(element, name.toLowerCase(), |
| (...skipping 19 matching lines...) Expand all Loading... |
| 273 case Node.COMMENT_NODE: | 287 case Node.COMMENT_NODE: |
| 274 case Node.DOCUMENT_FRAGMENT_NODE: | 288 case Node.DOCUMENT_FRAGMENT_NODE: |
| 275 case Node.TEXT_NODE: | 289 case Node.TEXT_NODE: |
| 276 case Node.CDATA_SECTION_NODE: | 290 case Node.CDATA_SECTION_NODE: |
| 277 break; | 291 break; |
| 278 default: | 292 default: |
| 279 _removeNode(node, parent); | 293 _removeNode(node, parent); |
| 280 } | 294 } |
| 281 } | 295 } |
| 282 } | 296 } |
| OLD | NEW |