@@ -118,12 +118,12 @@ export function createUriListSnippet(
118
118
119
119
if ( insertAsVideo ) {
120
120
insertedAudioVideoCount ++ ;
121
- snippet . appendText ( `<video src="${ mdPath } " controls title="` ) ;
121
+ snippet . appendText ( `<video src="${ escapeHtmlAttribute ( mdPath ) } " controls title="` ) ;
122
122
snippet . appendPlaceholder ( 'Title' ) ;
123
123
snippet . appendText ( '"></video>' ) ;
124
124
} else if ( insertAsAudio ) {
125
125
insertedAudioVideoCount ++ ;
126
- snippet . appendText ( `<audio src="${ mdPath } " controls title="` ) ;
126
+ snippet . appendText ( `<audio src="${ escapeHtmlAttribute ( mdPath ) } " controls title="` ) ;
127
127
snippet . appendPlaceholder ( 'Title' ) ;
128
128
snippet . appendText ( '"></audio>' ) ;
129
129
} else {
@@ -139,7 +139,7 @@ export function createUriListSnippet(
139
139
const placeholderIndex = typeof options ?. placeholderStartIndex !== 'undefined' ? options ?. placeholderStartIndex + i : undefined ;
140
140
snippet . appendPlaceholder ( placeholderText , placeholderIndex ) ;
141
141
142
- snippet . appendText ( `](${ mdPath } )` ) ;
142
+ snippet . appendText ( `](${ escapeMarkdownLinkPath ( mdPath ) } )` ) ;
143
143
}
144
144
145
145
if ( i < uris . length - 1 && uris . length > 1 ) {
@@ -246,11 +246,53 @@ function getMdPath(dir: vscode.Uri | undefined, file: vscode.Uri) {
246
246
// so that drive-letters are resolved cast insensitively. However we then want to
247
247
// convert back to a posix path to insert in to the document.
248
248
const relativePath = path . relative ( dir . fsPath , file . fsPath ) ;
249
- return encodeURI ( path . posix . normalize ( relativePath . split ( path . sep ) . join ( path . posix . sep ) ) ) ;
249
+ return path . posix . normalize ( relativePath . split ( path . sep ) . join ( path . posix . sep ) ) ;
250
250
}
251
251
252
- return encodeURI ( path . posix . relative ( dir . path , file . path ) ) ;
252
+ return path . posix . relative ( dir . path , file . path ) ;
253
253
}
254
254
255
255
return file . toString ( false ) ;
256
256
}
257
+
258
+ function escapeHtmlAttribute ( attr : string ) : string {
259
+ return encodeURI ( attr ) . replaceAll ( '"' , '"' ) ;
260
+ }
261
+
262
+ function escapeMarkdownLinkPath ( mdPath : string ) : string {
263
+ if ( needsBracketLink ( mdPath ) ) {
264
+ return '<' + mdPath . replace ( '<' , '\\<' ) . replace ( '>' , '\\>' ) + '>' ;
265
+ }
266
+
267
+ return encodeURI ( mdPath ) ;
268
+ }
269
+
270
+ function needsBracketLink ( mdPath : string ) {
271
+ // Links with whitespace or control characters must be enclosed in brackets
272
+ if ( mdPath . startsWith ( '<' ) || / \s | [ \u007F \u0000 - \u001f ] / . test ( mdPath ) ) {
273
+ return true ;
274
+ }
275
+
276
+ // Check if the link has mis-matched parens
277
+ if ( ! / [ \( \) ] / . test ( mdPath ) ) {
278
+ return false ;
279
+ }
280
+
281
+ let previousChar = '' ;
282
+ let nestingCount = 0 ;
283
+ for ( const char of mdPath ) {
284
+ if ( char === '(' && previousChar !== '\\' ) {
285
+ nestingCount ++ ;
286
+ } else if ( char === ')' && previousChar !== '\\' ) {
287
+ nestingCount -- ;
288
+ }
289
+
290
+ if ( nestingCount < 0 ) {
291
+ return true ;
292
+ }
293
+ previousChar = char ;
294
+ }
295
+
296
+ return nestingCount > 0 ;
297
+ }
298
+
0 commit comments