Skip to content

Commit 81173a7

Browse files
authored
fix: do not set "rel: 'noopener noreferrer'" for local links (#160)
1 parent a93ae0f commit 81173a7

File tree

2 files changed

+93
-11
lines changed

2 files changed

+93
-11
lines changed

src/utils/url.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {getLinkProps, isAbsoluteUrl, isLinkExternal} from './url';
2+
3+
describe('URL utils check', () => {
4+
test.each([
5+
['https://user:[email protected]:8080/p/a/t/h?query=string&query2=1#hash', true],
6+
['http://example.net/path', true],
7+
['/p/a/t/h?query=string&query2=1#hash', false],
8+
['/path', false],
9+
['path', false],
10+
])("isAbsoluteUrl('%s') should return '%s'", (url, result) => {
11+
expect(isAbsoluteUrl(url)).toEqual(result);
12+
});
13+
14+
test.each([
15+
[
16+
'https://user:[email protected]:8080/p/a/t/h?query=string&query2=1#hash',
17+
'example.net',
18+
true,
19+
],
20+
[
21+
'https://user:[email protected]:8080/p/a/t/h?query=string&query2=1#hash',
22+
'sub.example.com',
23+
false,
24+
],
25+
[
26+
'https://user:[email protected]:8080/p/a/t/h?query=string&query2=1#hash',
27+
undefined,
28+
true,
29+
],
30+
['http://example.net/path', 'example.net', false],
31+
['http://example.net/path', 'sub.example.com', true],
32+
['http://example.net/path', undefined, true],
33+
['/p/a/t/h?query=string&query2=1#hash', 'example.net', false],
34+
['/p/a/t/h?query=string&query2=1#hash', undefined, false],
35+
['/path', 'example.net', false],
36+
['/path', undefined, false],
37+
['path', 'example.net', false],
38+
['path', undefined, false],
39+
])("isLinkExternal('%s', '%s') should return '%s'", (url, hostname, result) => {
40+
expect(isLinkExternal(url, hostname)).toEqual(result);
41+
});
42+
43+
test.each([
44+
['http://example.net/path', 'example.net', '_blank', {target: '_blank'}],
45+
['http://example.net/path', 'example.net', undefined, {}],
46+
[
47+
'http://example.net/path',
48+
'example.com',
49+
'_blank',
50+
{target: '_blank', rel: 'noopener noreferrer'},
51+
],
52+
[
53+
'http://example.net/path',
54+
'example.com',
55+
undefined,
56+
{target: '_blank', rel: 'noopener noreferrer'},
57+
],
58+
[
59+
'http://example.net/path',
60+
undefined,
61+
'_blank',
62+
{target: '_blank', rel: 'noopener noreferrer'},
63+
],
64+
[
65+
'http://example.net/path',
66+
undefined,
67+
undefined,
68+
{target: '_blank', rel: 'noopener noreferrer'},
69+
],
70+
71+
['/path', 'example.net', '_blank', {target: '_blank'}],
72+
['/path', 'example.net', undefined, {}],
73+
['/path', undefined, '_blank', {target: '_blank'}],
74+
['/path', undefined, undefined, {}],
75+
])("getLinkProps('%s', '%s', '%s') should return '%s'", (url, hostname, target, result) => {
76+
expect(getLinkProps(url, hostname, target)).toEqual(result);
77+
});
78+
});

src/utils/url.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,34 @@ import {parse, format} from 'url';
22

33
export type Query = Record<string, number | string | null>;
44

5+
const EXAMPLE_URL = 'https://example.org';
56
export const EXTERNAL_LINK_PROPS = {target: '_blank', rel: 'noopener noreferrer'};
67

78
export function getLinkProps(url: string, hostname?: string, target?: string) {
89
let linkProps = {target};
910

10-
if (target === '_blank' || isLinkExternal(url, hostname)) {
11+
if (isLinkExternal(url, hostname)) {
1112
linkProps = {...linkProps, ...EXTERNAL_LINK_PROPS};
1213
}
1314

1415
return linkProps;
1516
}
1617

17-
export function isLinkExternal(url: string, routerHostname?: string) {
18-
if (!routerHostname) {
19-
return true;
20-
}
21-
22-
const {hostname} = parse(url);
18+
export function isAbsoluteUrl(url: string | URL) {
19+
// Using example URL as base for relative links
20+
const urlObj = new URL(url, EXAMPLE_URL);
2321

24-
if (!hostname) {
25-
return false;
26-
}
22+
return (
23+
// Compare url origin with example and check that original url was not example one
24+
urlObj.origin !== EXAMPLE_URL || (typeof url === 'string' && url.startsWith(EXAMPLE_URL))
25+
);
26+
}
2727

28-
return getNonLocaleHostName(hostname) !== getNonLocaleHostName(routerHostname);
28+
export function isLinkExternal(url: string, routerHostname?: string) {
29+
return (
30+
isAbsoluteUrl(url) &&
31+
getNonLocaleHostName(new URL(url).hostname) !== getNonLocaleHostName(routerHostname ?? '')
32+
);
2933
}
3034

3135
export function getNonLocaleHostName(hostname: string) {

0 commit comments

Comments
 (0)