diff --git a/src/hoverAction.ts b/src/hoverAction.ts index f4e91f4d0..760013524 100644 --- a/src/hoverAction.ts +++ b/src/hoverAction.ts @@ -74,7 +74,7 @@ class JavaHoverProvider implements HoverProvider { } const contributed = new MarkdownString(contributedCommands.map((command) => this.convertCommandToMarkdown(command)).join(' | ')); - contributed.isTrusted = true; + contributed.isTrusted = { enabledCommands: contributedCommands.map((command) => command.command) }; let contents: MarkdownString[] = [ contributed ]; let range; if (serverHover && serverHover.contents) { diff --git a/src/providerDispatcher.ts b/src/providerDispatcher.ts index 3d4e29414..abacbe141 100644 --- a/src/providerDispatcher.ts +++ b/src/providerDispatcher.ts @@ -211,7 +211,13 @@ export function fixJdtSchemeHoverLinks(hover: Hover): Hover { const newContents: (MarkedString | MarkdownString)[] = []; for (const content of hover.contents) { if (content instanceof MarkdownString) { - newContents.push(fixJdtLinksInDocumentation(content)); + // Skip our own trusted contributed commands (e.g. "Go to Super Implementation"); + // only sanitize untrusted server-provided Javadoc. + if (content.isTrusted) { + newContents.push(content); + } else { + newContents.push(fixJdtLinksInDocumentation(content)); + } } else { newContents.push(content); } diff --git a/test/standard-mode-suite/hoverLinks.test.ts b/test/standard-mode-suite/hoverLinks.test.ts new file mode 100644 index 000000000..627dc8867 --- /dev/null +++ b/test/standard-mode-suite/hoverLinks.test.ts @@ -0,0 +1,37 @@ +'use strict'; + +import * as assert from 'assert'; +import { Hover, MarkdownString } from 'vscode'; +import { fixJdtSchemeHoverLinks } from '../../src/providerDispatcher'; + +suite('Hover Links Test', () => { + + test('trusted contributed command links are preserved (super implementation)', () => { + const contributed = new MarkdownString('[Go to Super Implementation](command:java.action.navigateToSuperImplementation?%5B%5D)'); + contributed.isTrusted = { enabledCommands: ['java.action.navigateToSuperImplementation'] }; + const hover = new Hover([contributed]); + + const fixed = fixJdtSchemeHoverLinks(hover); + const value = (fixed.contents[0] as MarkdownString).value; + assert.ok(value.includes('(command:java.action.navigateToSuperImplementation'), 'contributed command link should not be sanitized'); + }); + + test('untrusted server command links are sanitized', () => { + const javadoc = new MarkdownString('[click here](command:evil.command?param=true)'); + const hover = new Hover([javadoc]); + + const fixed = fixJdtSchemeHoverLinks(hover); + const value = (fixed.contents[0] as MarkdownString).value; + assert.strictEqual(value, 'click here', 'server command link should be stripped to its label'); + }); + + test('jdt:// links are converted to command links', () => { + const javadoc = new MarkdownString('[String](jdt://contents/Foo.class)'); + const hover = new Hover([javadoc]); + + const fixed = fixJdtSchemeHoverLinks(hover); + const value = (fixed.contents[0] as MarkdownString).value; + assert.ok(value.includes('(command:'), 'jdt link should be converted to a command link'); + assert.ok(!value.includes('jdt://'), 'jdt scheme should be replaced'); + }); +});