From ffc19c51afa814a622d3cdaf14c4ee504e1c4ea1 Mon Sep 17 00:00:00 2001 From: Yan233_ Date: Wed, 29 Jul 2026 23:20:21 +0800 Subject: [PATCH 1/2] fix: safely serialize webview initialization data Encode settings and other dynamic values before embedding them in the webview's inline script, preserving Windows paths and preventing script termination. --- src/utilsPure.ts | 15 +++++++++++++++ src/webview/JudgeView.ts | 21 +++++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/src/utilsPure.ts b/src/utilsPure.ts index 0b39211..1e62035 100644 --- a/src/utilsPure.ts +++ b/src/utilsPure.ts @@ -48,3 +48,18 @@ export const toAsciiFilename = (input: string): string => { }) .join(''); }; + +/** Serializes a value for use as an expression inside an HTML script element. */ +export const serializeForInlineScript = (value: unknown): string => { + const serialized = JSON.stringify(value); + if (serialized === undefined) { + throw new TypeError('Cannot serialize value for an inline script'); + } + + return serialized + .replace(/&/g, '\\u0026') + .replace(//g, '\\u003e') + .replace(/\u2028/g, '\\u2028') + .replace(/\u2029/g, '\\u2029'); +}; diff --git a/src/webview/JudgeView.ts b/src/webview/JudgeView.ts index 8fecde8..73bdb37 100644 --- a/src/webview/JudgeView.ts +++ b/src/webview/JudgeView.ts @@ -19,6 +19,7 @@ import { } from '../preferences'; import { setOnlineJudgeEnv, onlineJudgeEnv } from '../compiler'; import { translations } from './translations'; +import { serializeForInlineScript } from '../utilsPure'; class JudgeViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = 'cph.judgeView'; @@ -329,6 +330,17 @@ class JudgeViewProvider implements vscode.WebviewViewProvider { pythonCommand = 'python'; } + const initialState = serializeForInlineScript({ + meowAudioUri: meowAudioUri.toString(), + remoteMessage, + generatedJsonUri: generatedJsonUri.toString(), + remoteServerAddress, + showLiveUserCount, + showOutputDifference: !getHideOutputDifferencePref(), + translations: translation, + pythonCommand, + }); + const html = ` @@ -349,14 +361,7 @@ class JudgeViewProvider implements vscode.WebviewViewProvider { // Since the react script takes time to load, the problem is sent to the webview before it has even loaded. // So, for the initial request, ask for it again. window.vscodeApi = acquireVsCodeApi(); - window.meowAudioUri = '${meowAudioUri}'; - window.remoteMessage = '${remoteMessage}'; - window.generatedJsonUri = '${generatedJsonUri}'; - window.remoteServerAddress = '${remoteServerAddress}'; - window.showLiveUserCount = ${showLiveUserCount}; - window.showOutputDifference = ${!getHideOutputDifferencePref()}; - window.translations = ${JSON.stringify(translation)}; - window.pythonCommand = '${pythonCommand}'; + Object.assign(window, ${initialState}); document.addEventListener( 'DOMContentLoaded', From 9712145adc43599f6a5742d0d80253090bf8822f Mon Sep 17 00:00:00 2001 From: Yan233_ Date: Wed, 29 Jul 2026 23:20:21 +0800 Subject: [PATCH 2/2] test: cover inline script serialization Add regression coverage for Windows command paths and values that could alter or terminate an inline script. --- src/tests/utilsPure.test.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/tests/utilsPure.test.ts b/src/tests/utilsPure.test.ts index 6f077c9..85ac3c5 100644 --- a/src/tests/utilsPure.test.ts +++ b/src/tests/utilsPure.test.ts @@ -1,5 +1,9 @@ globalThis.logger = { ...console }; -import { words_in_text, toPascalCase } from '../utilsPure'; +import { + serializeForInlineScript, + words_in_text, + toPascalCase, +} from '../utilsPure'; describe('problem name parser', () => { test('mix of latin, non latin and numbers', () => { @@ -105,3 +109,25 @@ describe('toPascalCase', () => { expect(toPascalCase('Binary_Search_Tree')).toBe('BinarySearchTree'); }); }); + +describe('serializeForInlineScript', () => { + test('preserves Windows paths without creating escape sequences', () => { + const pythonCommand = + 'C:\\Users\\ExampleUser\\AppData\\Roaming\\uv\\python\\cpython-xxx\\python.exe'; + const serialized = serializeForInlineScript(pythonCommand); + + expect(serialized).toContain('C:\\\\Users\\\\ExampleUser'); + expect(serialized).toContain( + 'Roaming\\\\uv\\\\python\\\\cpython-xxx\\\\python.exe', + ); + expect(JSON.parse(serialized)).toBe(pythonCommand); + }); + + test('escapes characters that can terminate or alter an inline script', () => { + const value = "'\n\u2028\u2029&"; + const serialized = serializeForInlineScript({ value }); + + expect(serialized).not.toMatch(/[<>&\u2028\u2029]/u); + expect(JSON.parse(serialized)).toEqual({ value }); + }); +});