Skip to content

Commit c3b60f6

Browse files
committed
Add Scratch.download API
1 parent fdd954c commit c3b60f6

File tree

4 files changed

+81
-0
lines changed

4 files changed

+81
-0
lines changed

src/extension-support/extension-worker.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Object.assign(global.Scratch, ScratchCommon, {
8787
canGeolocate: () => Promise.resolve(false),
8888
canEmbed: () => Promise.resolve(false),
8989
canDownload: () => Promise.resolve(false),
90+
download: () => Promise.reject(new Error('Scratch.download not supported in sandboxed extensions')),
9091
translate
9192
});
9293

src/extension-support/tw-unsandboxed-extension-runner.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,18 @@ const setupUnsandboxedExtensionAPI = vm => new Promise(resolve => {
140140
location.href = url;
141141
};
142142

143+
Scratch.download = async (url, name) => {
144+
if (!await Scratch.canDownload(url, name)) {
145+
throw new Error(`Permission to download ${name} rejected.`);
146+
}
147+
const link = document.createElement('a');
148+
link.href = url;
149+
link.download = name;
150+
document.body.appendChild(link);
151+
link.click();
152+
link.remove();
153+
};
154+
143155
Scratch.translate = createTranslate(vm);
144156

145157
global.Scratch = Scratch;

test/unit/tw_sandboxed_extensions.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,8 @@ test('canDownload', async t => {
146146
t.equal(await global.Scratch.canDownload('https://example.com/test.sb3', 'test.sb3'), false);
147147
t.end();
148148
});
149+
150+
test('download', async t => {
151+
await t.rejects(global.Scratch.download('https://turbowarp.org/', 'index.html'), /not supported in sandboxed extension/);
152+
t.end();
153+
});

test/unit/tw_unsandboxed_extensions.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,69 @@ test('canDownload', async t => {
459459
t.end();
460460
});
461461

462+
test('download', async t => {
463+
const vm = new VirtualMachine();
464+
UnsandboxedExtensionRunner.setupUnsandboxedExtensionAPI(vm);
465+
466+
const actualDownloadAttempts = [];
467+
global.document = {
468+
createElement: tagName => ({
469+
tagName,
470+
click () {
471+
actualDownloadAttempts.push([
472+
this.href,
473+
this.download
474+
]);
475+
},
476+
remove () {}
477+
}),
478+
body: {
479+
appendChild () {}
480+
}
481+
};
482+
483+
const canDownloadChecks = [];
484+
vm.securityManager.canDownload = (url, name) => {
485+
canDownloadChecks.push([url, name]);
486+
return url === 'https://example.com/safe.txt' && name === 'safe.txt';
487+
};
488+
489+
await global.Scratch.download('https://example.com/safe.txt', 'safe.txt');
490+
await t.rejects(global.Scratch.download('https://example.com/unsafe.txt', 'safe.txt'), /Permission to download/);
491+
await t.rejects(global.Scratch.download('https://example.com/safe.txt', 'unsafe.txt'), /Permission to download/);
492+
await t.rejects(global.Scratch.download('https://example.com/unsafe.txt', 'unsafe.txt'), /Permission to download/);
493+
// eslint-disable-next-line no-script-url
494+
await t.rejects(global.Scratch.download('javascript:alert(1)', 'safe.txt'), /Permission to download/);
495+
496+
t.same(actualDownloadAttempts, [
497+
[
498+
'https://example.com/safe.txt',
499+
'safe.txt'
500+
]
501+
]);
502+
503+
t.same(canDownloadChecks, [
504+
[
505+
'https://example.com/safe.txt',
506+
'safe.txt'
507+
],
508+
[
509+
'https://example.com/unsafe.txt',
510+
'safe.txt'
511+
],
512+
[
513+
'https://example.com/safe.txt',
514+
'unsafe.txt'
515+
],
516+
[
517+
'https://example.com/unsafe.txt',
518+
'unsafe.txt'
519+
]
520+
]);
521+
522+
t.end();
523+
});
524+
462525
test('CREATE_UNSANDBOXED_EXTENSION_API', t => {
463526
const vm = new VirtualMachine();
464527
vm.on('CREATE_UNSANDBOXED_EXTENSION_API', api => {

0 commit comments

Comments
 (0)