Add three new unsafe APIs to the unsafe_html rule (#2054)
diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 7cba025..32c7a74 100644
--- a/lib/src/rules/unsafe_html.dart
+++ b/lib/src/rules/unsafe_html.dart
@@ -4,6 +4,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/dart/element/type.dart';
import '../analyzer.dart';
import '../util/dart_type_utilities.dart';
@@ -13,9 +14,14 @@
const _details = r'''
-**AVOID** assigning directly to the src field of an EmbedElement,
-IFrameElement, ImageElement, or ScriptElement, or the href field of an
-AnchorElement.
+**AVOID**
+
+* assigning directly to the `href` field of an AnchorElement
+* assigning directly to the `src` field of an EmbedElement, IFrameElement,
+ ImageElement, or ScriptElement
+* assigning directly to the `srcdoc` field of an IFrameElement
+* calling the `createFragment` method of Element
+* calling the `setInnerHtml` method of Element
**BAD:**
@@ -24,6 +30,12 @@
```
''';
+extension on DartType {
+ /// Returns whether this type extends [className] from the dart:html library.
+ bool extendsDartHtmlClass(String className) =>
+ DartTypeUtilities.extendsClass(this, className, 'dart.dom.html');
+}
+
class UnsafeHtml extends LintRule implements NodeLintRule {
UnsafeHtml()
: super(
@@ -37,6 +49,7 @@
NodeLintRegistry registry, LinterContext context) {
final visitor = _Visitor(this);
registry.addAssignmentExpression(this, visitor);
+ registry.addMethodInvocation(this, visitor);
}
}
@@ -45,6 +58,12 @@
LintCode('unsafe_html', '$_descPrefix (assigning "href" attribute).');
static const srcAttributeCode =
LintCode('unsafe_html', '$_descPrefix (assigning "src" attribute).');
+ static const srcdocAttributeCode =
+ LintCode('unsafe_html', '$_descPrefix (assigning "srcdoc" attribute).');
+ static const createFragmentMethodCode = LintCode('unsafe_html',
+ '$_descPrefix (calling the "createFragment" method of Element).');
+ static const setInnerHtmlMethodCode = LintCode('unsafe_html',
+ '$_descPrefix (calling the "setInnerHtml" method of Element).');
final LintRule rule;
@@ -64,28 +83,44 @@
AssignmentExpression assignment) {
if (property == null || target == null) return;
- // It is more efficient to first check if `src` (or `href`) is being
- // assigned, _then_ check if the target of an interesting type.
- if (property.name == 'src') {
+ // It is more efficient to check the setter's name before checking whether
+ // the target is an interesting type.
+ if (property.name == 'href') {
final type = target.staticType;
- if (type.isDynamic ||
- DartTypeUtilities.extendsClass(
- type, 'EmbedElement', 'dart.dom.html') ||
- DartTypeUtilities.extendsClass(
- type, 'IFrameElement', 'dart.dom.html') ||
- DartTypeUtilities.extendsClass(
- type, 'ImageElement', 'dart.dom.html') ||
- DartTypeUtilities.extendsClass(
- type, 'ScriptElement', 'dart.dom.html')) {
- rule.reportLint(assignment, errorCode: srcAttributeCode);
- }
- } else if (property.name == 'href') {
- final type = target.staticType;
- if (type.isDynamic ||
- DartTypeUtilities.extendsClass(
- type, 'AnchorElement', 'dart.dom.html')) {
+ if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
rule.reportLint(assignment, errorCode: hrefAttributeCode);
}
+ } else if (property.name == 'src') {
+ final type = target.staticType;
+ if (type.isDynamic ||
+ type.extendsDartHtmlClass('EmbedElement') ||
+ type.extendsDartHtmlClass('IFrameElement') ||
+ type.extendsDartHtmlClass('ImageElement') ||
+ type.extendsDartHtmlClass('ScriptElement')) {
+ rule.reportLint(assignment, errorCode: srcAttributeCode);
+ }
+ } else if (property.name == 'srcdoc') {
+ final type = target.staticType;
+ if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
+ rule.reportLint(assignment, errorCode: srcdocAttributeCode);
+ }
+ }
+ }
+
+ @override
+ void visitMethodInvocation(MethodInvocation node) {
+ var type = node.target?.staticType;
+ if (type == null) return;
+
+ var methodName = node.methodName?.name;
+ if (methodName == null) return;
+
+ if (methodName == 'createFragment' &&
+ (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
+ rule.reportLint(node, errorCode: createFragmentMethodCode);
+ } else if (methodName == 'setInnerHtml' &&
+ (type.isDynamic || type.extendsDartHtmlClass('Element'))) {
+ rule.reportLint(node, errorCode: setInnerHtmlMethodCode);
}
}
}
diff --git a/test/rules/unsafe_html.dart b/test/rules/unsafe_html.dart
index 0dbae82..b0340da 100644
--- a/test/rules/unsafe_html.dart
+++ b/test/rules/unsafe_html.dart
@@ -25,18 +25,37 @@
..type = 'application/javascript';
script?.src = 'foo.js'; // LINT
+ IFrameElement()..srcdoc = 'foo'; // LINT
+
+ var heading = HeadingElement();
+ heading.createFragment('<script>'); // LINT
+ heading.setInnerHtml('<script>'); // LINT
+
C().src = 'foo.js'; // OK
- C()..src = 'foo.js'; // OK
- C()?.src = 'foo.js'; // OK
+ var c = C();
+ c..src = 'foo.js'; // OK
+ c?.src = 'foo.js'; // OK
+ c.srcdoc = 'foo.js'; // OK
+ c.createFragment('<script>'); // OK
+ c.setInnerHtml('<script>'); // OK
dynamic d;
d.src = 'foo.js'; // LINT
+ d.srcdoc = 'foo.js'; // LINT
d.href = 'foo.js'; // LINT
+ d.createFragment('<script>'); // LINT
+ d.setInnerHtml('<script>'); // LINT
(script as dynamic).src = 'foo.js'; // LINT
(C() as dynamic).src = 'foo.js'; // LINT
}
class C {
String src;
+ String srcdoc;
String href;
+
+ void createFragment(String html) {}
+
+ void setInnerHtml(String html) {}
}
+