From 94bae393f7d46cce0701243805d811c7ffa7e90e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaros=C5=82aw=20Sadzi=C5=84ski?=
 <jaroslaw.sadzinski@gmail.com>
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?=
 <jaroslaw.sadzinski@gmail.com>
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");