Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let opener service validate that only specific commands can be run in command uris #165204

Merged
merged 1 commit into from
Nov 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/vs/editor/browser/services/openerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,25 @@ class CommandOpener implements IOpener {
if (!matchesScheme(target, Schemas.command)) {
return false;
}

if (!options?.allowCommands) {
// silently ignore commands when command-links are disabled, also
// surpress other openers by returning TRUE
// suppress other openers by returning TRUE
return true;
}
// run command or bail out if command isn't known

if (typeof target === 'string') {
target = URI.parse(target);
}

if (Array.isArray(options.allowCommands)) {
// Only allow specific commands
if (!options.allowCommands.includes(target.path)) {
// Suppress other openers by returning TRUE
return true;
}
}

// execute as command
let args: any = [];
try {
Expand Down
45 changes: 17 additions & 28 deletions src/vs/editor/contrib/markdownRenderer/browser/markdownRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter } from 'vs/base/common/event';
import { IMarkdownString, MarkdownStringTrustedOptions } from 'vs/base/common/htmlContent';
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
Expand Down Expand Up @@ -112,35 +110,26 @@ export class MarkdownRenderer {
}

export async function openLinkFromMarkdown(openerService: IOpenerService, link: string, isTrusted: boolean | MarkdownStringTrustedOptions | undefined): Promise<boolean> {
let allowCommands = false;
if (isTrusted) {
try {
const uri = URI.parse(link);
if (uri.scheme === Schemas.command) {
if (typeof isTrusted === 'boolean') {
if (!isTrusted) {
return false;
}

allowCommands = true;
} else {
// Only allow a subset of commands
if (!isTrusted.enabledCommands.includes(uri.path)) {
return false;
}

allowCommands = true;
}
}
} catch {
// noop
}
}

try {
return await openerService.open(link, { fromUserGesture: true, allowContributedOpeners: true, allowCommands });
return await openerService.open(link, {
fromUserGesture: true,
allowContributedOpeners: true,
allowCommands: toAllowCommandsOption(isTrusted),
});
} catch (e) {
onUnexpectedError(e);
return false;
}
}

function toAllowCommandsOption(isTrusted: boolean | MarkdownStringTrustedOptions | undefined): boolean | readonly string[] {
if (isTrusted === true) {
return true; // Allow all commands
}

if (isTrusted && Array.isArray(isTrusted.enabledCommands)) {
return isTrusted.enabledCommands; // Allow subset of commands
}

return false; // Block commands
}
4 changes: 3 additions & 1 deletion src/vs/platform/opener/common/opener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ export type OpenInternalOptions = {

/**
* Allow command links to be handled.
*
* If this is an array, then only the commands included in the array can be run.
*/
readonly allowCommands?: boolean;
readonly allowCommands?: boolean | readonly string[];
};

export type OpenExternalOptions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -682,35 +682,36 @@ var requirejs = (function() {
break;
}
case 'clicked-link': {
let linkToOpen: URI | string | undefined;

if (matchesScheme(data.href, Schemas.command)) {
// We allow a very limited set of commands
const uri = URI.parse(data.href);
switch (uri.path) {
case 'workbench.action.openLargeOutput': {
const outputId = uri.query;
const group = this.editorGroupService.activeGroup;
if (group) {
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
}

this.openerService.open(CellUri.generateCellOutputUri(this.documentUri, outputId));
return;
}
case 'github-issues.authNow':
case 'workbench.extensions.search':
case 'workbench.action.openSettings': {
this.openerService.open(data.href, { fromUserGesture: true, allowCommands: true, fromWorkspace: true });
return;
if (uri.path === 'workbench.action.openLargeOutput') {
const outputId = uri.query;
const group = this.editorGroupService.activeGroup;
if (group) {
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
}

this.openerService.open(CellUri.generateCellOutputUri(this.documentUri, outputId));
return;
}

// We allow a very limited set of commands
this.openerService.open(data.href, {
fromUserGesture: true,
fromWorkspace: true,
allowCommands: [
'github-issues.authNow',
'workbench.extensions.search',
'workbench.action.openSettings',
],
});
return;
}

let linkToOpen: URI | string | undefined;
if (matchesSomeScheme(data.href, Schemas.http, Schemas.https, Schemas.mailto, Schemas.vscodeNotebookCell, Schemas.vscodeNotebook)) {
linkToOpen = data.href;
} else if (!/^[\w\-]+:/.test(data.href)) {
Expand Down Expand Up @@ -742,7 +743,7 @@ var requirejs = (function() {
}

if (linkToOpen) {
this.openerService.open(linkToOpen, { fromUserGesture: true, allowCommands: false, fromWorkspace: true });
this.openerService.open(linkToOpen, { fromUserGesture: true, fromWorkspace: true });
}
break;
}
Expand Down