Skip to content

Commit af2acf4

Browse files
authored
Merge pull request microsoft#258825 from mjbvz/magnificent-chinchilla
Allow disabled checkbox inputs in rendered markdown
2 parents ead5a7c + 3a49a29 commit af2acf4

File tree

5 files changed

+40
-9
lines changed

5 files changed

+40
-9
lines changed

src/vs/base/browser/markdownRenderer.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,11 @@ function sanitizeRenderedMarkdown(
424424
return domSanitize.sanitizeHtml(renderedMarkdown, sanitizerConfig);
425425
}
426426

427+
export const allowedMarkdownHtmlTags = Object.freeze([
428+
...domSanitize.basicMarkupHtmlTags,
429+
'input', // Allow inputs for rendering checkboxes. Other types of inputs are removed and the inputs are always disabled
430+
]);
431+
427432
export const allowedMarkdownHtmlAttributes = [
428433
'align',
429434
'autoplay',
@@ -483,7 +488,7 @@ function getSanitizerOptions(isTrusted: boolean | MarkdownStringTrustedOptions,
483488
// HTML tags that can result from markdown are from reading https://spec.commonmark.org/0.29/
484489
// HTML table tags that can result from markdown are from https://github.github.com/gfm/#tables-extension-
485490
allowedTags: {
486-
override: options.allowedTags?.override ?? domSanitize.basicMarkupHtmlTags
491+
override: options.allowedTags?.override ?? allowedMarkdownHtmlTags
487492
},
488493
allowedAttributes: {
489494
override: allowedMarkdownHtmlAttributes,
@@ -541,15 +546,19 @@ function getSanitizerOptions(isTrusted: boolean | MarkdownStringTrustedOptions,
541546
}
542547
},
543548
uponSanitizeElement: (element, e) => {
549+
let wantsReplaceWithPlaintext = false;
544550
if (e.tagName === 'input') {
545551
if (element.attributes.getNamedItem('type')?.value === 'checkbox') {
546552
element.setAttribute('disabled', '');
547-
} else if (!options.replaceWithPlaintext) {
553+
} else if (options.replaceWithPlaintext) {
554+
wantsReplaceWithPlaintext = true;
555+
} else {
548556
element.remove();
557+
return;
549558
}
550559
}
551560

552-
if (options.replaceWithPlaintext && !e.allowedTags[e.tagName] && e.tagName !== 'body') {
561+
if (options.replaceWithPlaintext && (wantsReplaceWithPlaintext || (!e.allowedTags[e.tagName] && e.tagName !== 'body'))) {
553562
if (element.parentElement) {
554563
let startTagText: string;
555564
let endTagText: string | undefined;

src/vs/base/test/browser/markdownRenderer.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,17 @@ suite('MarkdownRenderer', () => {
346346
const result = store.add(renderMarkdown(mds)).element;
347347
assert.strictEqual(result.innerHTML, `<img src="vscode-file://vscode-app/images/cat.gif">`);
348348
});
349+
350+
test('Should only allow checkbox inputs', () => {
351+
const mds = new MarkdownString(
352+
'text: <input type="text">\ncheckbox:<input type="checkbox">',
353+
{ supportHtml: true });
354+
355+
const result = store.add(renderMarkdown(mds)).element;
356+
357+
// Inputs should always be disabled too
358+
assert.strictEqual(result.innerHTML, `<p>text: \ncheckbox:<input type="checkbox" disabled=""></p>`);
359+
});
349360
});
350361

351362
suite('fillInIncompleteTokens', () => {

src/vs/workbench/contrib/chat/browser/chatMarkdownRenderer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js';
1818
import product from '../../../../platform/product/common/product.js';
1919
import { REVEAL_IN_EXPLORER_COMMAND_ID } from '../../files/browser/fileConstants.js';
2020

21-
export const allowedChatMarkdownHtmlTags = [
21+
export const allowedChatMarkdownHtmlTags = Object.freeze([
2222
'b',
2323
'blockquote',
2424
'br',
@@ -53,7 +53,9 @@ export const allowedChatMarkdownHtmlTags = [
5353
// Not in the official list, but used for codicons and other vscode markdown extensions
5454
'span',
5555
'div',
56-
];
56+
57+
'input', // Allowed for rendering checkboxes. Other types of inputs are removed and the inputs are always disabled
58+
]);
5759

5860
/**
5961
* This wraps the MarkdownRenderer and applies sanitizer options needed for Chat.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<div class="rendered-markdown"><p>&lt;area&gt;</p><hr><br><input type="checkbox" disabled=""><p></p></div>

src/vs/workbench/contrib/chat/test/browser/chatMarkdownRenderer.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,18 @@ suite('ChatMarkdownRenderer', () => {
7979
});
8080

8181
test('self-closing elements', async () => {
82-
const md = new MarkdownString('<area><hr><br><input type="text" value="test">');
83-
md.supportHtml = true;
84-
const result = store.add(testRenderer.render(md));
85-
await assertSnapshot(result.element.outerHTML);
82+
{
83+
const md = new MarkdownString('<area><hr><br><input type="text" value="test">');
84+
md.supportHtml = true;
85+
const result = store.add(testRenderer.render(md));
86+
await assertSnapshot(result.element.outerHTML);
87+
}
88+
{
89+
const md = new MarkdownString('<area><hr><br><input type="checkbox">');
90+
md.supportHtml = true;
91+
const result = store.add(testRenderer.render(md));
92+
await assertSnapshot(result.element.outerHTML);
93+
}
8694
});
8795

8896
test('html comments', async () => {

0 commit comments

Comments
 (0)