Change:
Issue Type: <bFeature Request</bPlease allow us to delete trusted domains for link protection. Selecting a domain and hitting Backspace and Delete don't do anything. Even better would be to have an option to reset the list of links. VS Code version: Code 1.38.0 (, 2019-09-03T21:47:00.464Z) OS version: Darwin x64 18.7.0 <!-- generated by issue reporter --
(Experimental duplicate detection) Thanks for submitting this issue. Please also check if it is already covered by an existing one, like: <!-- score: 0.541 --- <!-- score: 0.509 --- <!-- score: 0.492 --<!-- potentialduplicatescomment --
If you run , you can toggle off domains to delete them. Does that not work for you?
Okay thanks, that does indeed work. I thought it would just be a simple toggle. Where it this data located? It would be nice to have it as an array in the VSC , like most of the other permanent settings.
For security purposes, we decided to not put it in , which is writable by any extensions.
Is there any way to reset the list, other than doing it manually?
Double click the upper left checkbox should unselect all — or do you want reset instead of clear?
I would like an option to reset the list, so that if I a bunch of custom URLs, then resetting the list would remove my custom URLs, and re-add any of the default URLs that I removed, if any.
See

Discussion:
} /** <del> * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. </del> <ins> * Localize a message. * * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` * For example, `localize('hello {0}', name)` </ins> */ export declare function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; /** <del> * Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`. </del> <ins> * Localize a message. * * `message` can contain `{n}` notation where it is replaced by the nth value in `...args` * For example, `localize('hello {0}', name)` </ins> */ export declare function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { URI } from 'vs/base/common/uri'; import { readTrustedDomains } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { IProductService } from 'vs/platform/product/common/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; const TRUSTED_DOMAINS_URI = URI.parse('trustedDomains:/Trusted Domains'); export const configureTrustedDomainSettingsCommand = { id: 'workbench.action.configureTrustedDomain', description: { description: localize('trustedDomain.configureTrustedDomain', 'Configure Trusted Domains'), args: [] }, handler: async (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' }); return; } }; export const toggleLinkProtection = { id: 'workbench.action.toggleLinkProtection', description: { description: localize( 'trustedDomain.toggleLinkProtectionForTrustedDomains', 'Toggle Link Protection for Trusted Domains' ), args: [] }, handler: async (accessor: ServicesAccessor) => { const storageService = accessor.get(IStorageService); const notificationService = accessor.get(INotificationService); const trustedDomains = readTrustedDomains(storageService, accessor.get(IProductService)); if (trustedDomains.indexOf('*') === -1) { storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomains.concat(['*'])), StorageScope.GLOBAL ); notificationService.info(localize('trustedDomain.linkProtectionDisabled', 'Link Protection disabled')); } else { storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomains.filter(x => x !== '*')), StorageScope.GLOBAL ); notificationService.info(localize('trustedDomain.linkProtectionEnabled', 'Link Protection enabled')); } return; } }; export async function configureOpenerTrustedDomainsHandler( trustedDomains: string[], domainToConfigure: string, quickInputService: IQuickInputService, storageService: IStorageService, editorService: IEditorService ) { const openAllLinksItem: IQuickPickItem = { type: 'item', label: localize('trustedDomain.trustAllAndOpenLink', 'Disable Link Protection and open link'), id: '*', picked: trustedDomains.indexOf('*') !== -1 }; const trustDomainAndOpenLinkItem: IQuickPickItem = { type: 'item', label: localize('trustedDomain.trustDomainAndOpenLink', 'Trust {0} and open link', domainToConfigure), id: domainToConfigure, picked: true }; const configureTrustedDomainItem: IQuickPickItem = { type: 'item', label: localize('trustedDomain.configureTrustedDomains', 'Configure Trusted Domains'), id: 'configure' }; const pickedResult = await quickInputService.pick( [openAllLinksItem, trustDomainAndOpenLinkItem, configureTrustedDomainItem], { activeItem: trustDomainAndOpenLinkItem } ); if (pickedResult) { if (pickedResult.id === 'configure') { editorService.openEditor({ resource: TRUSTED_DOMAINS_URI, mode: 'jsonc' }); return trustedDomains; } if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) { storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify([...trustedDomains, pickedResult.id]), StorageScope.GLOBAL ); return [...trustedDomains, pickedResult.id]; } } return []; } </ins>

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; import { parse } from 'vs/base/common/json'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileService, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; import { IProductService } from 'vs/platform/product/common/product'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; const TRUSTED_DOMAINS_SCHEMA = 'trustedDomains'; const TRUSTED_DOMAINS_STAT: IStat = { type: FileType.File, ctime: Date.now(), mtime: Date.now(), size: 0 }; const CONFIG_HELP_TEXT = `// Configure trusted domains by editing and saving this file // You can use `*` to match subdomains. For example https://*.visualstudio.com would match: // - https://code.visualstudio.com // - https://update.code.visualstudio.com `; export class TrustedDomainsFileSystemProvider implements IFileSystemProvider, IWorkbenchContribution { readonly capabilities = FileSystemProviderCapabilities.FileReadWrite; readonly onDidChangeCapabilities = Event.None; readonly onDidChangeFile = Event.None; constructor( @IFileService private readonly fileService: IFileService, @IStorageService private readonly storageService: IStorageService ) { this.fileService.registerProvider(TRUSTED_DOMAINS_SCHEMA, this); } stat(resource: URI): Promise<IStat> { return Promise.resolve(TRUSTED_DOMAINS_STAT); } readFile(resource: URI): Promise<Uint8Array> { let trustedDomains: string[] = []; try { const trustedDomainsSrc = this.storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } const trustedDomainsContent = CONFIG_HELP_TEXT + JSON.stringify(trustedDomains, null, 2); const buffer = Buffer.from(trustedDomainsContent, 'utf-8'); return Promise.resolve(buffer); } writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> { let trustedDomainsd = []; try { trustedDomainsd = parse(content.toString()); } catch (err) { } this.storageService.store( 'http.linkProtectionTrustedDomains', JSON.stringify(trustedDomainsd), StorageScope.GLOBAL ); return Promise.resolve(); } watch(resource: URI, opts: IWatchOptions): IDisposable { return { dispose() { return; } }; } mkdir(resource: URI): Promise<void> { return Promise.resolve(undefined!); } readdir(resource: URI): Promise<[string, FileType][]> { return Promise.resolve(undefined!); } delete(resource: URI, opts: FileDeleteOptions): Promise<void> { return Promise.resolve(undefined!); } rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> { return Promise.resolve(undefined!); } } </ins>

