diff --git a/source/assets/js/playground/playground.ts b/source/assets/js/playground/playground.ts index 5e5ded0..41984de 100644 --- a/source/assets/js/playground/playground.ts +++ b/source/assets/js/playground/playground.ts @@ -54,6 +54,17 @@ function debounce(func: Function, timeout = 200) { }, timeout); }; } +/** + * Encode the HTML in a user-submitted string to print safely using innerHTML + * Adapted from https://vanillajstoolkit.com/helpers/encodehtml/ + * @param {string} str The user-submitted string + * @return {string} The sanitized string + */ +function encodeHTML(str: string): string { + return str.replace(/[^\w-_. ]/gi, function (c) { + return `${c.charCodeAt(0)};`; + }); +} function setupPlayground() { const initialState: PlaygroundState = { @@ -161,14 +172,11 @@ function setupPlayground() { } function lineNumberFormatter(number?: number): string { - if (!number) return ''; + if (typeof number === 'undefined') return ''; number = number + 1; return `${number} `; } - // TODO: escape messages to prevent XSS - // Example vector: - // @debug function displayForConsoleLog(item: ConsoleLog): string { if (item.type === 'error') { let lineNumber; @@ -177,12 +185,23 @@ function setupPlayground() { } return `
@error:${lineNumberFormatter( lineNumber, - )} ${item.error?.toString() || ''}
`; + )} ${encodeHTML(item.error?.toString() || '') || ''}`; } else if (['debug', 'warn'].includes(item.type)) { - const lineNumber = item.options.span?.start?.line; + let lineNumber = item.options.span?.start?.line; + if (typeof lineNumber === 'undefined') { + const stack = 'stack' in item.options ? item.options.stack : ''; + const needleFromStackRegex = /^- (\d+):/; + const match = stack?.match(needleFromStackRegex); + if (match && match[1]) { + // Stack trace starts at 1, all others come from span, which starts at 0, so adjust before formatting. + lineNumber = parseInt(match[1]) - 1; + } + } return `@${ item.type - }:${lineNumberFormatter(lineNumber)} ${item.message}
`; + }:${lineNumberFormatter(lineNumber)} ${encodeHTML( + item.message, + )}`; } else return ''; }