Add three more new unsafe APIs to the unsafe_html rule (#2055)
Add three more new unsafe APIs to the unsafe_html rule
diff --git a/lib/src/rules/unsafe_html.dart b/lib/src/rules/unsafe_html.dart
index 32c7a74..393fbeb 100644
--- a/lib/src/rules/unsafe_html.dart
+++ b/lib/src/rules/unsafe_html.dart
@@ -21,7 +21,10 @@
ImageElement, or ScriptElement
* assigning directly to the `srcdoc` field of an IFrameElement
* calling the `createFragment` method of Element
+* calling the `open` method of Window
* calling the `setInnerHtml` method of Element
+* calling the `Element.html` constructor
+* calling the `DocumentFragment.html` constructor
**BAD:**
@@ -49,21 +52,22 @@
NodeLintRegistry registry, LinterContext context) {
final visitor = _Visitor(this);
registry.addAssignmentExpression(this, visitor);
+ registry.addInstanceCreationExpression(this, visitor);
registry.addMethodInvocation(this, visitor);
}
}
class _Visitor extends SimpleAstVisitor<void> {
- static const hrefAttributeCode =
- 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).');
+ // TODO(srawlins): Reference attributes ('href', 'src', and 'srcdoc') with
+ // single-quotes to match the convention in the analyzer and linter packages.
+ // This requires some coordination within Google, as various allow-lists are
+ // keyed on the exact text of the LintCode message.
+ static const unsafeAttributeCode =
+ LintCode('unsafe_html', '$_descPrefix (assigning "{0}" attribute).');
+ static const unsafeMethodCode = LintCode(
+ 'unsafe_html', "$_descPrefix (calling the '{0}' method of {1}).");
+ static const unsafeConstructorCode = LintCode(
+ 'unsafe_html', "$_descPrefix (calling the '{0}' constructor of {1}).");
final LintRule rule;
@@ -88,7 +92,8 @@
if (property.name == 'href') {
final type = target.staticType;
if (type.isDynamic || type.extendsDartHtmlClass('AnchorElement')) {
- rule.reportLint(assignment, errorCode: hrefAttributeCode);
+ rule.reportLint(assignment,
+ arguments: ['href'], errorCode: unsafeAttributeCode);
}
} else if (property.name == 'src') {
final type = target.staticType;
@@ -97,12 +102,32 @@
type.extendsDartHtmlClass('IFrameElement') ||
type.extendsDartHtmlClass('ImageElement') ||
type.extendsDartHtmlClass('ScriptElement')) {
- rule.reportLint(assignment, errorCode: srcAttributeCode);
+ rule.reportLint(assignment,
+ arguments: ['src'], errorCode: unsafeAttributeCode);
}
} else if (property.name == 'srcdoc') {
final type = target.staticType;
if (type.isDynamic || type.extendsDartHtmlClass('IFrameElement')) {
- rule.reportLint(assignment, errorCode: srcdocAttributeCode);
+ rule.reportLint(assignment,
+ arguments: ['srcdoc'], errorCode: unsafeAttributeCode);
+ }
+ }
+ }
+
+ @override
+ void visitInstanceCreationExpression(InstanceCreationExpression node) {
+ var type = node.staticType;
+ if (type == null) return;
+
+ var constructorName = node.constructorName;
+ if (constructorName?.name?.name == 'html') {
+ if (type.extendsDartHtmlClass('DocumentFragment')) {
+ rule.reportLint(node,
+ arguments: ['html', 'DocumentFragment'],
+ errorCode: unsafeConstructorCode);
+ } else if (type.extendsDartHtmlClass('Element')) {
+ rule.reportLint(node,
+ arguments: ['html', 'Element'], errorCode: unsafeConstructorCode);
}
}
}
@@ -117,10 +142,17 @@
if (methodName == 'createFragment' &&
(type.isDynamic || type.extendsDartHtmlClass('Element'))) {
- rule.reportLint(node, errorCode: createFragmentMethodCode);
+ rule.reportLint(node,
+ arguments: ['createFragment', 'Element'],
+ errorCode: unsafeMethodCode);
} else if (methodName == 'setInnerHtml' &&
(type.isDynamic || type.extendsDartHtmlClass('Element'))) {
- rule.reportLint(node, errorCode: setInnerHtmlMethodCode);
+ rule.reportLint(node,
+ arguments: ['setInnerHtml', 'Element'], errorCode: unsafeMethodCode);
+ } else if (methodName == 'open' &&
+ (type.isDynamic || type.extendsDartHtmlClass('Window'))) {
+ rule.reportLint(node,
+ arguments: ['open', 'Window'], errorCode: unsafeMethodCode);
}
}
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 951dedc..d46fc41 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -12,7 +12,7 @@
sdk: '>=2.6.0 <3.0.0'
dependencies:
- analyzer: ^0.39.3
+ analyzer: ^0.39.6
args: '>=1.4.0 <2.0.0'
charcode: ^1.0.0
glob: ^1.0.3
diff --git a/test/rules/unsafe_html.dart b/test/rules/unsafe_html.dart
index b0340da..b8d94bc 100644
--- a/test/rules/unsafe_html.dart
+++ b/test/rules/unsafe_html.dart
@@ -27,23 +27,31 @@
IFrameElement()..srcdoc = 'foo'; // LINT
- var heading = HeadingElement();
+ var heading = HeadingElement.h1();
heading.createFragment('<script>'); // LINT
heading.setInnerHtml('<script>'); // LINT
+ Window().open('url', 'name'); // LINT
+
+ DocumentFragment.html('<script>'); // LINT
+ Element.html('<script>'); // LINT
+
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.open('url', 'name'); // OK
c.setInnerHtml('<script>'); // OK
+ C.html('<script>'); // OK
dynamic d;
d.src = 'foo.js'; // LINT
d.srcdoc = 'foo.js'; // LINT
d.href = 'foo.js'; // LINT
d.createFragment('<script>'); // LINT
+ d.open('url', 'name'); // LINT
d.setInnerHtml('<script>'); // LINT
(script as dynamic).src = 'foo.js'; // LINT
(C() as dynamic).src = 'foo.js'; // LINT
@@ -54,8 +62,13 @@
String srcdoc;
String href;
+ C();
+
+ C.html(String content);
+
void createFragment(String html) {}
+ void open(String url, String name) {}
+
void setInnerHtml(String html) {}
}
-