Skip to content

Commit

Permalink
Ignore URLs with custom protocols
Browse files Browse the repository at this point in the history
Fixes #177
Fixes #152
Closes #153
  • Loading branch information
sindresorhus committed Oct 29, 2022
1 parent 92cfc6b commit 1ce2387
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 21 deletions.
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ export interface Options {
/**
[Normalize](https://en.wikipedia.org/wiki/URL_normalization) a URL.
URLs with custom protocols are not normalized and just passed through by default. Supported protocols are: `https`, `http`, `file`, and `data`.
@param url - URL to normalize, including [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs).
@example
Expand Down
19 changes: 17 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ const DATA_URL_DEFAULT_CHARSET = 'us-ascii';

const testParameter = (name, filters) => filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name);

const supportedProtocols = new Set([
'https:',
'http:',
'file:',
]);

const hasCustomProtocol = urlString => {
try {
const {protocol} = new URL(urlString);
return protocol?.endsWith(':') && !supportedProtocols.has(protocol);
} catch {
return false;
}
};

const normalizeDataURL = (urlString, {stripHash}) => {
const match = /^data:(?<type>[^,]*?),(?<data>[^#]*?)(?:#(?<hash>.*))?$/.exec(urlString);

Expand Down Expand Up @@ -81,8 +96,8 @@ export default function normalizeUrl(urlString, options) {
return normalizeDataURL(urlString, options);
}

if (/^view-source:/i.test(urlString)) {
throw new Error('`view-source:` is not supported as it is a non-standard protocol');
if (hasCustomProtocol(urlString)) {
return urlString;
}

const hasRelativeProtocol = urlString.startsWith('//');
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ normalizeUrl('//www.sindresorhus.com:80/../baz?b=bar&a=foo');

### normalizeUrl(url, options?)

URLs with custom protocols are not normalized and just passed through by default. Supported protocols are: `https`, `http`, `file`, and `data`.

#### url

Type: `string`
Expand Down
31 changes: 12 additions & 19 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ test('main', t => {
t.is(normalizeUrl('http://sindresorhus.com'), 'http://sindresorhus.com');
t.is(normalizeUrl('http://sindresorhus.com:80'), 'http://sindresorhus.com');
t.is(normalizeUrl('https://sindresorhus.com:443'), 'https://sindresorhus.com');
t.is(normalizeUrl('ftp://sindresorhus.com:21'), 'ftp://sindresorhus.com');
t.is(normalizeUrl('http://www.sindresorhus.com'), 'http://sindresorhus.com');
t.is(normalizeUrl('www.com'), 'http://www.com');
t.is(normalizeUrl('http://www.www.sindresorhus.com'), 'http://www.www.sindresorhus.com');
Expand All @@ -37,27 +36,25 @@ test('main', t => {
t.is(normalizeUrl('http://sindresorhus.com/foo#bar:~:text=hello%20world', {stripHash: true}), 'http://sindresorhus.com/foo');
t.is(normalizeUrl('http://sindresorhus.com/foo/bar/../baz'), 'http://sindresorhus.com/foo/baz');
t.is(normalizeUrl('http://sindresorhus.com/foo/bar/./baz'), 'http://sindresorhus.com/foo/bar/baz');
t.is(normalizeUrl('sindre://www.sorhus.com'), 'sindre://sorhus.com');
t.is(normalizeUrl('sindre://www.sorhus.com/'), 'sindre://sorhus.com');
t.is(normalizeUrl('sindre://www.sorhus.com/foo/bar'), 'sindre://sorhus.com/foo/bar');
// t.is(normalizeUrl('sindre://www.sorhus.com'), 'sindre://sorhus.com');
// t.is(normalizeUrl('sindre://www.sorhus.com/'), 'sindre://sorhus.com');
// t.is(normalizeUrl('sindre://www.sorhus.com/foo/bar'), 'sindre://sorhus.com/foo/bar');
t.is(normalizeUrl('https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png'), 'https://i.vimeocdn.com/filter/overlay?src0=https://i.vimeocdn.com/video/598160082_1280x720.jpg&src1=https://f.vimeocdn.com/images_v6/share/play_icon_overlay.png');
});

test('stripAuthentication option', t => {
t.is(normalizeUrl('http://user:password@www.sindresorhus.com'), 'http://sindresorhus.com');
t.is(normalizeUrl('https://user:password@www.sindresorhus.com'), 'https://sindresorhus.com');
t.is(normalizeUrl('https://user:password@www.sindresorhus.com/@user'), 'https://sindresorhus.com/@user');
t.is(normalizeUrl('user:password@sindresorhus.com'), 'http://sindresorhus.com');
t.is(normalizeUrl('http://user:password@www.êxample.com'), 'http://xn--xample-hva.com');
t.is(normalizeUrl('sindre://user:password@www.sorhus.com'), 'sindre://sorhus.com');
// t.is(normalizeUrl('sindre://user:password@www.sorhus.com'), 'sindre://sorhus.com');

const options = {stripAuthentication: false};
t.is(normalizeUrl('http://user:password@www.sindresorhus.com', options), 'http://user:password@sindresorhus.com');
t.is(normalizeUrl('https://user:password@www.sindresorhus.com', options), 'https://user:password@sindresorhus.com');
t.is(normalizeUrl('https://user:password@www.sindresorhus.com/@user', options), 'https://user:password@sindresorhus.com/@user');
t.is(normalizeUrl('user:password@sindresorhus.com', options), 'http://user:password@sindresorhus.com');
t.is(normalizeUrl('http://user:password@www.êxample.com', options), 'http://user:password@xn--xample-hva.com');
t.is(normalizeUrl('sindre://user:password@www.sorhus.com', options), 'sindre://user:password@sorhus.com');
// t.is(normalizeUrl('sindre://user:password@www.sorhus.com', options), 'sindre://user:password@sorhus.com');
});

test('stripProtocol option', t => {
Expand All @@ -66,8 +63,6 @@ test('stripProtocol option', t => {
t.is(normalizeUrl('http://sindresorhus.com', options), 'sindresorhus.com');
t.is(normalizeUrl('https://www.sindresorhus.com', options), 'sindresorhus.com');
t.is(normalizeUrl('//www.sindresorhus.com', options), 'sindresorhus.com');
t.is(normalizeUrl('sindre://user:password@www.sorhus.com', options), 'sindre://sorhus.com');
t.is(normalizeUrl('sindre://www.sorhus.com', options), 'sindre://sorhus.com');
});

test('stripTextFragment option', t => {
Expand Down Expand Up @@ -98,7 +93,7 @@ test('stripWWW option', t => {
t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com');
t.is(normalizeUrl('www.sindresorhus.com', options), 'http://www.sindresorhus.com');
t.is(normalizeUrl('http://www.êxample.com', options), 'http://www.xn--xample-hva.com');
t.is(normalizeUrl('sindre://www.sorhus.com', options), 'sindre://www.sorhus.com');
// t.is(normalizeUrl('sindre://www.sorhus.com', options), 'sindre://www.sorhus.com');

const options2 = {stripWWW: true};
t.is(normalizeUrl('http://www.vue.amsterdam', options2), 'http://vue.amsterdam');
Expand Down Expand Up @@ -393,14 +388,6 @@ test('prevents homograph attack', t => {
t.is(normalizeUrl('https://ebаy.com'), 'https://xn--eby-7cd.com');
});

test('view-source URL', t => {
t.throws(() => {
normalizeUrl('view-source:https://www.sindresorhus.com');
}, {
message: '`view-source:` is not supported as it is a non-standard protocol',
});
});

test('does not have exponential performance for data URLs', t => {
for (let index = 0; index < 1000; index += 50) {
const url = 'data:' + Array.from({length: index}).fill(',#').join('') + '\ra';
Expand All @@ -414,3 +401,9 @@ test('does not have exponential performance for data URLs', t => {
t.true(difference < 100, `Execution time: ${difference}`);
}
});

test('ignore custom schemes', t => {
t.is(normalizeUrl('tel:004346382763'), 'tel:004346382763');
t.is(normalizeUrl('mailto:office@foo.com'), 'mailto:office@foo.com');
t.is(normalizeUrl('sindre://www.sindresorhus.com'), 'sindre://www.sindresorhus.com');
});

0 comments on commit 1ce2387

Please sign in to comment.