From c062d0589b32459fa69359cb67a87eb63ba359d2 Mon Sep 17 00:00:00 2001
From: Karishma Chadha <kchadha@media.mit.edu>
Date: Mon, 4 Feb 2019 21:31:37 -0500
Subject: [PATCH] Escape special characters in user defined strings that can
 appear in the toolbox xml.

---
 src/lib/make-toolbox-xml.js     | 16 ++++++++++++++++
 test/integration/blocks.test.js | 18 ++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js
index 4217b6e6b..3443e05d2 100644
--- a/src/lib/make-toolbox-xml.js
+++ b/src/lib/make-toolbox-xml.js
@@ -138,6 +138,18 @@ const motion = function (isStage, targetId) {
     `;
 };
 
+const xmlEscape = function (unsafe) {
+    return unsafe.replace(/[<>&'"]/g, c => {
+        switch (c) {
+        case '<': return '&lt;';
+        case '>': return '&gt;';
+        case '&': return '&amp;';
+        case '\'': return '&apos;';
+        case '"': return '&quot;';
+        }
+    });
+};
+
 const looks = function (isStage, targetId, costumeName, backdropName) {
     const hello = ScratchBlocks.ScratchMsgs.translate('LOOKS_HELLO', 'Hello!');
     const hmm = ScratchBlocks.ScratchMsgs.translate('LOOKS_HMM', 'Hmm...');
@@ -714,6 +726,10 @@ const makeToolboxXML = function (isStage, targetId, categoriesXML,
     costumeName = '', backdropName = '', soundName = '') {
     const gap = [categorySeparator];
 
+    costumeName = xmlEscape(costumeName);
+    backdropName = xmlEscape(backdropName);
+    soundName = xmlEscape(soundName);
+
     const everything = [
         xmlOpen,
         motion(isStage, targetId), gap,
diff --git a/test/integration/blocks.test.js b/test/integration/blocks.test.js
index d70396032..b39f05a79 100644
--- a/test/integration/blocks.test.js
+++ b/test/integration/blocks.test.js
@@ -191,6 +191,24 @@ describe('Working with the blocks', () => {
         await clickText('newname', scope.blocksTab);
     });
 
+    test('Renaming costume with a special character should not break toolbox', async () => {
+        await loadUri(uri);
+        await clickXpath('//button[@title="Try It"]');
+
+        // Rename the costume
+        await clickText('Costumes');
+        const el = await findByXpath("//input[@value='costume1']");
+        await el.sendKeys('<NewCostume>');
+
+        // Make sure it is updated in the block menu
+        await clickText('Code');
+        await clickText('Looks', scope.blocksTab);
+        await driver.sleep(500); // Wait for scroll to finish
+        await clickText('<NewCostume>', scope.blocksTab);
+
+        await clickText('Sound', scope.blocksTab);
+    });
+
     // NOTE: This test describes the current behavior so that changes are not
     // introduced inadvertly, but I know this is not the desired behavior
     test('Adding costumes DOES NOT update the default costume name in the toolbox', async () => {
-- 
GitLab