mirror of
https://github.com/n8n-io/n8n.git
synced 2026-07-31 13:05:42 +02:00
Some checks are pending
Build: Benchmark Image / build (push) Waiting to run
CI: Master (Build, Test, Lint) / Build for Github Cache (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (22.22.3) (push) Waiting to run
CI: Master (Build, Test, Lint) / Unit tests (24.15.0) (push) Waiting to run
CI: Master (Build, Test, Lint) / Lint (push) Waiting to run
CI: Master (Build, Test, Lint) / Performance (push) Waiting to run
CI: Master (Build, Test, Lint) / Notify Slack on failure (push) Blocked by required conditions
Util: Sync API Docs / sync-public-api (push) Waiting to run
111 lines
3.7 KiB
TypeScript
111 lines
3.7 KiB
TypeScript
import * as fflate from 'fflate';
|
|
|
|
import { boundedUnzip } from '../../decompress/BoundedUnzip';
|
|
|
|
type CompressionLevel = NonNullable<fflate.ZipOptions['level']>;
|
|
|
|
function createZipData(
|
|
files: Record<string, number>,
|
|
options?: { compressionLevel?: CompressionLevel },
|
|
): Buffer {
|
|
const zippable: fflate.Zippable = {};
|
|
for (const [name, size] of Object.entries(files)) {
|
|
zippable[name] = [new Uint8Array(size), { level: options?.compressionLevel ?? 6 }];
|
|
}
|
|
return Buffer.from(fflate.zipSync(zippable));
|
|
}
|
|
|
|
function createZipWithUnsupportedCompression(): Buffer {
|
|
const compressed = createZipData({ 'file.txt': 1 });
|
|
|
|
compressed[8] = 99;
|
|
compressed[9] = 0;
|
|
|
|
const centralDirectoryOffset = compressed.indexOf(Buffer.from([0x50, 0x4b, 0x01, 0x02]));
|
|
if (centralDirectoryOffset === -1) throw new Error('Central directory header not found');
|
|
|
|
compressed[centralDirectoryOffset + 10] = 99;
|
|
compressed[centralDirectoryOffset + 11] = 0;
|
|
|
|
return compressed;
|
|
}
|
|
|
|
describe('boundedUnzip', () => {
|
|
it('should decompress a zip with files within the size limit', async () => {
|
|
const compressed = createZipData({ 'file1.txt': 512, 'file2.txt': 256 });
|
|
const result = await boundedUnzip(compressed, 2048, 100);
|
|
|
|
expect(Object.keys(result)).toHaveLength(2);
|
|
expect(result['file1.txt']).toBeInstanceOf(Buffer);
|
|
expect(result['file1.txt'].length).toBe(512);
|
|
expect(result['file2.txt'].length).toBe(256);
|
|
});
|
|
|
|
it('should reject when total decompressed output exceeds the size limit', async () => {
|
|
const compressed = createZipData({ 'file1.txt': 1024, 'file2.txt': 1024 });
|
|
|
|
await expect(boundedUnzip(compressed, 1500, 100)).rejects.toThrow(
|
|
'The decompressed output exceeds the maximum allowed size of 0 MB',
|
|
);
|
|
});
|
|
|
|
it('should reject when entry count exceeds the limit', async () => {
|
|
const files: Record<string, number> = {};
|
|
for (let i = 0; i < 5; i++) {
|
|
files[`file${i}.txt`] = 10;
|
|
}
|
|
const compressed = createZipData(files);
|
|
|
|
await expect(boundedUnzip(compressed, 1024 * 1024, 3)).rejects.toThrow(
|
|
'The archive contains more than 3 entries',
|
|
);
|
|
});
|
|
|
|
it('should handle empty zip archive', async () => {
|
|
const compressed = createZipData({});
|
|
const result = await boundedUnzip(compressed, 1024, 100);
|
|
|
|
expect(Object.keys(result)).toHaveLength(0);
|
|
});
|
|
|
|
it('should skip directory entries', async () => {
|
|
const zippable: fflate.Zippable = {
|
|
folder: {},
|
|
'folder/file.txt': [new Uint8Array(100), { level: 6 }],
|
|
};
|
|
const compressed = Buffer.from(fflate.zipSync(zippable));
|
|
const result = await boundedUnzip(compressed, 1024, 100);
|
|
|
|
expect(Object.keys(result)).toEqual(expect.arrayContaining(['folder/file.txt']));
|
|
expect(result['folder']).toBeUndefined();
|
|
});
|
|
|
|
it('should handle stored (uncompressed) zip entries', async () => {
|
|
const compressed = createZipData({ 'stored.txt': 256 }, { compressionLevel: 0 });
|
|
const result = await boundedUnzip(compressed, 1024, 100);
|
|
|
|
expect(result['stored.txt'].length).toBe(256);
|
|
});
|
|
|
|
it('should reject truncated zip archives', async () => {
|
|
const compressed = createZipData({ 'file.txt': 256 });
|
|
const truncated = compressed.subarray(0, compressed.length - 10);
|
|
|
|
await expect(boundedUnzip(truncated, 1024, 100)).rejects.toThrow('invalid zip data');
|
|
});
|
|
|
|
it('should reject invalid zip data', async () => {
|
|
await expect(boundedUnzip(Buffer.from('invalid zip data'), 1024, 100)).rejects.toThrow(
|
|
'invalid zip data',
|
|
);
|
|
});
|
|
|
|
it('should surface zip stream errors', async () => {
|
|
const compressed = createZipWithUnsupportedCompression();
|
|
|
|
await expect(boundedUnzip(compressed, 1024, 100)).rejects.toThrow(
|
|
'ZIP entry "file.txt" couldn\'t be decompressed. Check the archive and try again. Original error: unknown compression type 99',
|
|
);
|
|
});
|
|
});
|