Skip to content

Commit

Permalink
Merge pull request #202 from thornbill/fix-normalize-version-madness
Browse files Browse the repository at this point in the history
Add normalize-url utility
  • Loading branch information
thornbill authored Jul 14, 2022
2 parents 0ea72a2 + af787b4 commit 9cb7ef0
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 18 deletions.
3 changes: 0 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ updates:
interval: weekly
open-pull-requests-limit: 10
rebase-strategy: disabled
ignore:
- dependency-name: normalize-url
update-types: [ version-update:semver-major ]

- package-ecosystem: github-actions
directory: /
Expand Down
3 changes: 0 additions & 3 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ module.exports = {
testEnvironment: 'node',
runner: 'groups',

// normalize-url exports an ES module so we need to transform js files
// and change the ignore pattern so it is transformed
preset: 'ts-jest/presets/js-with-ts',
globals: {
'ts-jest': {
tsconfig: { allowJs: true }
}
},
transformIgnorePatterns: [ '/node_modules/(?!(normalize-url)/)' ],

// Coverage options
collectCoverageFrom: [ 'src/**' ],
Expand Down
5 changes: 0 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
},
"dependencies": {
"axios": "^0.27.2",
"compare-versions": "^4.0.0",
"normalize-url": "^6.1.0"
"compare-versions": "^4.0.0"
}
}
5 changes: 2 additions & 3 deletions src/utils/__tests__/address-candidates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,9 @@ describe('Address Candidates', () => {
expect(candidates[1]).toBe('http://example.com:8888/');
});

it('should return the entered url non http(s) protocols', () => {
it('should return an empty list for urls with non http(s) protocols', () => {
const candidates = getAddressCandidates('ftp://example.com');
expect(candidates).toHaveLength(1);
expect(candidates[0]).toBe('ftp://example.com/');
expect(candidates).toHaveLength(0);
});

it('should return an empty list for invalid urls', () => {
Expand Down
53 changes: 53 additions & 0 deletions src/utils/__tests__/normalize-url.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import normalizeUrl from '../normalize-url';

/**
* Url normalizing tests.
*
* @group unit/utils
*/
describe('Normalize URLs', () => {
it('should normalize URLs correctly', () => {
expect(normalizeUrl('http://example.com/')).toBe('http://example.com/');
expect(normalizeUrl('http://example.com')).toBe('http://example.com/');
expect(normalizeUrl('https://example.com/')).toBe('https://example.com/');
expect(normalizeUrl('http://example.com/foo/bar/')).toBe('http://example.com/foo/bar');

// Don't reduce double slashes if part of a protocol
expect(normalizeUrl('https://example.com/https://jellyfin.org')).toBe('https://example.com/https://jellyfin.org');
expect(normalizeUrl('https://example.com/https://jellyfin.org/foo//bar')).toBe('https://example.com/https://jellyfin.org/foo/bar');
expect(normalizeUrl('https://example.com/http://jellyfin.org')).toBe('https://example.com/http://jellyfin.org');
expect(normalizeUrl('https://example.com/http://jellyfin.org/foo//bar')).toBe('https://example.com/http://jellyfin.org/foo/bar');

// Strip trailing dots in domain names
expect(normalizeUrl('http://example.com./')).toBe('http://example.com/');

// Strip hashes from URLs
expect(normalizeUrl('http://example.com/#/hash/path')).toBe('http://example.com/');
});

it('should default to using http protocol when not specified', () => {
expect(normalizeUrl('//example.com/')).toBe('http://example.com/');
expect(normalizeUrl('example.com/')).toBe('http://example.com/');
expect(normalizeUrl('example.com')).toBe('http://example.com/');
});

it('should throw for non http(s) protocols', () => {
expect(() => {
normalizeUrl('data:ASDF');
}).toThrow('data URLs are not supported');

expect(() => {
normalizeUrl('view-source:example.com');
}).toThrow('`view-source:` is not supported as it is a non-standard protocol');

expect(() => {
normalizeUrl('ftp://example.com');
}).toThrow('only http or https protocols are supported');
});
});
115 changes: 115 additions & 0 deletions src/utils/normalize-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* MIT License
*
* Copyright (c) 2022 Jellyfin Contributors
* Copyright (c) 2015 - 2022 Sindre Sorhus <[email protected]> (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.
*/

import { HTTP_PROTOCOL } from './url';

/*
* A fork of https://github.com/sindresorhus/normalize-url ported to typescript with all unneeded features removed.
* This was necessary due to v7 only providing ES module builds that are poorly supported and v6 using poorly supported
* regex features.
*/

export default function normalizeUrl(urlString: string): string {
urlString = urlString.trim();

// Data URL
if (/^data:/i.test(urlString)) {
throw new Error('data URLs are not supported');
}

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+:)?\/\/)|^\/\//, HTTP_PROTOCOL);
}

if (!/^https?:/i.test(urlString)) {
throw new Error('only http or https protocols are supported');
}

const urlObject = new URL(urlString);

// Remove hash
urlObject.hash = '';

// 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(/(?<!\b[a-z][a-z\d+\-.]{1,50}:)\/{2,}/g, '/');` when Safari supports negative lookbehind.

// Split the string by occurrences of this protocol regex, and perform
// duplicate-slash replacement on the strings between those occurrences
// (if any).
const protocolRegex = /\b[a-z][a-z\d+\-.]{1,50}:\/\//g;

let lastIndex = 0;
let result = '';
for (;;) {
const match = protocolRegex.exec(urlObject.pathname);
if (!match) {
break;
}

const protocol = match[0];
const protocolAtIndex = match.index;
const intermediate = urlObject.pathname.slice(lastIndex, protocolAtIndex);

result += intermediate.replace(/\/{2,}/g, '/');
result += protocol;
lastIndex = protocolAtIndex + protocol.length;
}

const remnant = urlObject.pathname.slice(lastIndex, urlObject.pathname.length);
result += remnant.replace(/\/{2,}/g, '/');

urlObject.pathname = result;
}

// Decode URI octets
if (urlObject.pathname) {
try {
urlObject.pathname = decodeURI(urlObject.pathname);
} catch { /* swallow errors */ }
}

if (urlObject.hostname) {
// Remove trailing dot
urlObject.hostname = urlObject.hostname.replace(/\.$/, '');
}

// Remove query parameters
urlObject.search = '';

// Remove a trailing slash
urlObject.pathname = urlObject.pathname.replace(/\/$/, '');

// Take advantage of many of the WHATWG URL normalizations
urlString = urlObject.toString();

return urlString;
}
4 changes: 2 additions & 2 deletions src/utils/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/

import normalizeUrl from 'normalize-url';
import normalizeUrl from './normalize-url';

/** The http protocol string. */
export const HTTP_PROTOCOL = 'http:';
Expand Down Expand Up @@ -49,6 +49,6 @@ export function parseUrl(input: string): URL {
}

return new URL(
normalizeUrl(input, { stripWWW: false })
normalizeUrl(input)
);
}

0 comments on commit 9cb7ef0

Please sign in to comment.