diff --git a/package-lock.json b/package-lock.json index 7dc3baed80..c28a756d59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,6 +166,12 @@ "@types/node": "*" } }, + "@types/tldjs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@types/tldjs/-/tldjs-2.3.0.tgz", + "integrity": "sha512-+gqspH/N6YjpApp96/XzM2AZK4R0Bk2qb4e5o16indSvgblfFaAIxNV8BdJmbqfSAYUyZubLzvrmpvdVEmBq3A==", + "dev": true + }, "@types/webcrypto": { "version": "0.0.28", "resolved": "https://registry.npmjs.org/@types/webcrypto/-/webcrypto-0.0.28.tgz", @@ -5936,8 +5942,7 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qjobs": { "version": "1.2.0", @@ -7087,6 +7092,14 @@ "setimmediate": "^1.0.4" } }, + "tldjs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tldjs/-/tldjs-2.3.1.tgz", + "integrity": "sha512-W/YVH/QczLUxVjnQhFC61Iq232NWu3TqDdO0S/MtXVz4xybejBov4ud+CIwN9aYqjOecEqIy0PscGkwpG9ZyTw==", + "requires": { + "punycode": "^1.4.1" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", diff --git a/package.json b/package.json index 2647556ef8..305763fe70 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@types/node-fetch": "^2.1.2", "@types/node-forge": "^0.7.5", "@types/papaparse": "^4.5.3", + "@types/tldjs": "^2.3.0", "@types/webcrypto": "0.0.28", "concurrently": "3.5.1", "electron": "2.0.11", @@ -80,6 +81,7 @@ "node-forge": "0.7.6", "papaparse": "4.6.0", "rxjs": "6.3.2", + "tldjs": "2.3.1", "zone.js": "0.8.26" } } diff --git a/spec/common/misc/utils.spec.ts b/spec/common/misc/utils.spec.ts index adbe753718..3638b5964c 100644 --- a/spec/common/misc/utils.spec.ts +++ b/spec/common/misc/utils.spec.ts @@ -1,6 +1,35 @@ import { Utils } from '../../../src/misc/utils'; describe('Utils Service', () => { + describe('getDomain', () => { + it('should fail for invalid urls', () => { + expect(Utils.getDomain(null)).toBeNull(); + expect(Utils.getDomain(undefined)).toBeNull(); + expect(Utils.getDomain(' ')).toBeNull(); + expect(Utils.getDomain('https://bit!:"_&ward.com')).toBeNull(); + expect(Utils.getDomain('bitwarden')).toBeNull(); + }); + + it('should handle urls without protocol', () => { + expect(Utils.getDomain('bitwarden.com')).toBe('bitwarden.com'); + expect(Utils.getDomain('wrong://bitwarden.com')).toBe('bitwarden.com'); + }); + + it('should handle valid urls', () => { + expect(Utils.getDomain('https://bitwarden')).toBe('bitwarden'); + expect(Utils.getDomain('https://bitwarden.com')).toBe('bitwarden.com'); + expect(Utils.getDomain('http://bitwarden.com')).toBe('bitwarden.com'); + expect(Utils.getDomain('http://vault.bitwarden.com')).toBe('bitwarden.com'); + expect(Utils.getDomain('https://user:password@bitwarden.com:8080/password/sites?and&query#hash')).toBe('bitwarden.com'); + expect(Utils.getDomain('https://bitwarden.unknown')).toBe('bitwarden.unknown'); + }); + + it('should support localhost and IP', () => { + expect(Utils.getDomain('https://localhost')).toBe('localhost'); + expect(Utils.getDomain('https://192.168.1.1')).toBe('192.168.1.1'); + }); + }); + describe('getHostname', () => { it('should fail for invalid urls', () => { expect(Utils.getHostname(null)).toBeNull(); diff --git a/src/abstractions/platformUtils.service.ts b/src/abstractions/platformUtils.service.ts index e33b97db54..14131a13c4 100644 --- a/src/abstractions/platformUtils.service.ts +++ b/src/abstractions/platformUtils.service.ts @@ -13,7 +13,6 @@ export abstract class PlatformUtilsService { isIE: () => boolean; isMacAppStore: () => boolean; analyticsId: () => string; - getDomain: (uriString: string) => string; isViewOpen: () => boolean; lockTimeout: () => number; launchUri: (uri: string, options?: any) => void; diff --git a/src/electron/services/electronPlatformUtils.service.ts b/src/electron/services/electronPlatformUtils.service.ts index c243558c92..536de73617 100644 --- a/src/electron/services/electronPlatformUtils.service.ts +++ b/src/electron/services/electronPlatformUtils.service.ts @@ -17,7 +17,6 @@ import { MessagingService } from '../../abstractions/messaging.service'; import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; import { AnalyticsIds } from '../../misc/analytics'; -import { Utils } from '../../misc/utils'; export class ElectronPlatformUtilsService implements PlatformUtilsService { identityClientId: string; @@ -99,10 +98,6 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService { return this.analyticsIdCache; } - getDomain(uriString: string): string { - return Utils.getHostname(uriString); - } - isViewOpen(): boolean { return false; } diff --git a/src/misc/utils.ts b/src/misc/utils.ts index 0b5c86dc25..2760d2af6c 100644 --- a/src/misc/utils.ts +++ b/src/misc/utils.ts @@ -1,3 +1,5 @@ +import * as tldjs from 'tldjs'; + import { I18nService } from '../abstractions/i18n.service'; // tslint:disable-next-line @@ -163,6 +165,36 @@ export class Utils { } } + static getDomain(uriString: string): string { + if (uriString == null) { + return null; + } + + uriString = uriString.trim(); + if (uriString === '') { + return null; + } + + if (uriString.startsWith('http://') || uriString.startsWith('https://')) { + try { + const url = Utils.getUrlObject(uriString); + if (url.hostname === 'localhost' || Utils.validIpAddress(url.hostname)) { + return url.hostname; + } + + const urlDomain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(url.hostname) : null; + return urlDomain != null ? urlDomain : url.hostname; + } catch (e) { } + } + + const domain = tldjs != null && tldjs.getDomain != null ? tldjs.getDomain(uriString) : null; + if (domain != null) { + return domain; + } + + return null; + } + static getQueryParams(uriString: string): Map { const url = Utils.getUrl(uriString); if (url == null || url.search == null || url.search === '') { @@ -197,6 +229,12 @@ export class Utils { }; } + private static validIpAddress(ipString: string): boolean { + // tslint:disable-next-line + const ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + return ipRegex.test(ipString); + } + private static isMobile(win: Window) { let mobile = false; ((a) => { @@ -225,6 +263,10 @@ export class Utils { return null; } + return Utils.getUrlObject(uriString); + } + + private static getUrlObject(uriString: string): URL { try { if (nodeURL != null) { return nodeURL.URL ? new nodeURL.URL(uriString) : nodeURL.parse(uriString); diff --git a/src/models/view/loginUriView.ts b/src/models/view/loginUriView.ts index 476067959a..c402c6fb3b 100644 --- a/src/models/view/loginUriView.ts +++ b/src/models/view/loginUriView.ts @@ -4,8 +4,6 @@ import { View } from './view'; import { LoginUri } from '../domain/loginUri'; -import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; - import { Utils } from '../../misc/utils'; export class LoginUriView implements View { @@ -35,15 +33,9 @@ export class LoginUriView implements View { get domain(): string { if (this._domain == null && this.uri != null) { - const containerService = (Utils.global as any).bitwardenContainerService; - if (containerService) { - const platformUtilsService: PlatformUtilsService = containerService.getPlatformUtilsService(); - this._domain = platformUtilsService.getDomain(this.uri); - if (this._domain === '') { - this._domain = null; - } - } else { - throw new Error('global bitwardenContainerService not initialized.'); + this._domain = Utils.getDomain(this.uri); + if (this._domain === '') { + this._domain = null; } } diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts index b201c171bd..2ceb801dd2 100644 --- a/src/services/cipher.service.ts +++ b/src/services/cipher.service.ts @@ -37,7 +37,6 @@ import { ApiService } from '../abstractions/api.service'; import { CipherService as CipherServiceAbstraction } from '../abstractions/cipher.service'; import { CryptoService } from '../abstractions/crypto.service'; import { I18nService } from '../abstractions/i18n.service'; -import { PlatformUtilsService } from '../abstractions/platformUtils.service'; import { SearchService } from '../abstractions/search.service'; import { SettingsService } from '../abstractions/settings.service'; import { StorageService } from '../abstractions/storage.service'; @@ -59,7 +58,7 @@ export class CipherService implements CipherServiceAbstraction { constructor(private cryptoService: CryptoService, private userService: UserService, private settingsService: SettingsService, private apiService: ApiService, private storageService: StorageService, private i18nService: I18nService, - private platformUtilsService: PlatformUtilsService, private searchService: () => SearchService) { + private searchService: () => SearchService) { } get decryptedCipherCache() { @@ -311,7 +310,7 @@ export class CipherService implements CipherServiceAbstraction { return Promise.resolve([]); } - const domain = this.platformUtilsService.getDomain(url); + const domain = Utils.getDomain(url); const eqDomainsPromise = domain == null ? Promise.resolve([]) : this.settingsService.getEquivalentDomains().then((eqDomains: any[][]) => { let matches: any[] = []; diff --git a/src/services/container.service.ts b/src/services/container.service.ts index 10e3f6390e..08f428ad0f 100644 --- a/src/services/container.service.ts +++ b/src/services/container.service.ts @@ -1,9 +1,7 @@ import { CryptoService } from '../abstractions/crypto.service'; -import { PlatformUtilsService } from '../abstractions/platformUtils.service'; export class ContainerService { - constructor(private cryptoService: CryptoService, - private platformUtilsService: PlatformUtilsService) { + constructor(private cryptoService: CryptoService) { } // deprecated, use attachToGlobal instead @@ -20,8 +18,4 @@ export class ContainerService { getCryptoService(): CryptoService { return this.cryptoService; } - - getPlatformUtilsService(): PlatformUtilsService { - return this.platformUtilsService; - } }