Safer construction of notebook html (#170987) (#21699) (#21709)

Co-authored-by: Martin Aeschlimann <martinae@microsoft.com>

Co-authored-by: Martin Aeschlimann <martinae@microsoft.com>
This commit is contained in:
Karl Burtram
2023-01-23 14:33:13 -08:00
committed by GitHub
parent 20cbc04211
commit 74ec8dcac7
3 changed files with 14 additions and 31 deletions

View File

@@ -128,16 +128,20 @@ export class OutputElement extends Disposable {
private _renderSearchForMimetype(viewModel: ICellOutputViewModel, mimeType: string): IInsetRenderOutput {
const query = `@tag:notebookRenderer ${mimeType}`;
const p = DOM.$('p', undefined, `No renderer could be found for mimetype "${mimeType}", but one might be available on the Marketplace.`);
const a = DOM.$('a', { href: `command:workbench.extensions.search?%22${query}%22`, class: 'monaco-button monaco-text-button', tabindex: 0, role: 'button', style: 'padding: 8px; text-decoration: none; color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); max-width: 200px;' }, `Search Marketplace`);
return {
type: RenderOutputType.Html,
source: viewModel,
htmlContent: `<p>No renderer could be found for mimetype "${mimeType}", but one might be available on the Marketplace.</p>
<a href="command:workbench.extensions.search?%22${query}%22" class="monaco-button monaco-text-button" tabindex="0" role="button" style="padding: 8px; text-decoration: none; color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); max-width: 200px;">Search Marketplace</a>`
htmlContent: p.outerHTML + a.outerHTML,
};
}
private _renderMessage(viewModel: ICellOutputViewModel, message: string): IInsetRenderOutput {
return { type: RenderOutputType.Html, source: viewModel, htmlContent: `<p>${message}</p>` };
const el = DOM.$('p', undefined, message);
return { type: RenderOutputType.Html, source: viewModel, htmlContent: el.outerHTML };
}
private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: ICellOutputViewModel) {

View File

@@ -238,16 +238,20 @@ export class CellOutputElement extends Disposable {
private _renderSearchForMimetype(viewModel: ICellOutputViewModel, mimeType: string): IInsetRenderOutput {
const query = `@tag:notebookRenderer ${mimeType}`;
const p = DOM.$('p', undefined, `No renderer could be found for mimetype "${mimeType}", but one might be available on the Marketplace.`);
const a = DOM.$('a', { href: `command:workbench.extensions.search?%22${query}%22`, class: 'monaco-button monaco-text-button', tabindex: 0, role: 'button', style: 'padding: 8px; text-decoration: none; color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); max-width: 200px;' }, `Search Marketplace`);
return {
type: RenderOutputType.Html,
source: viewModel,
htmlContent: `<p>No renderer could be found for mimetype "${mimeType}", but one might be available on the Marketplace.</p>
<a href="command:workbench.extensions.search?%22${query}%22" class="monaco-button monaco-text-button" tabindex="0" role="button" style="padding: 8px; text-decoration: none; color: rgb(255, 255, 255); background-color: rgb(14, 99, 156); max-width: 200px;">Search Marketplace</a>`
htmlContent: p.outerHTML + a.outerHTML
};
}
private _renderMessage(viewModel: ICellOutputViewModel, message: string): IInsetRenderOutput {
return { type: RenderOutputType.Html, source: viewModel, htmlContent: `<p>${message}</p>` };
const el = DOM.$('p', undefined, message);
return { type: RenderOutputType.Html, source: viewModel, htmlContent: el.outerHTML };
}
private async _attachToolbar(outputItemDiv: HTMLElement, notebookTextModel: NotebookTextModel, kernel: INotebookKernel | undefined, index: number, mimeTypes: readonly IOrderedMimeType[]) {

View File

@@ -153,30 +153,6 @@ async function webviewPreloads(ctx: PreloadContext) {
document.body.addEventListener('click', handleInnerClick);
const preservedScriptAttributes: (keyof HTMLScriptElement)[] = [
'type', 'src', 'nonce', 'noModule', 'async',
];
// derived from https://github.com/jquery/jquery/blob/d0ce00cdfa680f1f0c38460bc51ea14079ae8b07/src/core/DOMEval.js
const domEval = (container: Element) => {
const arr = Array.from(container.getElementsByTagName('script'));
for (let n = 0; n < arr.length; n++) {
const node = arr[n];
const scriptTag = document.createElement('script');
const trustedScript = ttPolicy?.createScript(node.innerText) ?? node.innerText;
scriptTag.text = trustedScript as string;
for (const key of preservedScriptAttributes) {
const val = node[key] || node.getAttribute && node.getAttribute(key);
if (val) {
scriptTag.setAttribute(key, val as any);
}
}
// TODO@connor4312: should script with src not be removed?
container.appendChild(scriptTag).parentNode!.removeChild(scriptTag);
}
};
async function loadScriptSource(url: string, originalUri = url): Promise<string> {
const res = await fetch(url);
const text = await res.text();
@@ -2038,7 +2014,6 @@ async function webviewPreloads(ctx: PreloadContext) {
if (content.type === RenderOutputType.Html) {
const trustedHtml = ttPolicy?.createHTML(content.htmlContent) ?? content.htmlContent;
this.element.innerHTML = trustedHtml as string;
domEval(this.element);
} else if (preloadsAndErrors.some(e => e instanceof Error)) {
const errors = preloadsAndErrors.filter((e): e is Error => e instanceof Error);
showPreloadErrors(this.element, ...errors);