Skip to content

Commit 5aa4470

Browse files
authored
Merge pull request #1216 from gfx/no-target-blank/only-for-external-links
Don't issue errors for `<a/>` with target=_blank and non-external URLs
2 parents 089beec + 89c33b6 commit 5aa4470

File tree

3 files changed

+50
-28
lines changed

3 files changed

+50
-28
lines changed

docs/rules/jsx-no-target-blank.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Prevent usage of unsafe `target='_blank'` (react/jsx-no-target-blank)
22

3-
When creating a JSX element that has an a tag, it is often desired to have
3+
When creating a JSX element that has an `a` tag, it is often desired to have
44
the link open in a new tab using the `target='_blank'` attribute. Using this
55
attribute unaccompanied by `rel='noreferrer noopener'`, however, is a severe
66
security vulnerability ([see here for more details](https://mathiasbynens.github.io/rel-noopener))
@@ -11,14 +11,16 @@ This rules requires that you accompany all `target='_blank'` attributes with `re
1111
The following patterns are considered errors:
1212

1313
```jsx
14-
var Hello = <a target='_blank'></a>
14+
var Hello = <a target='_blank' href="http://example.com/"></a>
1515
```
1616

1717
The following patterns are not considered errors:
1818

1919
```jsx
2020
var Hello = <p target='_blank'></p>
21-
var Hello = <a target='_blank' rel='noopener noreferrer'></a>
21+
var Hello = <a target='_blank' rel='noopener noreferrer' href="http://example.com"></a>
22+
var Hello = <a target='_blank' href="relative/path/in/the/host"></a>
23+
var Hello = <a target='_blank' href="/absolute/path/in/the/host"></a>
2224
var Hello = <a></a>
2325
```
2426

lib/rules/jsx-no-target-blank.js

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@
88
// Rule Definition
99
// ------------------------------------------------------------------------------
1010

11+
function isTargetBlank(attr) {
12+
return attr.name.name === 'target' &&
13+
attr.value.type === 'Literal' &&
14+
attr.value.value.toLowerCase() === '_blank';
15+
}
16+
17+
function hasExternalLink(element) {
18+
return element.attributes.find(function (attr) {
19+
return attr.name &&
20+
attr.name.name === 'href' &&
21+
attr.value.type === 'Literal' &&
22+
/^(?:\w+:|\/\/)/.test(attr.value.value);
23+
});
24+
}
25+
26+
function hasSecureRel(element) {
27+
return element.attributes.find(function (attr) {
28+
if (attr.name.name === 'rel') {
29+
var tags = attr.value.type === 'Literal' && attr.value.value.toLowerCase().split(' ');
30+
return !tags || (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0);
31+
}
32+
return false;
33+
});
34+
}
35+
1136
module.exports = {
1237
meta: {
1338
docs: {
@@ -26,25 +51,12 @@ module.exports = {
2651
}
2752

2853
if (
29-
node.name.name === 'target' &&
30-
node.value.type === 'Literal' &&
31-
node.value.value.toLowerCase() === '_blank'
54+
isTargetBlank(node) &&
55+
hasExternalLink(node.parent) &&
56+
!hasSecureRel(node.parent)
3257
) {
33-
var relFound = false;
34-
var attrs = node.parent.attributes;
35-
for (var idx in attrs) {
36-
if (attrs[idx].name && attrs[idx].name.name === 'rel') {
37-
var tags = attrs[idx].value.type === 'Literal' && attrs[idx].value.value.toLowerCase().split(' ');
38-
if (!tags || (tags.indexOf('noopener') >= 0 && tags.indexOf('noreferrer') >= 0)) {
39-
relFound = true;
40-
break;
41-
}
42-
}
43-
}
44-
if (!relFound) {
45-
context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
46-
'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
47-
}
58+
context.report(node, 'Using target="_blank" without rel="noopener noreferrer" ' +
59+
'is a security risk: see https://mathiasbynens.github.io/rel-noopener');
4860
}
4961
}
5062
};

tests/lib/rules/jsx-no-target-blank.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,31 +35,39 @@ ruleTester.run('jsx-no-target-blank', rule, {
3535
{code: '<p target="_blank"></p>'},
3636
{code: '<a href="foobar" target="_BLANK" rel="NOOPENER noreferrer"></a>'},
3737
{code: '<a target="_blank" rel={relValue}></a>'},
38-
{code: '<a target={targetValue} rel="noopener noreferrer"></a>'}
38+
{code: '<a target={targetValue} rel="noopener noreferrer"></a>'},
39+
{code: '<a target={targetValue} href="relative/path"></a>'},
40+
{code: '<a target={targetValue} href="/absolute/path"></a>'}
3941
],
4042
invalid: [{
41-
code: '<a target="_blank"></a>',
43+
code: '<a target="_blank" href="http://example.com"></a>',
4244
errors: [{
4345
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
4446
' see https://mathiasbynens.github.io/rel-noopener'
4547
}]
4648
}, {
47-
code: '<a target="_blank" rel=""></a>',
49+
code: '<a target="_blank" rel="" href="http://example.com"></a>',
4850
errors: [{
4951
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
5052
' see https://mathiasbynens.github.io/rel-noopener'
5153
}]
5254
}, {
53-
code: '<a target="_blank" rel="noopenernoreferrer"></a>',
55+
code: '<a target="_blank" rel="noopenernoreferrer" href="http://example.com"></a>',
5456
errors: [{
5557
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
5658
' see https://mathiasbynens.github.io/rel-noopener'
5759
}]
5860
}, {
59-
code: '<a target="_BLANK"></a>',
61+
code: '<a target="_BLANK" href="http://example.com"></a>',
6062
errors: [{
6163
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
6264
' see https://mathiasbynens.github.io/rel-noopener'
63-
}]}
64-
]
65+
}]
66+
}, {
67+
code: '<a target="_blank" href="//example.com"></a>',
68+
errors: [{
69+
message: 'Using target="_blank" without rel="noopener noreferrer" is a security risk:' +
70+
' see https://mathiasbynens.github.io/rel-noopener'
71+
}]
72+
}]
6573
});

0 commit comments

Comments
 (0)