Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/fiddle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,19 +92,26 @@ export class FiddleFactory {
const md5sum = createHash('md5');
for (const content of map.values()) md5sum.update(content);
const hash = md5sum.digest('hex');
const folder = path.join(this.fiddles, hash);
const folder = path.resolve(this.fiddles, hash);
await fs.promises.mkdir(folder, { recursive: true });
d({ folder });

// save content to that temp directory
await Promise.all(
[...map.entries()].map(([filename, content]) =>
util.promisify(fs.writeFile)(
path.join(folder, filename),
content,
'utf8',
),
),
[...map.entries()].map(([filename, content]) => {
const filePath = path.resolve(folder, filename);
const relative = path.relative(folder, filePath);
if (
!relative ||
relative.startsWith('..') ||
path.isAbsolute(relative)
) {
throw new Error(
`Refusing to write file outside of fiddle: "${filename}"`,
);
}
return util.promisify(fs.writeFile)(filePath, content, 'utf8');
}),
);

return new Fiddle(path.join(folder, 'main.js'), 'entries');
Expand Down
8 changes: 8 additions & 0 deletions src/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ function getZipName(version: string): string {
return `electron-v${version}-${process.platform}-${process.arch}.zip`;
}

function assertValidVersion(version: string): void {
if (!semver.valid(version)) {
throw new Error(`Invalid Electron version: "${version}"`);
}
}

export type ProgressObject = { percent: number };

/**
Expand Down Expand Up @@ -163,6 +169,7 @@ export class Installer extends EventEmitter {
public async remove(version: string): Promise<void> {
const d = debug('fiddle-core:Installer:remove');
d(version);
assertValidVersion(version);
let isBinaryDeleted = false;
// utility to re-run removal functions upon failure
// due to windows filesystem lockfile jank
Expand Down Expand Up @@ -270,6 +277,7 @@ export class Installer extends EventEmitter {
opts?: Partial<InstallerParams>,
): Promise<ElectronBinary> {
const d = debug(`fiddle-core:Installer:${version}:ensureDownloadedImpl`);
assertValidVersion(version);
const { electronDownloads } = this.paths;
const zipFile = path.join(electronDownloads, getZipName(version));
const zipFileExists = fs.existsSync(zipFile);
Expand Down
11 changes: 11 additions & 0 deletions tests/fiddle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@ describe('FiddleFactory', () => {
}
});

it('rejects entries with filenames that escape the fiddle directory', async () => {
const files: [string, string][] = [
['main.js', '"use strict";'],
[path.join('..', '..', 'escaped.txt'), 'pwned'],
];
await expect(fiddleFactory.create(files)).rejects.toThrow(
/outside of fiddle/,
);
expect(fs.existsSync(path.join(tmpdir, 'escaped.txt'))).toBe(false);
});

it('reads fiddles from gists', async () => {
const gistId = '642fa8daaebea6044c9079e3f8a46390';
const fiddle = await fiddleFactory.create(gistId);
Expand Down
15 changes: 15 additions & 0 deletions tests/installer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ describe('Installer', () => {
expect(nockScope.isDone());
});

it('rejects versions that are not valid semver', async () => {
await expect(
installer.ensureDownloaded(path.join('..', 'x')),
).rejects.toThrow(/Invalid Electron version/);
});

it('resets install state on error', async () => {
// setup: version is not installed
expect(installer.state(version)).toBe(missing);
Expand Down Expand Up @@ -297,6 +303,15 @@ describe('Installer', () => {
expect(fs.existsSync(extractDir)).toBe(false);
expect(events).toStrictEqual([{ version, state: missing }]);
});

it('rejects versions that are not valid semver', async () => {
const canary = path.join(tmpdir, 'canary');
await fs.promises.mkdir(canary);
await expect(installer.remove(path.join('..', 'canary'))).rejects.toThrow(
/Invalid Electron version/,
);
expect(fs.existsSync(canary)).toBe(true);
});
});

describe('install()', () => {
Expand Down