Skip to content

Commit

Permalink
Add Scratch.download API
Browse files Browse the repository at this point in the history
  • Loading branch information
GarboMuffin committed Dec 22, 2024
1 parent fdd954c commit c3b60f6
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/extension-support/extension-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Object.assign(global.Scratch, ScratchCommon, {
canGeolocate: () => Promise.resolve(false),
canEmbed: () => Promise.resolve(false),
canDownload: () => Promise.resolve(false),
download: () => Promise.reject(new Error('Scratch.download not supported in sandboxed extensions')),
translate
});

Expand Down
12 changes: 12 additions & 0 deletions src/extension-support/tw-unsandboxed-extension-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,18 @@ const setupUnsandboxedExtensionAPI = vm => new Promise(resolve => {
location.href = url;
};

Scratch.download = async (url, name) => {
if (!await Scratch.canDownload(url, name)) {
throw new Error(`Permission to download ${name} rejected.`);
}
const link = document.createElement('a');
link.href = url;
link.download = name;
document.body.appendChild(link);
link.click();
link.remove();
};

Scratch.translate = createTranslate(vm);

global.Scratch = Scratch;
Expand Down
5 changes: 5 additions & 0 deletions test/unit/tw_sandboxed_extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,8 @@ test('canDownload', async t => {
t.equal(await global.Scratch.canDownload('https://example.com/test.sb3', 'test.sb3'), false);
t.end();
});

test('download', async t => {
await t.rejects(global.Scratch.download('https://turbowarp.org/', 'index.html'), /not supported in sandboxed extension/);
t.end();
});
63 changes: 63 additions & 0 deletions test/unit/tw_unsandboxed_extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,69 @@ test('canDownload', async t => {
t.end();
});

test('download', async t => {
const vm = new VirtualMachine();
UnsandboxedExtensionRunner.setupUnsandboxedExtensionAPI(vm);

const actualDownloadAttempts = [];
global.document = {
createElement: tagName => ({
tagName,
click () {
actualDownloadAttempts.push([
this.href,
this.download
]);
},
remove () {}
}),
body: {
appendChild () {}
}
};

const canDownloadChecks = [];
vm.securityManager.canDownload = (url, name) => {
canDownloadChecks.push([url, name]);
return url === 'https://example.com/safe.txt' && name === 'safe.txt';
};

await global.Scratch.download('https://example.com/safe.txt', 'safe.txt');
await t.rejects(global.Scratch.download('https://example.com/unsafe.txt', 'safe.txt'), /Permission to download/);
await t.rejects(global.Scratch.download('https://example.com/safe.txt', 'unsafe.txt'), /Permission to download/);
await t.rejects(global.Scratch.download('https://example.com/unsafe.txt', 'unsafe.txt'), /Permission to download/);
// eslint-disable-next-line no-script-url
await t.rejects(global.Scratch.download('javascript:alert(1)', 'safe.txt'), /Permission to download/);

t.same(actualDownloadAttempts, [
[
'https://example.com/safe.txt',
'safe.txt'
]
]);

t.same(canDownloadChecks, [
[
'https://example.com/safe.txt',
'safe.txt'
],
[
'https://example.com/unsafe.txt',
'safe.txt'
],
[
'https://example.com/safe.txt',
'unsafe.txt'
],
[
'https://example.com/unsafe.txt',
'unsafe.txt'
]
]);

t.end();
});

test('CREATE_UNSANDBOXED_EXTENSION_API', t => {
const vm = new VirtualMachine();
vm.on('CREATE_UNSANDBOXED_EXTENSION_API', api => {
Expand Down

0 comments on commit c3b60f6

Please sign in to comment.