diff --git a/src/lib/make-toolbox-xml.js b/src/lib/make-toolbox-xml.js index 4217b6e6bca71b76bcf156c75d014cef051e3d4d..3443e05d2c0b5f5b763f28a5a146c10940251c5b 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 '<'; + case '>': return '>'; + case '&': return '&'; + case '\'': return '''; + case '"': return '"'; + } + }); +}; + 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 b2b048e4115b0271c70504d2b74deab67198b921..d9cfecd2c2a8b8fa26f46331782b8b590e973a8f 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 () => {