@@ -26,29 +26,40 @@ import { StandardKeyboardEvent } from './keyboardEvent.js';
26
26
import { StandardMouseEvent } from './mouseEvent.js' ;
27
27
import { renderLabelWithIcons } from './ui/iconLabel/iconLabels.js' ;
28
28
29
- export interface MarkedOptions extends Readonly < Omit < marked . MarkedOptions , 'extensions' | 'baseUrl' > > {
30
- readonly markedExtensions ?: marked . MarkedExtension [ ] ;
31
- }
32
-
29
+ /**
30
+ * Options for the rendering of markdown with {@link renderMarkdown}.
31
+ */
33
32
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
34
33
readonly codeBlockRenderer ?: ( languageId : string , value : string ) => Promise < HTMLElement > ;
35
34
readonly codeBlockRendererSync ?: ( languageId : string , value : string , raw ?: string ) => HTMLElement ;
36
35
readonly asyncRenderCallback ?: ( ) => void ;
36
+
37
37
readonly fillInIncompleteTokens ?: boolean ;
38
- readonly remoteImageIsAllowed ?: ( uri : URI ) => boolean ;
39
38
40
- readonly sanitizerOptions ?: ISanitizerOptions ;
39
+ readonly sanitizerConfig ?: MarkdownSanitizerConfig ;
41
40
42
- readonly markedOptions ?: MarkedOptions ;
41
+ readonly markedOptions ?: MarkdownRendererMarkedOptions ;
42
+ readonly markedExtensions ?: marked . MarkedExtension [ ] ;
43
43
}
44
44
45
- export interface ISanitizerOptions {
45
+ /**
46
+ * Subset of options passed to `Marked` for rendering markdown.
47
+ */
48
+ export interface MarkdownRendererMarkedOptions {
49
+ readonly gfm ?: boolean ;
50
+ readonly breaks ?: boolean ;
51
+ }
52
+
53
+ export interface MarkdownSanitizerConfig {
46
54
readonly replaceWithPlaintext ?: boolean ;
47
55
readonly allowedTags ?: {
48
56
readonly override : readonly string [ ] ;
49
57
} ;
50
58
readonly customAttrSanitizer ?: ( attrName : string , attrValue : string ) => boolean | string ;
51
- readonly allowedProductProtocols ?: readonly string [ ] ;
59
+ readonly allowedLinkSchemes ?: {
60
+ readonly augment : readonly string [ ] ;
61
+ } ;
62
+ readonly remoteImageIsAllowed ?: ( uri : URI ) => boolean ;
52
63
}
53
64
54
65
const defaultMarkedRenderers = Object . freeze ( {
@@ -110,14 +121,14 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
110
121
const disposables = new DisposableStore ( ) ;
111
122
let isDisposed = false ;
112
123
113
- const markedInstance = new marked . Marked ( ...( options . markedOptions ?. markedExtensions ?? [ ] ) ) ;
124
+ const markedInstance = new marked . Marked ( ...( options . markedExtensions ?? [ ] ) ) ;
114
125
const { renderer, codeBlocks, syncCodeBlocks } = createMarkdownRenderer ( markedInstance , options , markdown ) ;
115
126
const value = preprocessMarkdownString ( markdown ) ;
116
127
117
128
let renderedMarkdown : string ;
118
129
if ( options . fillInIncompleteTokens ) {
119
130
// The defaults are applied by parse but not lexer()/parser(), and they need to be present
120
- const opts : MarkedOptions = {
131
+ const opts : marked . MarkedOptions = {
121
132
...markedInstance . defaults ,
122
133
...options . markedOptions ,
123
134
renderer
@@ -136,12 +147,12 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
136
147
}
137
148
138
149
const htmlParser = new DOMParser ( ) ;
139
- const markdownHtmlDoc = htmlParser . parseFromString ( sanitizeRenderedMarkdown ( { isTrusted : markdown . isTrusted , ... options . sanitizerOptions } , renderedMarkdown ) as unknown as string , 'text/html' ) ;
150
+ const markdownHtmlDoc = htmlParser . parseFromString ( sanitizeRenderedMarkdown ( renderedMarkdown , markdown . isTrusted ?? false , options . sanitizerConfig ) as unknown as string , 'text/html' ) ;
140
151
141
152
rewriteRenderedLinks ( markdown , options , markdownHtmlDoc . body ) ;
142
153
143
154
const element = target ?? document . createElement ( 'div' ) ;
144
- element . innerHTML = sanitizeRenderedMarkdown ( { isTrusted : markdown . isTrusted , ... options . sanitizerOptions } , markdownHtmlDoc . body . innerHTML ) as unknown as string ;
155
+ element . innerHTML = sanitizeRenderedMarkdown ( markdownHtmlDoc . body . innerHTML , markdown . isTrusted ?? false , options . sanitizerConfig ) as unknown as string ;
145
156
146
157
if ( codeBlocks . length > 0 ) {
147
158
Promise . all ( codeBlocks ) . then ( ( tuples ) => {
@@ -222,9 +233,9 @@ function rewriteRenderedLinks(markdown: IMarkdownString, options: MarkdownRender
222
233
223
234
el . setAttribute ( 'src' , massageHref ( markdown , href , true ) ) ;
224
235
225
- if ( options . remoteImageIsAllowed ) {
236
+ if ( options . sanitizerConfig ?. remoteImageIsAllowed ) {
226
237
const uri = URI . parse ( href ) ;
227
- if ( uri . scheme !== Schemas . file && uri . scheme !== Schemas . data && ! options . remoteImageIsAllowed ( uri ) ) {
238
+ if ( uri . scheme !== Schemas . file && uri . scheme !== Schemas . data && ! options . sanitizerConfig . remoteImageIsAllowed ( uri ) ) {
228
239
el . replaceWith ( DOM . $ ( '' , undefined , el . outerHTML ) ) ;
229
240
}
230
241
}
@@ -280,7 +291,7 @@ function createMarkdownRenderer(marked: marked.Marked, options: MarkdownRenderOp
280
291
// Note: we always pass the output through dompurify after this so that we don't rely on
281
292
// marked for real sanitization.
282
293
renderer . html = ( { text } ) => {
283
- if ( options . sanitizerOptions ?. replaceWithPlaintext ) {
294
+ if ( options . sanitizerConfig ?. replaceWithPlaintext ) {
284
295
return escape ( text ) ;
285
296
}
286
297
@@ -401,17 +412,15 @@ function resolveWithBaseUri(baseUri: URI, href: string): string {
401
412
}
402
413
}
403
414
404
- interface IInternalSanitizerOptions extends ISanitizerOptions {
405
- readonly isTrusted ?: boolean | MarkdownStringTrustedOptions ;
406
- }
407
415
408
416
const selfClosingTags = [ 'area' , 'base' , 'br' , 'col' , 'command' , 'embed' , 'hr' , 'img' , 'input' , 'keygen' , 'link' , 'meta' , 'param' , 'source' , 'track' , 'wbr' ] ;
409
417
410
418
function sanitizeRenderedMarkdown (
411
- options : IInternalSanitizerOptions ,
412
419
renderedMarkdown : string ,
420
+ isTrusted : boolean | MarkdownStringTrustedOptions ,
421
+ options : MarkdownSanitizerConfig = { } ,
413
422
) : TrustedHTML {
414
- const sanitizerConfig = getSanitizerOptions ( options ) ;
423
+ const sanitizerConfig = getSanitizerOptions ( isTrusted , options ) ;
415
424
return domSanitize . sanitizeHtml ( renderedMarkdown , sanitizerConfig ) ;
416
425
}
417
426
@@ -448,7 +457,7 @@ export const allowedMarkdownHtmlAttributes = [
448
457
'class' ,
449
458
] ;
450
459
451
- function getSanitizerOptions ( options : IInternalSanitizerOptions ) : domSanitize . SanitizeOptions {
460
+ function getSanitizerOptions ( isTrusted : boolean | MarkdownStringTrustedOptions , options : MarkdownSanitizerConfig ) : domSanitize . DomSanitizerConfig {
452
461
const allowedLinkSchemes = [
453
462
Schemas . http ,
454
463
Schemas . https ,
@@ -460,12 +469,12 @@ function getSanitizerOptions(options: IInternalSanitizerOptions): domSanitize.Sa
460
469
Schemas . vscodeNotebookCell
461
470
] ;
462
471
463
- if ( options . isTrusted ) {
472
+ if ( isTrusted ) {
464
473
allowedLinkSchemes . push ( Schemas . command ) ;
465
474
}
466
475
467
- if ( options . allowedProductProtocols ) {
468
- allowedLinkSchemes . push ( ...options . allowedProductProtocols ) ;
476
+ if ( options . allowedLinkSchemes ?. augment ) {
477
+ allowedLinkSchemes . push ( ...options . allowedLinkSchemes . augment ) ;
469
478
}
470
479
471
480
return {
@@ -606,7 +615,7 @@ export function renderAsPlaintext(str: IMarkdownString | string, options?: {
606
615
}
607
616
608
617
const html = marked . parse ( value , { async : false , renderer : options ?. includeCodeBlocksFences ? plainTextWithCodeBlocksRenderer . value : plainTextRenderer . value } ) ;
609
- return sanitizeRenderedMarkdown ( { isTrusted : false } , html )
618
+ return sanitizeRenderedMarkdown ( html , /* isTrusted */ false , { } )
610
619
. toString ( )
611
620
. replace ( / & ( # \d + | [ a - z A - Z ] + ) ; / g, m => unescapeInfo . get ( m ) ?? m )
612
621
. trim ( ) ;
0 commit comments