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 | |
8 /** | 7 /** |
9 * Interface used to validate that only accepted elements and attributes are | 8 * Interface used to validate that only accepted elements and attributes are |
10 * allowed while parsing HTML strings into DOM nodes. | 9 * allowed while parsing HTML strings into DOM nodes. |
11 * | 10 * |
12 * In general, customization of validation behavior should be done via the | 11 * In general, customization of validation behavior should be done via the |
13 * [NodeValidatorBuilder] class to mitigate the chances of incorrectly | 12 * [NodeValidatorBuilder] class to mitigate the chances of incorrectly |
14 * implementing validation rules. | 13 * implementing validation rules. |
15 */ | 14 */ |
16 abstract class NodeValidator { | 15 abstract class NodeValidator { |
17 | |
18 /** | 16 /** |
19 * Construct a default NodeValidator which only accepts whitelisted HTML5 | 17 * Construct a default NodeValidator which only accepts whitelisted HTML5 |
20 * elements and attributes. | 18 * elements and attributes. |
21 * | 19 * |
22 * If a uriPolicy is not specified then the default uriPolicy will be used. | 20 * If a uriPolicy is not specified then the default uriPolicy will be used. |
23 */ | 21 */ |
24 factory NodeValidator({UriPolicy uriPolicy}) => | 22 factory NodeValidator({UriPolicy uriPolicy}) => |
25 new _Html5NodeValidator(uriPolicy: uriPolicy); | 23 new _Html5NodeValidator(uriPolicy: uriPolicy); |
26 | 24 |
27 factory NodeValidator.throws(NodeValidator base) => | 25 factory NodeValidator.throws(NodeValidator base) => |
(...skipping 17 matching lines...) Expand all Loading... |
45 /** | 43 /** |
46 * Performs sanitization of a node tree after construction to ensure that it | 44 * Performs sanitization of a node tree after construction to ensure that it |
47 * does not contain any disallowed elements or attributes. | 45 * does not contain any disallowed elements or attributes. |
48 * | 46 * |
49 * In general custom implementations of this class should not be necessary and | 47 * In general custom implementations of this class should not be necessary and |
50 * all validation customization should be done in custom NodeValidators, but | 48 * all validation customization should be done in custom NodeValidators, but |
51 * custom implementations of this class can be created to perform more complex | 49 * custom implementations of this class can be created to perform more complex |
52 * tree sanitization. | 50 * tree sanitization. |
53 */ | 51 */ |
54 abstract class NodeTreeSanitizer { | 52 abstract class NodeTreeSanitizer { |
55 | |
56 /** | 53 /** |
57 * Constructs a default tree sanitizer which will remove all elements and | 54 * Constructs a default tree sanitizer which will remove all elements and |
58 * attributes which are not allowed by the provided validator. | 55 * attributes which are not allowed by the provided validator. |
59 */ | 56 */ |
60 factory NodeTreeSanitizer(NodeValidator validator) => | 57 factory NodeTreeSanitizer(NodeValidator validator) => |
61 new _ValidatingTreeSanitizer(validator); | 58 new _ValidatingTreeSanitizer(validator); |
62 | 59 |
63 /** | 60 /** |
64 * Called with the root of the tree which is to be sanitized. | 61 * Called with the root of the tree which is to be sanitized. |
65 * | 62 * |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
118 * (such as https://example.com:80). | 115 * (such as https://example.com:80). |
119 */ | 116 */ |
120 class _SameOriginUriPolicy implements UriPolicy { | 117 class _SameOriginUriPolicy implements UriPolicy { |
121 final AnchorElement _hiddenAnchor = new AnchorElement(); | 118 final AnchorElement _hiddenAnchor = new AnchorElement(); |
122 final Location _loc = window.location; | 119 final Location _loc = window.location; |
123 | 120 |
124 bool allowsUri(String uri) { | 121 bool allowsUri(String uri) { |
125 _hiddenAnchor.href = uri; | 122 _hiddenAnchor.href = uri; |
126 // IE leaves an empty hostname for same-origin URIs. | 123 // IE leaves an empty hostname for same-origin URIs. |
127 return (_hiddenAnchor.hostname == _loc.hostname && | 124 return (_hiddenAnchor.hostname == _loc.hostname && |
128 _hiddenAnchor.port == _loc.port && | 125 _hiddenAnchor.port == _loc.port && |
129 _hiddenAnchor.protocol == _loc.protocol) || | 126 _hiddenAnchor.protocol == _loc.protocol) || |
130 (_hiddenAnchor.hostname == '' && | 127 (_hiddenAnchor.hostname == '' && |
131 _hiddenAnchor.port == '' && | 128 _hiddenAnchor.port == '' && |
132 (_hiddenAnchor.protocol == ':' || _hiddenAnchor.protocol == '')); | 129 (_hiddenAnchor.protocol == ':' || _hiddenAnchor.protocol == '')); |
133 } | 130 } |
134 } | 131 } |
135 | 132 |
136 | |
137 class _ThrowsNodeValidator implements NodeValidator { | 133 class _ThrowsNodeValidator implements NodeValidator { |
138 final NodeValidator validator; | 134 final NodeValidator validator; |
139 | 135 |
140 _ThrowsNodeValidator(this.validator) {} | 136 _ThrowsNodeValidator(this.validator) {} |
141 | 137 |
142 bool allowsElement(Element element) { | 138 bool allowsElement(Element element) { |
143 if (!validator.allowsElement(element)) { | 139 if (!validator.allowsElement(element)) { |
144 throw new ArgumentError(Element._safeTagName(element)); | 140 throw new ArgumentError(Element._safeTagName(element)); |
145 } | 141 } |
146 return true; | 142 return true; |
147 } | 143 } |
148 | 144 |
149 bool allowsAttribute(Element element, String attributeName, String value) { | 145 bool allowsAttribute(Element element, String attributeName, String value) { |
150 if (!validator.allowsAttribute(element, attributeName, value)) { | 146 if (!validator.allowsAttribute(element, attributeName, value)) { |
151 throw new ArgumentError('${Element._safeTagName(element)}[$attributeName="
$value"]'); | 147 throw new ArgumentError( |
| 148 '${Element._safeTagName(element)}[$attributeName="$value"]'); |
152 } | 149 } |
153 } | 150 } |
154 } | 151 } |
155 | 152 |
156 | |
157 /** | 153 /** |
158 * Standard tree sanitizer which validates a node tree against the provided | 154 * Standard tree sanitizer which validates a node tree against the provided |
159 * validator and removes any nodes or attributes which are not allowed. | 155 * validator and removes any nodes or attributes which are not allowed. |
160 */ | 156 */ |
161 class _ValidatingTreeSanitizer implements NodeTreeSanitizer { | 157 class _ValidatingTreeSanitizer implements NodeTreeSanitizer { |
162 NodeValidator validator; | 158 NodeValidator validator; |
163 _ValidatingTreeSanitizer(this.validator) {} | 159 _ValidatingTreeSanitizer(this.validator) {} |
164 | 160 |
165 void sanitizeTree(Node node) { | 161 void sanitizeTree(Node node) { |
166 void walk(Node node, Node parent) { | 162 void walk(Node node, Node parent) { |
(...skipping 11 matching lines...) Expand all Loading... |
178 // children of node and, but we have no way of getting to the next | 174 // children of node and, but we have no way of getting to the next |
179 // child, so start again from the last child. | 175 // child, so start again from the last child. |
180 _removeNode(child, node); | 176 _removeNode(child, node); |
181 child = null; | 177 child = null; |
182 nextChild = node.lastChild; | 178 nextChild = node.lastChild; |
183 } | 179 } |
184 if (child != null) walk(child, node); | 180 if (child != null) walk(child, node); |
185 child = nextChild; | 181 child = nextChild; |
186 } | 182 } |
187 } | 183 } |
| 184 |
188 walk(node, null); | 185 walk(node, null); |
189 } | 186 } |
190 | 187 |
191 /// Aggressively try to remove node. | 188 /// Aggressively try to remove node. |
192 void _removeNode(Node node, Node parent) { | 189 void _removeNode(Node node, Node parent) { |
193 // If we have the parent, it's presumably already passed more sanitization | 190 // If we have the parent, it's presumably already passed more sanitization |
194 // or is the fragment, so ask it to remove the child. And if that fails | 191 // or is the fragment, so ask it to remove the child. And if that fails |
195 // try to set the outer html. | 192 // try to set the outer html. |
196 if (parent == null) { | 193 if (parent == null) { |
197 node.remove(); | 194 node.remove(); |
(...skipping 20 matching lines...) Expand all Loading... |
218 var isAttr; | 215 var isAttr; |
219 try { | 216 try { |
220 // If getting/indexing attributes throws, count that as corrupt. | 217 // If getting/indexing attributes throws, count that as corrupt. |
221 attrs = element.attributes; | 218 attrs = element.attributes; |
222 isAttr = attrs['is']; | 219 isAttr = attrs['is']; |
223 var corruptedTest1 = Element._hasCorruptedAttributes(element); | 220 var corruptedTest1 = Element._hasCorruptedAttributes(element); |
224 | 221 |
225 // On IE, erratically, the hasCorruptedAttributes test can return false, | 222 // On IE, erratically, the hasCorruptedAttributes test can return false, |
226 // even though it clearly is corrupted. A separate copy of the test | 223 // even though it clearly is corrupted. A separate copy of the test |
227 // inlining just the basic check seems to help. | 224 // inlining just the basic check seems to help. |
228 corrupted = corruptedTest1 ? true : Element._hasCorruptedAttributesAdditio
nalCheck(element); | 225 corrupted = corruptedTest1 |
229 } catch(e) {} | 226 ? true |
| 227 : Element._hasCorruptedAttributesAdditionalCheck(element); |
| 228 } catch (e) {} |
230 var elementText = 'element unprintable'; | 229 var elementText = 'element unprintable'; |
231 try { | 230 try { |
232 elementText = element.toString(); | 231 elementText = element.toString(); |
233 } catch(e) {} | 232 } catch (e) {} |
234 try { | 233 try { |
235 var elementTagName = Element._safeTagName(element); | 234 var elementTagName = Element._safeTagName(element); |
236 _sanitizeElement(element, parent, corrupted, elementText, elementTagName, | 235 _sanitizeElement(element, parent, corrupted, elementText, elementTagName, |
237 attrs, isAttr); | 236 attrs, isAttr); |
238 } on ArgumentError { // Thrown by _ThrowsNodeValidator | 237 } on ArgumentError { |
| 238 // Thrown by _ThrowsNodeValidator |
239 rethrow; | 239 rethrow; |
240 } catch(e) { // Unexpected exception sanitizing -> remove | 240 } catch (e) { |
| 241 // Unexpected exception sanitizing -> remove |
241 _removeNode(element, parent); | 242 _removeNode(element, parent); |
242 window.console.warn('Removing corrupted element $elementText'); | 243 window.console.warn('Removing corrupted element $elementText'); |
243 } | 244 } |
244 } | 245 } |
245 | 246 |
246 /// Having done basic sanity checking on the element, and computed the | 247 /// Having done basic sanity checking on the element, and computed the |
247 /// important attributes we want to check, remove it if it's not valid | 248 /// important attributes we want to check, remove it if it's not valid |
248 /// or not allowed, either as a whole or particular attributes. | 249 /// or not allowed, either as a whole or particular attributes. |
249 void _sanitizeElement(Element element, Node parent, bool corrupted, | 250 void _sanitizeElement(Element element, Node parent, bool corrupted, |
250 String text, String tag, Map attrs, String isAttr) { | 251 String text, String tag, Map attrs, String isAttr) { |
251 if (false != corrupted) { | 252 if (false != corrupted) { |
252 _removeNode(element, parent); | 253 _removeNode(element, parent); |
253 window.console.warn( | 254 window.console |
254 'Removing element due to corrupted attributes on <$text>'); | 255 .warn('Removing element due to corrupted attributes on <$text>'); |
255 return; | 256 return; |
256 } | 257 } |
257 if (!validator.allowsElement(element)) { | 258 if (!validator.allowsElement(element)) { |
258 _removeNode(element, parent); | 259 _removeNode(element, parent); |
259 window.console.warn( | 260 window.console.warn('Removing disallowed element <$tag> from $parent'); |
260 'Removing disallowed element <$tag> from $parent'); | |
261 return; | 261 return; |
262 } | 262 } |
263 | 263 |
264 if (isAttr != null) { | 264 if (isAttr != null) { |
265 if (!validator.allowsAttribute(element, 'is', isAttr)) { | 265 if (!validator.allowsAttribute(element, 'is', isAttr)) { |
266 _removeNode(element, parent); | 266 _removeNode(element, parent); |
267 window.console.warn('Removing disallowed type extension ' | 267 window.console.warn('Removing disallowed type extension ' |
268 '<$tag is="$isAttr">'); | 268 '<$tag is="$isAttr">'); |
269 return; | 269 return; |
270 } | 270 } |
271 } | 271 } |
272 | 272 |
273 // TODO(blois): Need to be able to get all attributes, irrespective of | 273 // TODO(blois): Need to be able to get all attributes, irrespective of |
274 // XMLNS. | 274 // XMLNS. |
275 var keys = attrs.keys.toList(); | 275 var keys = attrs.keys.toList(); |
276 for (var i = attrs.length - 1; i >= 0; --i) { | 276 for (var i = attrs.length - 1; i >= 0; --i) { |
277 var name = keys[i]; | 277 var name = keys[i]; |
278 if (!validator.allowsAttribute(element, name.toLowerCase(), | 278 if (!validator.allowsAttribute( |
279 attrs[name])) { | 279 element, name.toLowerCase(), attrs[name])) { |
280 window.console.warn('Removing disallowed attribute ' | 280 window.console.warn('Removing disallowed attribute ' |
281 '<$tag $name="${attrs[name]}">'); | 281 '<$tag $name="${attrs[name]}">'); |
282 attrs.remove(name); | 282 attrs.remove(name); |
283 } | 283 } |
284 } | 284 } |
285 | 285 |
286 if (element is TemplateElement) { | 286 if (element is TemplateElement) { |
287 TemplateElement template = element; | 287 TemplateElement template = element; |
288 sanitizeTree(template.content); | 288 sanitizeTree(template.content); |
289 } | 289 } |
290 } | 290 } |
291 | 291 |
292 /// Sanitize the node and its children recursively. | 292 /// Sanitize the node and its children recursively. |
293 void sanitizeNode(Node node, Node parent) { | 293 void sanitizeNode(Node node, Node parent) { |
294 switch (node.nodeType) { | 294 switch (node.nodeType) { |
295 case Node.ELEMENT_NODE: | 295 case Node.ELEMENT_NODE: |
296 _sanitizeUntrustedElement(node, parent); | 296 _sanitizeUntrustedElement(node, parent); |
297 break; | 297 break; |
298 case Node.COMMENT_NODE: | 298 case Node.COMMENT_NODE: |
299 case Node.DOCUMENT_FRAGMENT_NODE: | 299 case Node.DOCUMENT_FRAGMENT_NODE: |
300 case Node.TEXT_NODE: | 300 case Node.TEXT_NODE: |
301 case Node.CDATA_SECTION_NODE: | 301 case Node.CDATA_SECTION_NODE: |
302 break; | 302 break; |
303 default: | 303 default: |
304 _removeNode(node, parent); | 304 _removeNode(node, parent); |
305 } | 305 } |
306 } | 306 } |
307 } | 307 } |
OLD | NEW |