pax_global_header00006660000000000000000000000064143041122630014506gustar00rootroot0000000000000052 comment=15abc16acec0069c5d8852104956ccd8e5aa85f9 normalize-url-7.1.0/000077500000000000000000000000001430411226300143135ustar00rootroot00000000000000normalize-url-7.1.0/.editorconfig000066400000000000000000000002571430411226300167740ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 normalize-url-7.1.0/.gitattributes000066400000000000000000000000231430411226300172010ustar00rootroot00000000000000* text=auto eol=lf normalize-url-7.1.0/.github/000077500000000000000000000000001430411226300156535ustar00rootroot00000000000000normalize-url-7.1.0/.github/funding.yml000066400000000000000000000001671430411226300200340ustar00rootroot00000000000000github: sindresorhus open_collective: sindresorhus tidelift: npm/normalize-url custom: https://sindresorhus.com/donate normalize-url-7.1.0/.github/security.md000066400000000000000000000002631430411226300200450ustar00rootroot00000000000000# Security Policy To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. normalize-url-7.1.0/.github/workflows/000077500000000000000000000000001430411226300177105ustar00rootroot00000000000000normalize-url-7.1.0/.github/workflows/main.yml000066400000000000000000000010611430411226300213550ustar00rootroot00000000000000name: CI on: - push - pull_request jobs: test: name: Node.js ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: - 16 - 14 - 12 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm test - uses: codecov/codecov-action@v2 if: matrix.node-version == 16 with: fail_ci_if_error: true normalize-url-7.1.0/.gitignore000066400000000000000000000000541430411226300163020ustar00rootroot00000000000000node_modules yarn.lock .nyc_output coverage normalize-url-7.1.0/.npmrc000066400000000000000000000000231430411226300154260ustar00rootroot00000000000000package-lock=false normalize-url-7.1.0/index.d.ts000066400000000000000000000152271430411226300162230ustar00rootroot00000000000000export interface Options { /** @default 'http:' Values: `'https:' | 'http:'` */ readonly defaultProtocol?: string; // TODO: Make this `'https:' | 'http:'` in the next major version. /** Prepends `defaultProtocol` to the URL if it's protocol-relative. @default true @example ``` normalizeUrl('//sindresorhus.com:80/'); //=> 'http://sindresorhus.com' normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false}); //=> '//sindresorhus.com' ``` */ readonly normalizeProtocol?: boolean; /** Normalizes `https:` URLs to `http:`. @default false @example ``` normalizeUrl('https://sindresorhus.com:80/'); //=> 'https://sindresorhus.com' normalizeUrl('https://sindresorhus.com:80/', {forceHttp: true}); //=> 'http://sindresorhus.com' ``` */ readonly forceHttp?: boolean; /** Normalizes `http:` URLs to `https:`. This option can't be used with the `forceHttp` option at the same time. @default false @example ``` normalizeUrl('https://sindresorhus.com:80/'); //=> 'https://sindresorhus.com' normalizeUrl('http://sindresorhus.com:80/', {forceHttps: true}); //=> 'https://sindresorhus.com' ``` */ readonly forceHttps?: boolean; /** Strip the [authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) part of a URL. @default true @example ``` normalizeUrl('user:password@sindresorhus.com'); //=> 'https://sindresorhus.com' normalizeUrl('user:password@sindresorhus.com', {stripAuthentication: false}); //=> 'https://user:password@sindresorhus.com' ``` */ readonly stripAuthentication?: boolean; /** Removes hash from the URL. @default false @example ``` normalizeUrl('sindresorhus.com/about.html#contact'); //=> 'http://sindresorhus.com/about.html#contact' normalizeUrl('sindresorhus.com/about.html#contact', {stripHash: true}); //=> 'http://sindresorhus.com/about.html' ``` */ readonly stripHash?: boolean; /** Remove the protocol from the URL: `http://sindresorhus.com` → `sindresorhus.com`. It will only remove `https://` and `http://` protocols. @default false @example ``` normalizeUrl('https://sindresorhus.com'); //=> 'https://sindresorhus.com' normalizeUrl('sindresorhus.com', {stripProtocol: true}); //=> 'sindresorhus.com' ``` */ readonly stripProtocol?: boolean; /** Strip the [text fragment](https://web.dev/text-fragments/) part of the URL __Note:__ The text fragment will always be removed if the `stripHash` option is set to `true`, as the hash contains the text fragment. @default true @example ``` normalizeUrl('http://sindresorhus.com/about.html#:~:text=hello'); //=> 'http://sindresorhus.com/about.html#' normalizeUrl('http://sindresorhus.com/about.html#section:~:text=hello'); //=> 'http://sindresorhus.com/about.html#section' normalizeUrl('http://sindresorhus.com/about.html#:~:text=hello', {stripTextFragment: false}); //=> 'http://sindresorhus.com/about.html#:~:text=hello' normalizeUrl('http://sindresorhus.com/about.html#section:~:text=hello', {stripTextFragment: false}); //=> 'http://sindresorhus.com/about.html#section:~:text=hello' ``` */ readonly stripTextFragment?: boolean; /** Removes `www.` from the URL. @default true @example ``` normalizeUrl('http://www.sindresorhus.com'); //=> 'http://sindresorhus.com' normalizeUrl('http://www.sindresorhus.com', {stripWWW: false}); //=> 'http://www.sindresorhus.com' ``` */ readonly stripWWW?: boolean; /** Removes query parameters that matches any of the provided strings or regexes. @default [/^utm_\w+/i] @example ``` normalizeUrl('www.sindresorhus.com?foo=bar&ref=test_ref', { removeQueryParameters: ['ref'] }); //=> 'http://sindresorhus.com/?foo=bar' ``` If a boolean is provided, `true` will remove all the query parameters. ``` normalizeUrl('www.sindresorhus.com?foo=bar', { removeQueryParameters: true }); //=> 'http://sindresorhus.com' ``` `false` will not remove any query parameter. ``` normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', { removeQueryParameters: false }); //=> 'http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test' ``` */ readonly removeQueryParameters?: ReadonlyArray | boolean; /** Keeps only query parameters that matches any of the provided strings or regexes. __Note__: It overrides the `removeQueryParameters` option. @default undefined @example ``` normalizeUrl('https://sindresorhus.com?foo=bar&ref=unicorn', { keepQueryParameters: ['ref'] }); //=> 'https://sindresorhus.com/?ref=unicorn' ``` */ readonly keepQueryParameters?: ReadonlyArray; /** Removes trailing slash. __Note__: Trailing slash is always removed if the URL doesn't have a pathname unless the `removeSingleSlash` option is set to `false`. @default true @example ``` normalizeUrl('http://sindresorhus.com/redirect/'); //=> 'http://sindresorhus.com/redirect' normalizeUrl('http://sindresorhus.com/redirect/', {removeTrailingSlash: false}); //=> 'http://sindresorhus.com/redirect/' normalizeUrl('http://sindresorhus.com/', {removeTrailingSlash: false}); //=> 'http://sindresorhus.com' ``` */ readonly removeTrailingSlash?: boolean; /** Remove a sole `/` pathname in the output. This option is independent of `removeTrailingSlash`. @default true @example ``` normalizeUrl('https://sindresorhus.com/'); //=> 'https://sindresorhus.com' normalizeUrl('https://sindresorhus.com/', {removeSingleSlash: false}); //=> 'https://sindresorhus.com/' ``` */ readonly removeSingleSlash?: boolean; /** Removes the default directory index file from path that matches any of the provided strings or regexes. When `true`, the regex `/^index\.[a-z]+$/` is used. @default false @example ``` normalizeUrl('www.sindresorhus.com/foo/default.php', { removeDirectoryIndex: [/^default\.[a-z]+$/] }); //=> 'http://sindresorhus.com/foo' ``` */ readonly removeDirectoryIndex?: boolean | ReadonlyArray; /** Sorts the query parameters alphabetically by key. @default true @example ``` normalizeUrl('www.sindresorhus.com?b=two&a=one&c=three', { sortQueryParameters: false }); //=> 'http://sindresorhus.com/?b=two&a=one&c=three' ``` */ readonly sortQueryParameters?: boolean; } /** [Normalize](https://en.wikipedia.org/wiki/URL_normalization) a URL. @param url - URL to normalize, including [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). @example ``` import normalizeUrl from 'normalize-url'; normalizeUrl('sindresorhus.com'); //=> 'http://sindresorhus.com' normalizeUrl('//www.sindresorhus.com:80/../baz?b=bar&a=foo'); //=> 'http://sindresorhus.com/baz?a=foo&b=bar' ``` */ export default function normalizeUrl(url: string, options?: Options): string; normalize-url-7.1.0/index.js000066400000000000000000000171671430411226300157740ustar00rootroot00000000000000// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs const DATA_URL_DEFAULT_MIME_TYPE = 'text/plain'; const DATA_URL_DEFAULT_CHARSET = 'us-ascii'; const testParameter = (name, filters) => filters.some(filter => filter instanceof RegExp ? filter.test(name) : filter === name); const normalizeDataURL = (urlString, {stripHash}) => { const match = /^data:(?[^,]*?),(?[^#]*?)(?:#(?.*))?$/.exec(urlString); if (!match) { throw new Error(`Invalid URL: ${urlString}`); } let {type, data, hash} = match.groups; const mediaType = type.split(';'); hash = stripHash ? '' : hash; let isBase64 = false; if (mediaType[mediaType.length - 1] === 'base64') { mediaType.pop(); isBase64 = true; } // Lowercase MIME type const mimeType = (mediaType.shift() || '').toLowerCase(); const attributes = mediaType .map(attribute => { let [key, value = ''] = attribute.split('=').map(string => string.trim()); // Lowercase `charset` if (key === 'charset') { value = value.toLowerCase(); if (value === DATA_URL_DEFAULT_CHARSET) { return ''; } } return `${key}${value ? `=${value}` : ''}`; }) .filter(Boolean); const normalizedMediaType = [ ...attributes, ]; if (isBase64) { normalizedMediaType.push('base64'); } if (normalizedMediaType.length > 0 || (mimeType && mimeType !== DATA_URL_DEFAULT_MIME_TYPE)) { normalizedMediaType.unshift(mimeType); } return `data:${normalizedMediaType.join(';')},${isBase64 ? data.trim() : data}${hash ? `#${hash}` : ''}`; }; export default function normalizeUrl(urlString, options) { options = { defaultProtocol: 'http:', normalizeProtocol: true, forceHttp: false, forceHttps: false, stripAuthentication: true, stripHash: false, stripTextFragment: true, stripWWW: true, removeQueryParameters: [/^utm_\w+/i], removeTrailingSlash: true, removeSingleSlash: true, removeDirectoryIndex: false, sortQueryParameters: true, ...options, }; urlString = urlString.trim(); // Data URL if (/^data:/i.test(urlString)) { 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'); } const hasRelativeProtocol = urlString.startsWith('//'); const isRelativeUrl = !hasRelativeProtocol && /^\.*\//.test(urlString); // Prepend protocol if (!isRelativeUrl) { urlString = urlString.replace(/^(?!(?:\w+:)?\/\/)|^\/\//, options.defaultProtocol); } const urlObject = new URL(urlString); if (options.forceHttp && options.forceHttps) { throw new Error('The `forceHttp` and `forceHttps` options cannot be used together'); } if (options.forceHttp && urlObject.protocol === 'https:') { urlObject.protocol = 'http:'; } if (options.forceHttps && urlObject.protocol === 'http:') { urlObject.protocol = 'https:'; } // Remove auth if (options.stripAuthentication) { urlObject.username = ''; urlObject.password = ''; } // Remove hash if (options.stripHash) { urlObject.hash = ''; } else if (options.stripTextFragment) { urlObject.hash = urlObject.hash.replace(/#?:~:text.*?$/i, ''); } // Remove duplicate slashes if not preceded by a protocol // NOTE: This could be implemented using a single negative lookbehind // regex, but we avoid that to maintain compatibility with older js engines // which do not have support for that feature. if (urlObject.pathname) { // TODO: Replace everything below with `urlObject.pathname = urlObject.pathname.replace(/(? 0) { let pathComponents = urlObject.pathname.split('/'); const lastComponent = pathComponents[pathComponents.length - 1]; if (testParameter(lastComponent, options.removeDirectoryIndex)) { pathComponents = pathComponents.slice(0, -1); urlObject.pathname = pathComponents.slice(1).join('/') + '/'; } } if (urlObject.hostname) { // Remove trailing dot urlObject.hostname = urlObject.hostname.replace(/\.$/, ''); // Remove `www.` if (options.stripWWW && /^www\.(?!www\.)[a-z\-\d]{1,63}\.[a-z.\-\d]{2,63}$/.test(urlObject.hostname)) { // Each label should be max 63 at length (min: 1). // Source: https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_host_names // Each TLD should be up to 63 characters long (min: 2). // It is technically possible to have a single character TLD, but none currently exist. urlObject.hostname = urlObject.hostname.replace(/^www\./, ''); } } // Remove query unwanted parameters if (Array.isArray(options.removeQueryParameters)) { // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy. for (const key of [...urlObject.searchParams.keys()]) { if (testParameter(key, options.removeQueryParameters)) { urlObject.searchParams.delete(key); } } } if (!Array.isArray(options.keepQueryParameters) && options.removeQueryParameters === true) { urlObject.search = ''; } // Keep wanted query parameters if (Array.isArray(options.keepQueryParameters) && options.keepQueryParameters.length > 0) { // eslint-disable-next-line unicorn/no-useless-spread -- We are intentionally spreading to get a copy. for (const key of [...urlObject.searchParams.keys()]) { if (!testParameter(key, options.keepQueryParameters)) { urlObject.searchParams.delete(key); } } } // Sort query parameters if (options.sortQueryParameters) { urlObject.searchParams.sort(); // Calling `.sort()` encodes the search parameters, so we need to decode them again. try { urlObject.search = decodeURIComponent(urlObject.search); } catch {} } if (options.removeTrailingSlash) { urlObject.pathname = urlObject.pathname.replace(/\/$/, ''); } const oldUrlString = urlString; // Take advantage of many of the Node `url` normalizations urlString = urlObject.toString(); if (!options.removeSingleSlash && urlObject.pathname === '/' && !oldUrlString.endsWith('/') && urlObject.hash === '') { urlString = urlString.replace(/\/$/, ''); } // Remove ending `/` unless removeSingleSlash is false if ((options.removeTrailingSlash || urlObject.pathname === '/') && urlObject.hash === '' && options.removeSingleSlash) { urlString = urlString.replace(/\/$/, ''); } // Restore relative protocol, if applicable if (hasRelativeProtocol && !options.normalizeProtocol) { urlString = urlString.replace(/^http:\/\//, '//'); } // Remove http/https if (options.stripProtocol) { urlString = urlString.replace(/^(?:https?:)?\/\//, ''); } return urlString; } normalize-url-7.1.0/index.test-d.ts000066400000000000000000000031601430411226300171710ustar00rootroot00000000000000import {expectType} from 'tsd'; import normalizeUrl from './index.js'; expectType(normalizeUrl('sindresorhus.com')); expectType(normalizeUrl('HTTP://xn--xample-hva.com:80/?b=bar&a=foo')); normalizeUrl('//sindresorhus.com:80/', {defaultProtocol: 'https:'}); normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false}); normalizeUrl('https://sindresorhus.com:80/', {forceHttp: true}); normalizeUrl('http://sindresorhus.com:80/', {forceHttps: true}); normalizeUrl('user:password@sindresorhus.com', {stripAuthentication: false}); normalizeUrl('sindresorhus.com/about.html#contact', {stripHash: true}); normalizeUrl('https://sindresorhus.com', {stripProtocol: true}); normalizeUrl('http://www.sindresorhus.com', {stripWWW: false}); // eslint-disable-line @typescript-eslint/naming-convention normalizeUrl('www.sindresorhus.com?foo=bar&ref=test_ref', { removeQueryParameters: ['ref', /test/], }); normalizeUrl('www.sindresorhus.com?foo=bar', { removeQueryParameters: true, }); normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', { removeQueryParameters: false, }); normalizeUrl('www.sindresorhus.com?foo=bar&ref=test_ref', { keepQueryParameters: ['ref', /test/], }); normalizeUrl('http://sindresorhus.com/', {removeTrailingSlash: false}); normalizeUrl('http://sindresorhus.com/', {removeSingleSlash: false}); normalizeUrl('www.sindresorhus.com/foo/default.php', { removeDirectoryIndex: [/^default\.[a-z]+$/, 'foo'], }); normalizeUrl('www.sindresorhus.com?b=two&a=one&c=three', { sortQueryParameters: false, }); normalizeUrl('www.sindresorhus.com/about#:~:text=hello', { stripTextFragment: false, }); normalize-url-7.1.0/license000066400000000000000000000021351430411226300156610ustar00rootroot00000000000000MIT License Copyright (c) Sindre Sorhus (https://sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. normalize-url-7.1.0/package.json000066400000000000000000000015201430411226300165770ustar00rootroot00000000000000{ "name": "normalize-url", "version": "7.1.0", "description": "Normalize a URL", "license": "MIT", "repository": "sindresorhus/normalize-url", "funding": "https://github.com/sponsors/sindresorhus", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "https://sindresorhus.com" }, "type": "module", "exports": "./index.js", "engines": { "node": ">=12.20" }, "scripts": { "test": "xo && c8 ava && tsd" }, "files": [ "index.js", "index.d.ts" ], "keywords": [ "normalize", "url", "uri", "address", "string", "normalization", "normalisation", "query", "querystring", "simplify", "strip", "trim", "canonical" ], "devDependencies": { "ava": "^4.0.1", "c8": "^7.11.0", "tsd": "^0.19.1", "xo": "^0.47.0" }, "c8": { "reporter": [ "text", "lcov" ] } } normalize-url-7.1.0/readme.md000066400000000000000000000167401430411226300161020ustar00rootroot00000000000000# normalize-url [![Coverage Status](https://codecov.io/gh/sindresorhus/normalize-url/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/normalize-url) > [Normalize](https://en.wikipedia.org/wiki/URL_normalization) a URL Useful when you need to display, store, deduplicate, sort, compare, etc, URLs. **Note:** This package does **not** do URL sanitization. [Garbage in, garbage out.](https://en.wikipedia.org/wiki/Garbage_in,_garbage_out) If you use this in a server context and accept URLs as user input, it's up to you to protect against invalid URLs, [path traversal attacks](https://owasp.org/www-community/attacks/Path_Traversal), etc. ## Install ```sh npm install normalize-url ``` *If you need Safari support, use version 4: `npm i normalize-url@4`* ## Usage ```js import normalizeUrl from 'normalize-url'; normalizeUrl('sindresorhus.com'); //=> 'http://sindresorhus.com' normalizeUrl('//www.sindresorhus.com:80/../baz?b=bar&a=foo'); //=> 'http://sindresorhus.com/baz?a=foo&b=bar' ``` ## API ### normalizeUrl(url, options?) #### url Type: `string` URL to normalize, including [data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs). #### options Type: `object` ##### defaultProtocol Type: `string`\ Default: `http:`\ Values: `'https:' | 'http:'` ##### normalizeProtocol Type: `boolean`\ Default: `true` Prepend `defaultProtocol` to the URL if it's protocol-relative. ```js normalizeUrl('//sindresorhus.com:80/'); //=> 'http://sindresorhus.com' normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false}); //=> '//sindresorhus.com' ``` ##### forceHttp Type: `boolean`\ Default: `false` Normalize `https:` to `http:`. ```js normalizeUrl('https://sindresorhus.com:80/'); //=> 'https://sindresorhus.com' normalizeUrl('https://sindresorhus.com:80/', {forceHttp: true}); //=> 'http://sindresorhus.com' ``` ##### forceHttps Type: `boolean`\ Default: `false` Normalize `http:` to `https:`. ```js normalizeUrl('https://sindresorhus.com:80/'); //=> 'https://sindresorhus.com' normalizeUrl('http://sindresorhus.com:80/', {forceHttps: true}); //=> 'https://sindresorhus.com' ``` This option can't be used with the `forceHttp` option at the same time. ##### stripAuthentication Type: `boolean`\ Default: `true` Strip the [authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) part of the URL. ```js normalizeUrl('user:password@sindresorhus.com'); //=> 'https://sindresorhus.com' normalizeUrl('user:password@sindresorhus.com', {stripAuthentication: false}); //=> 'https://user:password@sindresorhus.com' ``` ##### stripHash Type: `boolean`\ Default: `false` Strip the hash part of the URL. ```js normalizeUrl('sindresorhus.com/about.html#contact'); //=> 'http://sindresorhus.com/about.html#contact' normalizeUrl('sindresorhus.com/about.html#contact', {stripHash: true}); //=> 'http://sindresorhus.com/about.html' ``` ##### stripProtocol Type: `boolean`\ Default: `false` Remove the protocol from the URL: `http://sindresorhus.com` → `sindresorhus.com`. It will only remove `https://` and `http://` protocols. ```js normalizeUrl('https://sindresorhus.com'); //=> 'https://sindresorhus.com' normalizeUrl('https://sindresorhus.com', {stripProtocol: true}); //=> 'sindresorhus.com' ``` ##### stripTextFragment Type: `boolean`\ Default: `true` Strip the [text fragment](https://web.dev/text-fragments/) part of the URL. **Note:** The text fragment will always be removed if the `stripHash` option is set to `true`, as the hash contains the text fragment. ```js normalizeUrl('http://sindresorhus.com/about.html#:~:text=hello'); //=> 'http://sindresorhus.com/about.html#' normalizeUrl('http://sindresorhus.com/about.html#section:~:text=hello'); //=> 'http://sindresorhus.com/about.html#section' normalizeUrl('http://sindresorhus.com/about.html#:~:text=hello', {stripTextFragment: false}); //=> 'http://sindresorhus.com/about.html#:~:text=hello' normalizeUrl('http://sindresorhus.com/about.html#section:~:text=hello', {stripTextFragment: false}); //=> 'http://sindresorhus.com/about.html#section:~:text=hello' ``` ##### stripWWW Type: `boolean`\ Default: `true` Remove `www.` from the URL. ```js normalizeUrl('http://www.sindresorhus.com'); //=> 'http://sindresorhus.com' normalizeUrl('http://www.sindresorhus.com', {stripWWW: false}); //=> 'http://www.sindresorhus.com' ``` ##### removeQueryParameters Type: `Array | boolean`\ Default: `[/^utm_\w+/i]` Remove query parameters that matches any of the provided strings or regexes. ```js normalizeUrl('www.sindresorhus.com?foo=bar&ref=test_ref', { removeQueryParameters: ['ref'] }); //=> 'http://sindresorhus.com/?foo=bar' ``` If a boolean is provided, `true` will remove all the query parameters. ```js normalizeUrl('www.sindresorhus.com?foo=bar', { removeQueryParameters: true }); //=> 'http://sindresorhus.com' ``` `false` will not remove any query parameter. ```js normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', { removeQueryParameters: false }); //=> 'http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test' ``` ##### keepQueryParameters Type: `Array`\ Default: `undefined` Keeps only query parameters that matches any of the provided strings or regexes. **Note:** It overrides the `removeQueryParameters` option. ```js normalizeUrl('https://sindresorhus.com?foo=bar&ref=unicorn', { keepQueryParameters: ['ref'] }); //=> 'https://sindresorhus.com/?ref=unicorn' ``` ##### removeTrailingSlash Type: `boolean`\ Default: `true` Remove trailing slash. **Note:** Trailing slash is always removed if the URL doesn't have a pathname unless the `removeSingleSlash` option is set to `false`. ```js normalizeUrl('http://sindresorhus.com/redirect/'); //=> 'http://sindresorhus.com/redirect' normalizeUrl('http://sindresorhus.com/redirect/', {removeTrailingSlash: false}); //=> 'http://sindresorhus.com/redirect/' normalizeUrl('http://sindresorhus.com/', {removeTrailingSlash: false}); //=> 'http://sindresorhus.com' ``` ##### removeSingleSlash Type: `boolean`\ Default: `true` Remove a sole `/` pathname in the output. This option is independent of `removeTrailingSlash`. ```js normalizeUrl('https://sindresorhus.com/'); //=> 'https://sindresorhus.com' normalizeUrl('https://sindresorhus.com/', {removeSingleSlash: false}); //=> 'https://sindresorhus.com/' ``` ##### removeDirectoryIndex Type: `boolean | Array`\ Default: `false` Removes the default directory index file from path that matches any of the provided strings or regexes. When `true`, the regex `/^index\.[a-z]+$/` is used. ```js normalizeUrl('www.sindresorhus.com/foo/default.php', { removeDirectoryIndex: [/^default\.[a-z]+$/] }); //=> 'http://sindresorhus.com/foo' ``` ##### sortQueryParameters Type: `boolean`\ Default: `true` Sorts the query parameters alphabetically by key. ```js normalizeUrl('www.sindresorhus.com?b=two&a=one&c=three', { sortQueryParameters: false }); //=> 'http://sindresorhus.com/?b=two&a=one&c=three' ``` ## Related - [compare-urls](https://github.com/sindresorhus/compare-urls) - Compare URLs by first normalizing them ---
Get professional support for this package with a Tidelift subscription
Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies.
normalize-url-7.1.0/test.js000066400000000000000000000562641430411226300156450ustar00rootroot00000000000000import test from 'ava'; import normalizeUrl from './index.js'; test('main', t => { t.is(normalizeUrl('sindresorhus.com'), 'http://sindresorhus.com'); t.is(normalizeUrl('sindresorhus.com '), 'http://sindresorhus.com'); t.is(normalizeUrl('sindresorhus.com.'), 'http://sindresorhus.com'); t.is(normalizeUrl('SindreSorhus.com'), 'http://sindresorhus.com'); t.is(normalizeUrl('sindresorhus.com', {defaultProtocol: 'https:'}), 'https://sindresorhus.com'); t.is(normalizeUrl('HTTP://sindresorhus.com'), 'http://sindresorhus.com'); t.is(normalizeUrl('//sindresorhus.com'), 'http://sindresorhus.com'); 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'); t.is(normalizeUrl('www.sindresorhus.com'), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/foo/'), 'http://sindresorhus.com/foo'); t.is(normalizeUrl('sindresorhus.com/?foo=bar baz'), 'http://sindresorhus.com/?foo=bar+baz'); t.is(normalizeUrl('https://foo.com/https://bar.com'), 'https://foo.com/https://bar.com'); t.is(normalizeUrl('https://foo.com/https://bar.com/foo//bar'), 'https://foo.com/https://bar.com/foo/bar'); t.is(normalizeUrl('https://foo.com/http://bar.com'), 'https://foo.com/http://bar.com'); t.is(normalizeUrl('https://foo.com/http://bar.com/foo//bar'), 'https://foo.com/http://bar.com/foo/bar'); t.is(normalizeUrl('http://sindresorhus.com/%7Efoo/'), 'http://sindresorhus.com/~foo', 'decode URI octets'); t.is(normalizeUrl('https://foo.com/%FAIL%/07/94/ca/55.jpg'), 'https://foo.com/%FAIL%/07/94/ca/55.jpg'); t.is(normalizeUrl('http://sindresorhus.com/?'), 'http://sindresorhus.com'); t.is(normalizeUrl('êxample.com'), 'http://xn--xample-hva.com'); t.is(normalizeUrl('http://sindresorhus.com/?b=bar&a=foo'), 'http://sindresorhus.com/?a=foo&b=bar'); t.is(normalizeUrl('http://sindresorhus.com/?foo=bar*|<>:"'), 'http://sindresorhus.com/?foo=bar*|%3C%3E:%22'); t.is(normalizeUrl('http://sindresorhus.com:5000'), 'http://sindresorhus.com:5000'); t.is(normalizeUrl('//sindresorhus.com/', {normalizeProtocol: false}), '//sindresorhus.com'); t.is(normalizeUrl('//sindresorhus.com:80/', {normalizeProtocol: false}), '//sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/foo#bar'), 'http://sindresorhus.com/foo#bar'); t.is(normalizeUrl('http://sindresorhus.com/foo#bar', {stripHash: true}), 'http://sindresorhus.com/foo'); 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('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'); 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'); }); test('stripProtocol option', t => { const options = {stripProtocol: true}; t.is(normalizeUrl('http://www.sindresorhus.com', options), 'sindresorhus.com'); 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 => { t.is(normalizeUrl('http://sindresorhus.com'), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/about#'), 'http://sindresorhus.com/about'); t.is(normalizeUrl('http://sindresorhus.com/about#:~:text=hello'), 'http://sindresorhus.com/about'); t.is(normalizeUrl('http://sindresorhus.com/about#main'), 'http://sindresorhus.com/about#main'); t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello'), 'http://sindresorhus.com/about#main'); t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world'), 'http://sindresorhus.com/about#main'); const options = {stripTextFragment: false}; t.is(normalizeUrl('http://sindresorhus.com', options), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/about#:~:text=hello', options), 'http://sindresorhus.com/about#:~:text=hello'); t.is(normalizeUrl('http://sindresorhus.com/about#main', options), 'http://sindresorhus.com/about#main'); t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello', options), 'http://sindresorhus.com/about#main:~:text=hello'); t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world', options), 'http://sindresorhus.com/about#main:~:text=hello%20world'); const options2 = {stripHash: true, stripTextFragment: false}; t.is(normalizeUrl('http://sindresorhus.com', options2), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/about#:~:text=hello', options2), 'http://sindresorhus.com/about'); t.is(normalizeUrl('http://sindresorhus.com/about#main', options2), 'http://sindresorhus.com/about'); t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello', options2), 'http://sindresorhus.com/about'); t.is(normalizeUrl('http://sindresorhus.com/about#main:~:text=hello%20world', options2), 'http://sindresorhus.com/about'); }); test('stripWWW option', t => { const options = {stripWWW: false}; 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'); const options2 = {stripWWW: true}; t.is(normalizeUrl('http://www.vue.amsterdam', options2), 'http://vue.amsterdam'); t.is(normalizeUrl('http://www.sorhus.xx--bck1b9a5dre4c', options2), 'http://sorhus.xx--bck1b9a5dre4c'); const tooLongTLDURL = 'http://www.sorhus.' + ''.padEnd(64, 'a'); t.is(normalizeUrl(tooLongTLDURL, options2), tooLongTLDURL); }); test('removeQueryParameters option', t => { const options = { stripWWW: false, removeQueryParameters: [/^utm_\w+/i, 'ref'], }; t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test'), 'http://sindresorhus.com/?foo=bar'); t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com/?foo=bar'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com/?foo=bar'); }); test('removeQueryParameters boolean `true` option', t => { const options = { stripWWW: false, removeQueryParameters: true, }; t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com'); }); test('removeQueryParameters boolean `false` option', t => { const options = { stripWWW: false, removeQueryParameters: false, }; t.is(normalizeUrl('http://www.sindresorhus.com', options), 'http://www.sindresorhus.com'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com/?foo=bar'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com/?foo=bar&ref=test_ref&utm_medium=test'); }); test('keepQueryParameters option', t => { const options = { stripWWW: false, removeQueryParameters: false, keepQueryParameters: [/^utm_\w+/i, 'ref'], }; t.is(normalizeUrl('https://sindresorhus.com', options), 'https://sindresorhus.com'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar', options), 'http://www.sindresorhus.com'); t.is(normalizeUrl('www.sindresorhus.com?foo=bar&utm_medium=test&ref=test_ref', options), 'http://www.sindresorhus.com/?ref=test_ref&utm_medium=test'); }); test('forceHttp option', t => { const options = {forceHttp: true}; t.is(normalizeUrl('https://sindresorhus.com'), 'https://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com', options), 'http://sindresorhus.com'); t.is(normalizeUrl('https://www.sindresorhus.com', options), 'http://sindresorhus.com'); t.is(normalizeUrl('//sindresorhus.com', options), 'http://sindresorhus.com'); }); test('forceHttp option with forceHttps', t => { t.throws(() => { normalizeUrl('https://www.sindresorhus.com', {forceHttp: true, forceHttps: true}); }, { message: 'The `forceHttp` and `forceHttps` options cannot be used together', }); }); test('forceHttps option', t => { const options = {forceHttps: true}; t.is(normalizeUrl('https://sindresorhus.com'), 'https://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com', options), 'https://sindresorhus.com'); t.is(normalizeUrl('https://www.sindresorhus.com', options), 'https://sindresorhus.com'); t.is(normalizeUrl('//sindresorhus.com', options), 'https://sindresorhus.com'); }); test('removeTrailingSlash option', t => { const options = {removeTrailingSlash: false}; t.is(normalizeUrl('http://sindresorhus.com'), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/'), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com', options), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/', options), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/redirect'), 'http://sindresorhus.com/redirect'); t.is(normalizeUrl('http://sindresorhus.com/redirect/'), 'http://sindresorhus.com/redirect'); t.is(normalizeUrl('http://sindresorhus.com/redirect/', options), 'http://sindresorhus.com/redirect/'); t.is(normalizeUrl('http://sindresorhus.com/redirect/', options), 'http://sindresorhus.com/redirect/'); t.is(normalizeUrl('http://sindresorhus.com/#/'), 'http://sindresorhus.com/#/'); t.is(normalizeUrl('http://sindresorhus.com/#/', options), 'http://sindresorhus.com/#/'); t.is(normalizeUrl('http://sindresorhus.com/?unicorns=true'), 'http://sindresorhus.com/?unicorns=true'); t.is(normalizeUrl('http://sindresorhus.com/?unicorns=true', options), 'http://sindresorhus.com/?unicorns=true'); }); test('removeSingleSlash option', t => { const options = {removeSingleSlash: false}; t.is(normalizeUrl('https://sindresorhus.com', options), 'https://sindresorhus.com'); t.is(normalizeUrl('https://sindresorhus.com/', options), 'https://sindresorhus.com/'); t.is(normalizeUrl('https://sindresorhus.com/redirect', options), 'https://sindresorhus.com/redirect'); t.is(normalizeUrl('https://sindresorhus.com/redirect/', options), 'https://sindresorhus.com/redirect'); t.is(normalizeUrl('https://sindresorhus.com/#/', options), 'https://sindresorhus.com/#/'); t.is(normalizeUrl('https://sindresorhus.com/?unicorns=true', options), 'https://sindresorhus.com/?unicorns=true'); }); test('removeSingleSlash option combined with removeTrailingSlash option', t => { const options = {removeTrailingSlash: false, removeSingleSlash: false}; t.is(normalizeUrl('https://sindresorhus.com', options), 'https://sindresorhus.com'); t.is(normalizeUrl('https://sindresorhus.com/', options), 'https://sindresorhus.com/'); t.is(normalizeUrl('https://sindresorhus.com/redirect', options), 'https://sindresorhus.com/redirect'); t.is(normalizeUrl('https://sindresorhus.com/redirect/', options), 'https://sindresorhus.com/redirect/'); t.is(normalizeUrl('https://sindresorhus.com/#/', options), 'https://sindresorhus.com/#/'); t.is(normalizeUrl('https://sindresorhus.com/?unicorns=true', options), 'https://sindresorhus.com/?unicorns=true'); }); test('removeDirectoryIndex option', t => { const options1 = {removeDirectoryIndex: ['index.html', 'index.php']}; t.is(normalizeUrl('http://sindresorhus.com/index.html'), 'http://sindresorhus.com/index.html'); t.is(normalizeUrl('http://sindresorhus.com/index.html', options1), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/index.htm', options1), 'http://sindresorhus.com/index.htm'); t.is(normalizeUrl('http://sindresorhus.com/index.php', options1), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/path/index.html'), 'http://sindresorhus.com/path/index.html'); t.is(normalizeUrl('http://sindresorhus.com/path/index.html', options1), 'http://sindresorhus.com/path'); t.is(normalizeUrl('http://sindresorhus.com/path/index.htm', options1), 'http://sindresorhus.com/path/index.htm'); t.is(normalizeUrl('http://sindresorhus.com/path/index.php', options1), 'http://sindresorhus.com/path'); t.is(normalizeUrl('http://sindresorhus.com/foo/bar/index.html', options1), 'http://sindresorhus.com/foo/bar'); const options2 = {removeDirectoryIndex: [/^index\.[a-z]+$/, 'remove.html']}; t.is(normalizeUrl('http://sindresorhus.com/index.html'), 'http://sindresorhus.com/index.html'); t.is(normalizeUrl('http://sindresorhus.com/index.html', options2), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/index/index.html', options2), 'http://sindresorhus.com/index'); t.is(normalizeUrl('http://sindresorhus.com/remove.html', options2), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/default.htm', options2), 'http://sindresorhus.com/default.htm'); t.is(normalizeUrl('http://sindresorhus.com/index.php', options2), 'http://sindresorhus.com'); const options3 = {removeDirectoryIndex: true}; t.is(normalizeUrl('http://sindresorhus.com/index.html'), 'http://sindresorhus.com/index.html'); t.is(normalizeUrl('http://sindresorhus.com/index.html', options3), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/index.htm', options3), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/index.php', options3), 'http://sindresorhus.com'); }); test('removeTrailingSlash and removeDirectoryIndex options)', t => { const options1 = { removeTrailingSlash: true, removeDirectoryIndex: true, }; t.is(normalizeUrl('http://sindresorhus.com/path/', options1), 'http://sindresorhus.com/path'); t.is(normalizeUrl('http://sindresorhus.com/path/index.html', options1), 'http://sindresorhus.com/path'); t.is(normalizeUrl('http://sindresorhus.com/#/path/', options1), 'http://sindresorhus.com/#/path/'); t.is(normalizeUrl('http://sindresorhus.com/foo/#/bar/', options1), 'http://sindresorhus.com/foo#/bar/'); const options2 = { removeTrailingSlash: false, removeDirectoryIndex: true, }; t.is(normalizeUrl('http://sindresorhus.com/path/', options2), 'http://sindresorhus.com/path/'); t.is(normalizeUrl('http://sindresorhus.com/path/index.html', options2), 'http://sindresorhus.com/path/'); t.is(normalizeUrl('http://sindresorhus.com/#/path/', options2), 'http://sindresorhus.com/#/path/'); }); test('sortQueryParameters option', t => { const options1 = { sortQueryParameters: true, }; t.is(normalizeUrl('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W', options1), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); t.is(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options1), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); t.is(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options1), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); t.is(normalizeUrl('http://sindresorhus.com/', options1), 'http://sindresorhus.com'); const options2 = { sortQueryParameters: false, }; t.is(normalizeUrl('http://sindresorhus.com/?a=Z&b=Y&c=X&d=W', options2), 'http://sindresorhus.com/?a=Z&b=Y&c=X&d=W'); t.is(normalizeUrl('http://sindresorhus.com/?b=Y&c=X&a=Z&d=W', options2), 'http://sindresorhus.com/?b=Y&c=X&a=Z&d=W'); t.is(normalizeUrl('http://sindresorhus.com/?a=Z&d=W&b=Y&c=X', options2), 'http://sindresorhus.com/?a=Z&d=W&b=Y&c=X'); t.is(normalizeUrl('http://sindresorhus.com/', options2), 'http://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/?a=/path', options1), normalizeUrl('http://sindresorhus.com/?a=/path', options2)); }); test('invalid urls', t => { t.throws(() => { normalizeUrl('http://'); }, { message: /^Invalid URL/, }); t.throws(() => { normalizeUrl('/'); }, { message: /^Invalid URL/, }); t.throws(() => { normalizeUrl('/relative/path/'); }, { message: /^Invalid URL/, }); }); test('remove duplicate pathname slashes', t => { t.is(normalizeUrl('http://sindresorhus.com////foo/bar'), 'http://sindresorhus.com/foo/bar'); t.is(normalizeUrl('http://sindresorhus.com////foo////bar'), 'http://sindresorhus.com/foo/bar'); t.is(normalizeUrl('//sindresorhus.com//foo', {normalizeProtocol: false}), '//sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com:5000///foo'), 'http://sindresorhus.com:5000/foo'); t.is(normalizeUrl('http://sindresorhus.com///foo'), 'http://sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com:5000//foo'), 'http://sindresorhus.com:5000/foo'); t.is(normalizeUrl('http://sindresorhus.com//foo'), 'http://sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com/s3://sindresorhus.com'), 'http://sindresorhus.com/s3://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/s3://sindresorhus.com//foo'), 'http://sindresorhus.com/s3://sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com//foo/s3://sindresorhus.com'), 'http://sindresorhus.com/foo/s3://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/git://sindresorhus.com'), 'http://sindresorhus.com/git://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/git://sindresorhus.com//foo'), 'http://sindresorhus.com/git://sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com//foo/git://sindresorhus.com//foo'), 'http://sindresorhus.com/foo/git://sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com/a://sindresorhus.com//foo'), 'http://sindresorhus.com/a:/sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo'), 'http://sindresorhus.com/alongprotocolwithin50charlimitxxxxxxxxxxxxxxxxxxxx://sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx://sindresorhus.com//foo'), 'http://sindresorhus.com/alongprotocolexceeds50charlimitxxxxxxxxxxxxxxxxxxxxx:/sindresorhus.com/foo'); t.is(normalizeUrl('http://sindresorhus.com/a2-.+://sindresorhus.com'), 'http://sindresorhus.com/a2-.+://sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/a2-.+_://sindresorhus.com'), 'http://sindresorhus.com/a2-.+_:/sindresorhus.com'); t.is(normalizeUrl('http://sindresorhus.com/2abc://sindresorhus.com'), 'http://sindresorhus.com/2abc:/sindresorhus.com'); }); test('data URL', t => { // Invalid URL. t.throws(() => { normalizeUrl('data:'); }, { message: 'Invalid URL: data:', }); // Strip default MIME type t.is(normalizeUrl('data:text/plain,foo'), 'data:,foo'); // Strip default charset t.is(normalizeUrl('data:;charset=us-ascii,foo'), 'data:,foo'); // Normalize away trailing semicolon. t.is(normalizeUrl('data:;charset=UTF-8;,foo'), 'data:;charset=utf-8,foo'); // Empty MIME type. t.is(normalizeUrl('data:,'), 'data:,'); // Empty MIME type with charset. t.is(normalizeUrl('data:;charset=utf-8,foo'), 'data:;charset=utf-8,foo'); // Lowercase the MIME type. t.is(normalizeUrl('data:TEXT/HTML,foo'), 'data:text/html,foo'); // Strip empty hash. t.is(normalizeUrl('data:,foo# '), 'data:,foo'); // Key only mediaType attribute. t.is(normalizeUrl('data:;foo=;bar,'), 'data:;foo;bar,'); // Lowercase the charset. t.is(normalizeUrl('data:;charset=UTF-8,foo'), 'data:;charset=utf-8,foo'); // Remove spaces after the comma when it's base64. t.is(normalizeUrl('data:;base64, Zm9v #foo #bar'), 'data:;base64,Zm9v#foo #bar'); // Keep spaces when it's not base64. t.is(normalizeUrl('data:, foo #bar'), 'data:, foo #bar'); // Options. const options = { defaultProtocol: 'http:', normalizeProtocol: true, forceHttp: true, stripHash: true, stripWWW: true, stripProtocol: true, removeQueryParameters: [/^utm_\w+/i, 'ref'], sortQueryParameters: true, removeTrailingSlash: true, removeDirectoryIndex: true, }; t.is(normalizeUrl('data:,sindresorhus.com/', options), 'data:,sindresorhus.com/'); t.is(normalizeUrl('data:,sindresorhus.com/index.html', options), 'data:,sindresorhus.com/index.html'); t.is(normalizeUrl('data:,sindresorhus.com?foo=bar&a=a&utm_medium=test', options), 'data:,sindresorhus.com?foo=bar&a=a&utm_medium=test'); t.is(normalizeUrl('data:,foo#bar', options), 'data:,foo'); t.is(normalizeUrl('data:,www.sindresorhus.com', options), 'data:,www.sindresorhus.com'); }); test('prevents homograph attack', t => { // The input string uses Unicode to make it look like a valid `ebay.com` URL. 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'; const start = Date.now(); try { normalizeUrl(url); } catch {} const difference = Date.now() - start; t.true(difference < 100, `Execution time: ${difference}`); } });