Fix anchors and data images in extension pages

Fixes #144897

Refactors to have a single `hookDomPurifyHrefAndSrcSanitizer` function that handles sanitizing the href and src attributes. Also updates this function to allow fragment links `#` and data images
This commit is contained in:
Matt Bierner 2022-03-11 13:00:27 -08:00
parent 92bf104b52
commit c521f36041
No known key found for this signature in database
GPG key ID: 099C331567E11888
3 changed files with 51 additions and 52 deletions

View file

@ -1417,6 +1417,49 @@ export function detectFullscreen(): IDetectedFullscreen | null {
// -- sanitize and trusted html
/**
* Hooks dompurify using `afterSanitizeAttributes` to check that all `href` and `src`
* attributes are valid.
*/
export function hookDomPurifyHrefAndSrcSanitizer(allowedProtocols: readonly string[], allowDataImages = false): IDisposable {
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
// build an anchor to map URLs to
const anchor = document.createElement('a');
dompurify.addHook('afterSanitizeAttributes', (node) => {
// check all href/src attributes for validity
for (const attr of ['href', 'src']) {
if (node.hasAttribute(attr)) {
const attrValue = node.getAttribute(attr) as string;
if (attr === 'href' && attrValue.startsWith('#')) {
// Allow fragment links
continue;
}
anchor.href = attrValue;
if (!allowedProtocols.includes(anchor.protocol.replace(/:$/, ''))) {
if (allowDataImages && attr === 'src' && anchor.href.startsWith('data:')) {
continue;
}
node.removeAttribute(attr);
}
}
}
});
return toDisposable(() => {
dompurify.removeHook('afterSanitizeAttributes');
});
}
const defaultSafeProtocols = [
Schemas.http,
Schemas.https,
Schemas.command,
];
/**
* Sanitizes the given `value` and reset the given `node` with it.
*/
@ -1428,29 +1471,12 @@ export function safeInnerHtml(node: HTMLElement, value: string): void {
RETURN_DOM_FRAGMENT: false,
};
const allowedProtocols = [Schemas.http, Schemas.https, Schemas.command];
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
dompurify.addHook('afterSanitizeAttributes', (node) => {
// build an anchor to map URLs to
const anchor = document.createElement('a');
// check all href/src attributes for validity
for (const attr in ['href', 'src']) {
if (node.hasAttribute(attr)) {
anchor.href = node.getAttribute(attr) as string;
if (!allowedProtocols.includes(anchor.protocol)) {
node.removeAttribute(attr);
}
}
}
});
const hook = hookDomPurifyHrefAndSrcSanitizer(defaultSafeProtocols);
try {
const html = dompurify.sanitize(value, { ...options, RETURN_TRUSTED_TYPE: true });
node.innerHTML = html as unknown as string;
} finally {
dompurify.removeHook('afterSanitizeAttributes');
hook.dispose();
}
}

View file

@ -326,27 +326,13 @@ function sanitizeRenderedMarkdown(
}
});
// build an anchor to map URLs to
const anchor = document.createElement('a');
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
dompurify.addHook('afterSanitizeAttributes', (node) => {
// check all href/src attributes for validity
for (const attr of ['href', 'src']) {
if (node.hasAttribute(attr)) {
anchor.href = node.getAttribute(attr) as string;
if (!allowedSchemes.includes(anchor.protocol.replace(/:$/, ''))) {
node.removeAttribute(attr);
}
}
}
});
const hook = DOM.hookDomPurifyHrefAndSrcSanitizer(allowedSchemes);
try {
return dompurify.sanitize(renderedMarkdown, { ...config, RETURN_TRUSTED_TYPE: true });
} finally {
dompurify.removeHook('uponSanitizeAttribute');
dompurify.removeHook('afterSanitizeAttributes');
hook.dispose();
}
}

View file

@ -3,11 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { hookDomPurifyHrefAndSrcSanitizer } from 'vs/base/browser/dom';
import * as dompurify from 'vs/base/browser/dompurify/dompurify';
import { marked } from 'vs/base/common/marked/marked';
import { Schemas } from 'vs/base/common/network';
import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export const DEFAULT_MARKDOWN_STYLES = `
@ -152,21 +153,7 @@ code > div {
const allowedProtocols = [Schemas.http, Schemas.https, Schemas.command];
function sanitize(documentContent: string, allowUnknownProtocols: boolean): string {
// https://github.com/cure53/DOMPurify/blob/main/demos/hooks-scheme-allowlist.html
dompurify.addHook('afterSanitizeAttributes', (node) => {
// build an anchor to map URLs to
const anchor = document.createElement('a');
// check all href/src attributes for validity
for (const attr of ['href', 'src']) {
if (node.hasAttribute(attr)) {
anchor.href = node.getAttribute(attr) as string;
if (!allowedProtocols.includes(anchor.protocol.replace(/:$/, ''))) {
node.removeAttribute(attr);
}
}
}
});
const hook = hookDomPurifyHrefAndSrcSanitizer(allowedProtocols, true);
try {
return dompurify.sanitize(documentContent, {
@ -186,7 +173,7 @@ function sanitize(documentContent: string, allowUnknownProtocols: boolean): stri
...(allowUnknownProtocols ? { ALLOW_UNKNOWN_PROTOCOLS: true } : {}),
});
} finally {
dompurify.removeHook('afterSanitizeAttributes');
hook.dispose();
}
}