From 94bae393f7d46cce0701243805d811c7ffa7e90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?= Date: Mon, 23 Dec 2024 13:16:59 +0100 Subject: [PATCH 1/2] Sanitizing lolication files --- buildtools/build.sh | 1 + buildtools/sanitize_translations.js | 76 +++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 buildtools/sanitize_translations.js diff --git a/buildtools/build.sh b/buildtools/build.sh index 4c66a34562..68b16102ba 100755 --- a/buildtools/build.sh +++ b/buildtools/build.sh @@ -18,6 +18,7 @@ if [[ -e ext/buildtools/webpack.config.js ]]; then fi set -x +node buildtools/sanitize_translations.js tsc --build $PROJECT buildtools/update_type_info.sh app webpack --config $WEBPACK_CONFIG --mode production diff --git a/buildtools/sanitize_translations.js b/buildtools/sanitize_translations.js new file mode 100644 index 0000000000..9459631cb1 --- /dev/null +++ b/buildtools/sanitize_translations.js @@ -0,0 +1,76 @@ +// This file should be run during build. It will go through all the translations in the static/locales +// directory, and pass every key and value through the sanitizer. + +const fs = require('fs'); +const path = require('path'); +// Initialize purifier. +const createDOMPurify = require('dompurify'); +const { JSDOM } = require('jsdom'); +const window = new JSDOM('').window; +const DOMPurify = createDOMPurify(window); +DOMPurify.addHook('uponSanitizeAttribute', handleSanitizeAttribute); +function handleSanitizeAttribute(node) { + if (!('target' in node)) { return; } + node.setAttribute('target', '_blank'); +} + +const directoryPath = readDirectoryPath(); + +const fileStream = fs.readdirSync(directoryPath) + .map((file) => path.join(directoryPath, file)) + // Make sure it's a file + .filter((file) => fs.lstatSync(file).isFile()) + // Make sure it is json file + .filter((file) => file.endsWith(".json")) + // Read the contents and put it into an array [path, json] + .map((file) => [file, JSON.parse(fs.readFileSync(file, "utf8"))]); + +console.debug(`Found ${fileStream.length} files to sanitize`); + +const sanitized = fileStream.map(([file, json]) => { + return [file, json, sanitizedJson(json)]; +}); + +const onlyDifferent = sanitized.filter(([file, json, sanitizedJson]) => { + return JSON.stringify(json) !== JSON.stringify(sanitizedJson); +}); + +console.debug(`Found ${onlyDifferent.length} files that need sanitizing`); + +// Write the sanitized json back to the files +onlyDifferent.forEach(([file, json, sanitizedJson]) => { + console.info(`Sanitizing ${file}`); + fs.writeFileSync(file, JSON.stringify(sanitizedJson, null, 2)); +}); + +console.info("Sanitization complete"); + +function sanitizedJson(json) { + // This is recursive function as some keys can be objects themselves, but all values are either + // strings or objects. + return Object.keys(json).reduce((acc, key) => { + const value = json[key]; + if (typeof value === "string") { + acc[key] = purify(value); + } else if (typeof value === "object") { + acc[key] = sanitizedJson(value); + } + return acc; + }, {}); +} + + +function readDirectoryPath() { + // Directory path is optional, it defaults to static/locales, but can be passed as an argument. + const args = process.argv.slice(2); + if (args.length > 1) { + console.error("Too many arguments, expected at most 1 argument."); + process.exit(1); + } + return args[0] || path.join(__dirname, "../static/locales"); +} + +function purify(inputString) { + // This removes any html tags from the string + return DOMPurify.sanitize(inputString); +} From ad63d5692a8caa46d299df0b1a733bd56478e7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?= Date: Mon, 23 Dec 2024 16:18:24 +0100 Subject: [PATCH 2/2] Addressing comments --- buildtools/sanitize_translations.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/buildtools/sanitize_translations.js b/buildtools/sanitize_translations.js index 9459631cb1..874c7392f3 100644 --- a/buildtools/sanitize_translations.js +++ b/buildtools/sanitize_translations.js @@ -21,9 +21,9 @@ const fileStream = fs.readdirSync(directoryPath) // Make sure it's a file .filter((file) => fs.lstatSync(file).isFile()) // Make sure it is json file - .filter((file) => file.endsWith(".json")) + .filter((file) => file.endsWith(".json")) // Read the contents and put it into an array [path, json] - .map((file) => [file, JSON.parse(fs.readFileSync(file, "utf8"))]); + .map((file) => [file, JSON.parse(fs.readFileSync(file, "utf8"))]); console.debug(`Found ${fileStream.length} files to sanitize`); @@ -40,7 +40,7 @@ console.debug(`Found ${onlyDifferent.length} files that need sanitizing`); // Write the sanitized json back to the files onlyDifferent.forEach(([file, json, sanitizedJson]) => { console.info(`Sanitizing ${file}`); - fs.writeFileSync(file, JSON.stringify(sanitizedJson, null, 2)); + fs.writeFileSync(file, JSON.stringify(sanitizedJson, null, 4) + "\n"); }); console.info("Sanitization complete");