提交 40bc003e 编写于 作者: P Pine Wu

Fix #80595

上级 1963239c
......@@ -9,11 +9,17 @@ export interface ILocalizeInfo {
}
/**
* Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`.
* Localize a message.
*
* `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
* For example, `localize('hello {0}', name)`
*/
export declare function localize(info: ILocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
/**
* Localize a message. `message` can contain `{n}` notation where it is replaced by the nth value in `...args`.
* Localize a message.
*
* `message` can contain `{n}` notation where it is replaced by the nth value in `...args`
* For example, `localize('hello {0}', name)`
*/
export declare function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string;
/*---------------------------------------------------------------------------------------------
* 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 { IProductService } from 'vs/platform/product/common/product';
export const enum ConfigureTrustedDomainActionType {
ToggleTrustAll = 'toggleTrustAll',
Add = 'add',
Configure = 'configure',
Reset = 'reset'
}
export const configureTrustedDomainSettingsCommand = {
id: 'workbench.action.configureTrustedDomain',
description: {
description: localize('configureTrustedDomain', 'Configure Trusted Domains for Link Protection'),
args: []
},
handler: async (accessor: ServicesAccessor) => {
const quickInputService = accessor.get(IQuickInputService);
const storageService = accessor.get(IStorageService);
const productService = accessor.get(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) { }
const trustOrUntrustAllLabel =
trustedDomains.indexOf('*') === -1
? localize('trustedDomain.trustAll', 'Disable Link Protection')
: localize('trustedDomain.untrustAll', 'Enable Link Protection');
const trustOrUntrustAll: IQuickPickItem = {
id: ConfigureTrustedDomainActionType.ToggleTrustAll,
label: trustOrUntrustAllLabel
};
const result = await quickInputService.pick(
[
trustOrUntrustAll,
{ id: ConfigureTrustedDomainActionType.Add, label: localize('trustedDomain.add', 'Add Trusted Domain') },
{
id: ConfigureTrustedDomainActionType.Configure,
label: localize('trustedDomain.edit', 'View and configure Trusted Domains')
},
{ id: ConfigureTrustedDomainActionType.Reset, label: localize('trustedDomain.reset', 'Reset Trusted Domains') }
],
{}
);
if (result) {
switch (result.id) {
case ConfigureTrustedDomainActionType.ToggleTrustAll:
toggleAll(trustedDomains, storageService);
break;
case ConfigureTrustedDomainActionType.Add:
addDomain(trustedDomains, storageService, quickInputService);
break;
case ConfigureTrustedDomainActionType.Configure:
configureDomains(trustedDomains, storageService, quickInputService);
break;
case ConfigureTrustedDomainActionType.Reset:
resetDomains(storageService, productService);
break;
}
}
}
};
function toggleAll(trustedDomains: string[], storageService: IStorageService) {
if (trustedDomains.indexOf('*') === -1) {
storageService.store(
'http.linkProtectionTrustedDomains',
JSON.stringify(trustedDomains.concat(['*'])),
StorageScope.GLOBAL
);
} else {
storageService.store(
'http.linkProtectionTrustedDomains',
JSON.stringify(trustedDomains.filter(x => x !== '*')),
StorageScope.GLOBAL
);
}
}
function addDomain(trustedDomains: string[], storageService: IStorageService, quickInputService: IQuickInputService) {
quickInputService
.input({
placeHolder: 'Domain to trust',
validateInput: i => {
if (!i.match(/^https?:\/\//)) {
return Promise.resolve(undefined);
}
return Promise.resolve(i);
}
})
.then(result => {
console.log(result);
if (result) {
storageService.store(
'http.linkProtectionTrustedDomains',
JSON.stringify(trustedDomains.concat([result])),
StorageScope.GLOBAL
);
}
});
}
function configureDomains(
trustedDomains: string[],
storageService: IStorageService,
quickInputService: IQuickInputService
) {
const domainQuickPickItems: IQuickPickItem[] = trustedDomains
.filter(d => d !== '*')
.map(d => {
return {
type: 'item',
label: d,
id: d,
picked: true
};
});
quickInputService.pick(domainQuickPickItems, { canPickMany: true }).then(result => {
const pickedDomains: string[] = result.map(r => r.id!);
storageService.store('http.linkProtectionTrustedDomains', JSON.stringify(pickedDomains), StorageScope.GLOBAL);
});
}
function resetDomains(storageService: IStorageService, productService: IProductService) {
if (productService.linkProtectionTrustedDomains) {
storageService.store(
'http.linkProtectionTrustedDomains',
JSON.stringify(productService.linkProtectionTrustedDomains),
StorageScope.GLOBAL
);
} else {
storageService.store('http.linkProtectionTrustedDomains', JSON.stringify([]), StorageScope.GLOBAL);
}
}
export async function configureOpenerTrustedDomainsHandler(
trustedDomains: string[],
domainToConfigure: string,
quickInputService: IQuickInputService,
storageService: IStorageService
) {
const openAllLinksItem: IQuickPickItem = {
type: 'item',
label: localize('trustedDomain.trustAllAndOpenLink', 'Disable Link Protection and open link'),
id: '*',
picked: trustedDomains.indexOf('*') !== -1
};
const trustDomainItem: IQuickPickItem = {
type: 'item',
label: localize('trustedDomain.trustDomainAndOpenLink', 'Trust {0} and open link', domainToConfigure),
id: domainToConfigure,
picked: true
};
const pickedResult = await quickInputService.pick([openAllLinksItem, trustDomainItem], {
activeItem: trustDomainItem
});
if (pickedResult) {
if (pickedResult.id && trustedDomains.indexOf(pickedResult.id) === -1) {
storageService.store(
'http.linkProtectionTrustedDomains',
JSON.stringify([...trustedDomains, pickedResult.id]),
StorageScope.GLOBAL
);
return [...trustedDomains, pickedResult.id];
}
}
return [];
}
/*---------------------------------------------------------------------------------------------
* 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';
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
) {
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
);
// Trust all domains
if (pickedDomains.indexOf('*') !== -1) {
return true;
}
// Trust current domain
if (pickedDomains.indexOf(domainToOpen) !== -1) {
return true;
}
return false;
}
return false;
}
}
}
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. 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;
}
......@@ -3,29 +3,19 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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';
import { Action } from 'vs/base/common/actions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import {
IWorkbenchContribution,
IWorkbenchContributionsRegistry,
Extensions as WorkbenchExtensions
} from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
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';
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 } from 'vs/workbench/contrib/url/common/trustedDomains';
import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator';
export class OpenUrlAction extends Action {
static readonly ID = 'workbench.action.url.openUrl';
......@@ -54,227 +44,20 @@ Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registe
localize('developer', 'Developer')
);
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);
}
};
/**
* Trusted Domains Contribution
*/
CommandsRegistry.registerCommand(configureTrustedDomainCommand);
CommandsRegistry.registerCommand(configureTrustedDomainSettingsCommand);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: configureTrustedDomainCommand.id,
title: configureTrustedDomainCommand.description.description
id: configureTrustedDomainSettingsCommand.id,
title: configureTrustedDomainSettingsCommand.description.description
}
});
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) });
}
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;
}
}
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
OpenerValidatorContributions,
LifecyclePhase.Restored
);
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;
}
......@@ -5,7 +5,7 @@
import * as assert from 'assert';
import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/url.contribution';
import { isURLDomainTrusted } from 'vs/workbench/contrib/url/common/trustedDomainsValidator';
import { URI } from 'vs/base/common/uri';
suite('Link protection domain matching', () => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册