diff --git a/package-lock.json b/package-lock.json index de70d98..8f9e5ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10592,9 +10592,9 @@ } }, "svelte": { - "version": "3.42.3", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.42.3.tgz", - "integrity": "sha512-pbdtdNZEx2GBqSM6XEgPoHbwtvWBwFLt/1bRmzsyXZO+i424wFnPe7O5B3GOJDPFSxPRztumAW3mL5LPzecWUg==" + "version": "3.42.4", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.42.4.tgz", + "integrity": "sha512-DqC0AmDdBrrbIA+Kzl3yhBb6qCn4vZOAfxye2pTnIpinLegyagC5sLI8Pe9GPlXu9VpHBXIwpDDedpMfu++epA==" }, "svelte-dev-helper": { "version": "1.1.9", diff --git a/package.json b/package.json index 3ce04cc..07ca9ed 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "scratch-svg-renderer": "git://github.com/TurboWarp/scratch-svg-renderer.git#develop", "scratch-vm": "git://github.com/TurboWarp/scratch-vm.git#develop", "sha.js": "^2.4.11", - "svelte": "^3.42.3" + "svelte": "^3.42.4" }, "devDependencies": { "@babel/core": "^7.15.0", diff --git a/src/p4/SelectProject.svelte b/src/p4/SelectProject.svelte index 22d2123..9379a7f 100644 --- a/src/p4/SelectProject.svelte +++ b/src/p4/SelectProject.svelte @@ -134,9 +134,12 @@ e.target.select(); }; const handleDrop = ({detail: dataTransfer}) => { - $type = 'file'; - files = dataTransfer.files; - fileInputElement.files = files; + const name = dataTransfer.files[0].name; + if (name.endsWith('.sb') || name.endsWith('.sb2') || name.endsWith('.sb3')) { + $type = 'file'; + files = dataTransfer.files; + fileInputElement.files = files; + } }; diff --git a/src/packager/download-project.js b/src/packager/download-project.js index da873a8..0495e97 100644 --- a/src/packager/download-project.js +++ b/src/packager/download-project.js @@ -12,6 +12,16 @@ const identifyProjectType = (projectData) => { return null; }; +const isScratch1Project = (uint8array) => { + const MAGIC = 'ScratchV'; + for (let i = 0; i < MAGIC.length; i++) { + if (uint8array[i] !== MAGIC.charCodeAt(i)) { + return false; + } + } + return true; +}; + const unknownAnalysis = () => ({ stageVariables: {}, usesMusic: true @@ -270,15 +280,17 @@ const downloadProject = async (data, progressCallback) => { }); analysis = downloaded.analysis; } else { - // It's a binary blob blob = new Blob([data]); - let zip; - try { - zip = await JSZip.loadAsync(data); - } catch (e) { - // It's a Scratch 1 project, probably - } - if (zip) { + if (isScratch1Project(bufferView)) { + analysis = unknownAnalysis(); + } else { + let zip; + try { + zip = await JSZip.loadAsync(data); + } catch (e) { + console.warn(e); + throw new Error('Cannot parse project'); + } const projectDataFile = zip.file('project.json'); const projectDataText = await projectDataFile.async('text'); const projectData = JSON.parse(projectDataText); @@ -288,8 +300,6 @@ const downloadProject = async (data, progressCallback) => { } else { analysis = analyzeScratch2(projectData); } - } else { - analysis = unknownAnalysis(); } } diff --git a/src/scaffolding/dropper.js b/src/scaffolding/drop-area.js similarity index 95% rename from src/scaffolding/dropper.js rename to src/scaffolding/drop-area.js index 0f23dae..b537f85 100644 --- a/src/scaffolding/dropper.js +++ b/src/scaffolding/drop-area.js @@ -1,7 +1,7 @@ import styles from './style.css'; import {readAsText} from '../common/readers'; -class Dropper { +class DropArea { constructor (el, callback) { this.el = el; this.callback = callback; @@ -32,4 +32,4 @@ class Dropper { } } -export default Dropper; +export default DropArea; diff --git a/src/scaffolding/monitor.js b/src/scaffolding/monitor.js index d47de67..35a0c15 100644 --- a/src/scaffolding/monitor.js +++ b/src/scaffolding/monitor.js @@ -1,5 +1,5 @@ import ContextMenu from './context-menu'; -import Dropper from './dropper'; +import DropArea from './drop-area'; import styles from './style.css'; import {readAsText} from '../common/readers'; @@ -269,7 +269,7 @@ class ListMonitor extends Monitor { this.root.appendChild(this.rowsOuter); this.root.appendChild(this.footer); - this.dropper = new Dropper(this.rowsOuter, this.dropperCallback.bind(this)); + this.dropper = new DropArea(this.rowsOuter, this.dropperCallback.bind(this)); this.handleImport = this.handleImport.bind(this); this.handleExport = this.handleExport.bind(this); @@ -315,9 +315,7 @@ class ListMonitor extends Monitor { const files = e.target.files; if (files.length === 0) return; const file = files[0]; - readAsText(file).then((text) => { - this.import(reader.result); - }); + readAsText(file).then((text) => this.import(text)); }); fileSelector.click(); } diff --git a/src/scaffolding/question.js b/src/scaffolding/question.js index 0512ad7..05bb3c3 100644 --- a/src/scaffolding/question.js +++ b/src/scaffolding/question.js @@ -1,4 +1,4 @@ -import Dropper from './dropper'; +import DropArea from './drop-area'; import styles from './style.css'; class Question { @@ -25,7 +25,7 @@ class Question { this.input.className = styles.questionInput; this.input.addEventListener('keypress', this.onkeypress.bind(this)); - this.dropper = new Dropper(this.input, this.dropperCallback.bind(this)); + this.dropper = new DropArea(this.input, this.dropperCallback.bind(this)); this.submitButton = document.createElement('button'); this.submitButton.className = styles.questionSubmitButton; diff --git a/test/packager/download-project.test.js b/test/packager/download-project.test.js index 859524c..fa08b36 100644 --- a/test/packager/download-project.test.js +++ b/test/packager/download-project.test.js @@ -60,3 +60,12 @@ test('sb', async () => { expect(project.blob.size).toBe(554); expect(project.type).toBe('blob'); }); + +test('invalid project', async () => { + try { + await downloadProject(readTestProject('invalid.txt'), () => {}); + } catch (e) { + return; + } + throw new Error('Expected error, got success'); +}); diff --git a/test/packager/projects/invalid.txt b/test/packager/projects/invalid.txt new file mode 100644 index 0000000..590847a --- /dev/null +++ b/test/packager/projects/invalid.txt @@ -0,0 +1 @@ +I am not a valid project