<ins> /*--------------------------------------------------------------------------------------------- *  Copyright (c) Microsoft Corporation. All rights reserved. *  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/product'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { configureOpenerTrustedDomainsHandler } from 'vs/workbench/contrib/url/common/trustedDomains'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; export class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @IOpenerService private readonly _openerService: IOpenerService, @IStorageService private readonly _storageService: IStorageService, @IDialogService private readonly _dialogService: IDialogService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService, @IEditorService private readonly _editorService: IEditorService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); } async validateLink(resource: URI): Promise<boolean> { const { scheme, authority } = resource; if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { return true; } const domainToOpen = `${scheme}://${authority}`; const trustedDomains = readTrustedDomains(this._storageService, this._productService); if (isURLDomainTrusted(resource, trustedDomains)) { return true; } else { const { choice } = await this._dialogService.show( Severity.Info, localize( 'openExternalLinkAt', 'Do you want {0} to open the external website?n{1}', this._productService.nameShort, resource.toString(true) ), [ localize('openLink', 'Open Link'), localize('cancel', 'Cancel'), localize('configureTrustedDomains', 'Configure Trusted Domains') ], { cancelId: 1 } ); // Open Link if (choice === 0) { return true; } // Configure Trusted Domains else if (choice === 2) { const pickedDomains = await configureOpenerTrustedDomainsHandler( trustedDomains, domainToOpen, this._quickInputService, this._storageService, this._editorService ); // Trust all domains if (pickedDomains.indexOf('*') !== -1) { return true; } // Trust current domain if (pickedDomains.indexOf(domainToOpen) !== -1) { return true; } return false; } return false; } } } export function readTrustedDomains(storageService: IStorageService, productService: IProductService) { let trustedDomains: string[] = productService.linkProtectionTrustedDomains ? [...productService.linkProtectionTrustedDomains] : []; try { const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } return trustedDomains; } const rLocalhost = /^localhost(:d+)?$/i; const r127 = /^127.0.0.1(:d+)?$/; function isLocalhostAuthority(authority: string) { return rLocalhost.test(authority) || r127.test(authority); } /** * Check whether a domain like https://www.microsoft.com matches * the list of trusted domains. * * - Schemes must match * - There's no subdomain matching. For example https://microsoft.com doesn't match https://www.microsoft.com * - Star matches all subdomains. For example https://*.microsoft.com matches https://www.microsoft.com and https://foo.bar.microsoft.com */ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { if (isLocalhostAuthority(url.authority)) { return true; } const domain = `${url.scheme}://${url.authority}`; for (let i = 0; i < trustedDomains.length; i++) { if (trustedDomains[i] === '*') { return true; } if (trustedDomains[i] === domain) { return true; } if (trustedDomains[i].indexOf('*') !== -1) { const parsedTrustedDomain = URI.parse(trustedDomains[i]); if (url.scheme === parsedTrustedDomain.scheme) { let reversedAuthoritySegments = url.authority.split('.').reverse(); const reversedTrustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.').reverse(); if ( reversedTrustedDomainAuthoritySegments.length < reversedAuthoritySegments.length && reversedTrustedDomainAuthoritySegments[reversedTrustedDomainAuthoritySegments.length - 1] === '*' ) { reversedAuthoritySegments = reversedAuthoritySegments.slice(0, reversedTrustedDomainAuthoritySegments.length); } if ( reversedAuthoritySegments.every((val, i) => { return reversedTrustedDomainAuthoritySegments[i] === '*' || val === reversedTrustedDomainAuthoritySegments[i]; }) ) { return true; } } } } return false; } </ins>

*  Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ <del> import { localize } from 'vs/nls'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IURLService } from 'vs/platform/url/common/url'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; </del> import { Action } from 'vs/base/common/actions'; <del> import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; </del> <ins> import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; </ins> import { CommandsRegistry } from 'vs/platform/commands/common/commands'; <del> import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; </del> import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; <del> import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IProductService } from 'vs/platform/product/common/product'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; </del> <ins> import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { configureTrustedDomainSettingsCommand, toggleLinkProtection } from 'vs/workbench/contrib/url/common/trustedDomains'; import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider'; </ins> export class OpenUrlAction extends Action { static readonly ID = 'workbench.action.url.openUrl';

localize('developer', 'Developer') ); <del> const configureTrustedDomainsHandler = async ( quickInputService: IQuickInputService, storageService: IStorageService, linkProtectionTrustedDomains: string[], domainToConfigure?: string ) => { try { const trustedDomainsSrc = storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { linkProtectionTrustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } const domainQuickPickItems: IQuickPickItem[] = linkProtectionTrustedDomains .filter(d => d !== '*') .map(d => { return { type: 'item', label: d, id: d, picked: true }; }); const specialQuickPickItems: IQuickPickItem[] = [ { type: 'item', label: localize('openAllLinksWithoutPrompt', 'Open all links without prompt'), id: '*', picked: linkProtectionTrustedDomains.indexOf('*') !== -1 } ]; let domainToConfigureItem: IQuickPickItem | undefined = undefined; if (domainToConfigure && linkProtectionTrustedDomains.indexOf(domainToConfigure) === -1) { domainToConfigureItem = { type: 'item', label: domainToConfigure, id: domainToConfigure, picked: true, description: localize('trustDomainAndOpenLink', 'Trust domain and open link') }; specialQuickPickItems.push(<IQuickPickItem>domainToConfigureItem); } const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = domainQuickPickItems.length === 0 ? specialQuickPickItems : [...specialQuickPickItems, { type: 'separator' }, ...domainQuickPickItems]; const pickedResult = await quickInputService.pick(quickPickItems, { canPickMany: true, activeItem: domainToConfigureItem }); if (pickedResult) { const pickedDomains: string[] = pickedResult.map(r => r.id!); storageService.store('http.linkProtectionTrustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL); return pickedDomains; } return []; }; const configureTrustedDomainCommand = { id: 'workbench.action.configureLinkProtectionTrustedDomains', description: { description: localize('configureLinkProtectionTrustedDomains', 'Configure Trusted Domains for Link Protection'), args: [{ name: 'domainToConfigure', schema: { type: 'string' } }] }, handler: (accessor: ServicesAccessor, domainToConfigure?: string) => { const quickInputService = accessor.get(IQuickInputService); const storageService = accessor.get(IStorageService); const productService = accessor.get(IProductService); const trustedDomains = productService.linkProtectionTrustedDomains ? [...productService.linkProtectionTrustedDomains] : []; return configureTrustedDomainsHandler(quickInputService, storageService, trustedDomains, domainToConfigure); } }; </del> <ins> /** * Trusted Domains Contribution */ </ins> <del> CommandsRegistry.registerCommand(configureTrustedDomainCommand); </del> <ins> CommandsRegistry.registerCommand(configureTrustedDomainSettingsCommand); </ins> MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { <del> id: configureTrustedDomainCommand.id, title: configureTrustedDomainCommand.description.description </del> <ins> id: configureTrustedDomainSettingsCommand.id, title: configureTrustedDomainSettingsCommand.description.description </ins> } }); <del> class OpenerValidatorContributions implements IWorkbenchContribution { constructor( @IOpenerService private readonly _openerService: IOpenerService, @IStorageService private readonly _storageService: IStorageService, @IDialogService private readonly _dialogService: IDialogService, @IProductService private readonly _productService: IProductService, @IQuickInputService private readonly _quickInputService: IQuickInputService ) { this._openerService.registerValidator({ shouldOpen: r => this.validateLink(r) }); </del> <ins> CommandsRegistry.registerCommand(toggleLinkProtection); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: toggleLinkProtection.id, title: toggleLinkProtection.description.description </ins> } <ins> }); </ins> <del> async validateLink(resource: URI): Promise<boolean> { const { scheme, authority } = resource; if (!equalsIgnoreCase(scheme, Schemas.http) && !equalsIgnoreCase(scheme, Schemas.https)) { return true; } let trustedDomains: string[] = this._productService.linkProtectionTrustedDomains ? [...this._productService.linkProtectionTrustedDomains] : []; try { const trustedDomainsSrc = this._storageService.get('http.linkProtectionTrustedDomains', StorageScope.GLOBAL); if (trustedDomainsSrc) { trustedDomains = JSON.parse(trustedDomainsSrc); } } catch (err) { } const domainToOpen = `${scheme}://${authority}`; if (isURLDomainTrusted(resource, trustedDomains)) { return true; } else { const { choice } = await this._dialogService.show( Severity.Info, localize( 'openExternalLinkAt', 'Do you want {0} to open the external website?n{1}', this._productService.nameShort, resource.toString(true) ), [ localize('openLink', 'Open Link'), localize('cancel', 'Cancel'), localize('configureTrustedDomains', 'Configure Trusted Domains') ], { cancelId: 1 } ); // Open Link if (choice === 0) { return true; } // Configure Trusted Domains else if (choice === 2) { const pickedDomains = await configureTrustedDomainsHandler( this._quickInputService, this._storageService, trustedDomains, domainToOpen ); if (pickedDomains.indexOf(domainToOpen) !== -1) { return true; } return false; } return false; } } } </del> Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution( OpenerValidatorContributions, LifecyclePhase.Restored ); <del> const rLocalhost = /^localhost(:d+)?$/i; const r127 = /^127.0.0.1(:d+)?$/; function isLocalhostAuthority(authority: string) { return rLocalhost.test(authority) || r127.test(authority); } /** * Check whether a domain like https://www.microsoft.com matches * the list of trusted domains. * * - Schemes must match * - There's no subdomain matching. For example https://microsoft.com doesn't match https://www.microsoft.com * - Star matches all. For example https://*.microsoft.com matches https://www.microsoft.com */ export function isURLDomainTrusted(url: URI, trustedDomains: string[]) { if (isLocalhostAuthority(url.authority)) { return true; } const domain = `${url.scheme}://${url.authority}`; for (let i = 0; i < trustedDomains.length; i++) { if (trustedDomains[i] === '*') { return true; } if (trustedDomains[i] === domain) { return true; } if (trustedDomains[i].indexOf('*') !== -1) { const parsedTrustedDomain = URI.parse(trustedDomains[i]); if (url.scheme === parsedTrustedDomain.scheme) { const authoritySegments = url.authority.split('.'); const trustedDomainAuthoritySegments = parsedTrustedDomain.authority.split('.'); if (authoritySegments.length === trustedDomainAuthoritySegments.length) { if ( authoritySegments.every( (val, i) => trustedDomainAuthoritySegments[i] === '*' || val === trustedDomainAuthoritySegments[i] ) ) { return true; } } } } } return false; } </del> <ins> Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution( TrustedDomainsFileSystemProvider, LifecyclePhase.Ready ); </ins>

import * as assert from 'assert'; <del> import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/url.contribution'; </del> <ins> import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; </ins> import { URI } from 'vs/base/common/uri'; suite('Link protection domain matching', () => {

test('* star', () => { assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.x.org'])); <ins> assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.x.org'])); </ins> assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.x.*'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://a.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.x.org'), ['https://*.*.org'])); <del> assert.ok(!isURLDomainTrusted(URI.parse('https://a.b.c.org'), ['https://*.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.b.c.org'), ['https://*.*.*.org'])); </del> <ins> assert.ok(isURLDomainTrusted(URI.parse('https://a.b.x.org'), ['https://*.b.*.org'])); assert.ok(isURLDomainTrusted(URI.parse('https://a.a.b.x.org'), ['https://*.b.*.org'])); </ins> }); });