Out of Bounds Offset during File Compression

68 Views Asked by At

I have encountered the following function and its usage, yet I am unable to ascertain the reason behind the reported error stating that the offset is out of bounds.

My function:

function zip(files, zipFileName) {
    const zipData = [];
    files.forEach((file) => {
        const content = new TextEncoder().encode(file.content);
        zipData.push({
            name: file.name,
            content,
            contentLength: content.length
        });
    });
    const centralDirectory = [];
    let currentOffset = 0;
    zipData.forEach((file) => {
        centralDirectory.push({
            name: file.name,
            offset: currentOffset,
            contentLength: file.contentLength
        });
        currentOffset += file.contentLength;
    });
    const zipArray = [];
    zipData.forEach((file) => {
        const header = new Uint8Array([
            0x50, 0x4b, 0x03, 0x04, // local file header signature
            0x0A, 0x00, // version needed to extract
            0x00, 0x00, // general purpose bit flag
            0x00, 0x00, // compression method
            0x00, 0x00, 0x00, 0x00, // file modification time
            0x00, 0x00, 0x00, 0x00, // file modification date
            0x00, 0x00, 0x00, 0x00, // CRC-32
            0x00, 0x00, 0x00, 0x00, // compressed size
            0x00, 0x00, 0x00, 0x00, // uncompressed size
            file.name.length, 0x00 // file name length
        ]);
        const headerArray = new Uint8Array(header.length + file.name.length);
        headerArray.set(header);
        headerArray.set(new TextEncoder().encode(file.name), header.length);
        const content = new Uint8Array(file.content);
        const fileEntry = new Uint8Array(headerArray.length + content.length);
        fileEntry.set(headerArray);
        fileEntry.set(content, headerArray.length);
        zipArray.push(fileEntry);
    });
    const centralDirectoryArray = [];
    centralDirectory.forEach((file) => {
        const header = new Uint8Array([
            0x50, 0x4b, 0x01, 0x02, // central file header signature
            0x0A, 0x00, // version made by
            0x0A, 0x00, // version needed to extract
            0x00, 0x00, // general purpose bit flag
            0x00, 0x00, // compression method
            0x00, 0x00, 0x00, 0x00, // file modification time
            0x00, 0x00, 0x00, 0x00, // file modification date
            0x00, 0x00, 0x00, 0x00, // CRC-32
            0x00, 0x00, 0x00, 0x00, // compressed size
            0x00, 0x00, 0x00, 0x00, // uncompressed size
            file.name.length, 0x00, // file name length
            0x00, 0x00, // extra field length
            0x00, 0x00, // file comment length
            0x00, 0x00, // disk number start
            0x00, 0x00, // internal file attributes
            0x00, 0x00, 0x00, 0x00, // external file attributes
            file.currentOffset & 0xFF, // relative offset of local header (lo)
            (file.currentOffset >> 8) & 0xFF, // relative offset of local header (hi)
        ]);
        const headerArray = new Uint8Array(header.length + file.name.length);
        headerArray.set(header);
        headerArray.set(new TextEncoder().encode(file.name), header.length);
        centralDirectoryArray.push(headerArray);
    });
    const endOfCentralDirectory = new Uint8Array([
        0x50, 0x4b, 0x05, 0x06, // end of central directory signature
        0x00, 0x00, 0x00, 0x00, // number of this disk
        0x00, 0x00, 0x00, 0x00, // number of the disk with the start of the central directory
        centralDirectoryArray.length & 0xFF, // total number of entries in the central directory on this disk (lo)
        (centralDirectoryArray.length >> 8) & 0xFF, // total number of entries in the central directory on this disk (hi)
        centralDirectoryArray.length & 0xFF, // total number of entries in the central directory (lo)
        (centralDirectoryArray.length >> 8) & 0xFF, // total number of entries in the central directory (hi)
        centralDirectoryArray.reduce((acc, entry) => acc + entry.length, 0) & 0xFFFFFFFF, // size of the central directory (lo)
        ((centralDirectoryArray.reduce((acc, entry) => acc + entry.length, 0) >> 8) & 0xFFFFFFFF) & 0xFF, // size of the central directory (hi)
        (currentOffset & 0xFFFFFFFF) & 0xFF, // offset of start of central directory with respect to the starting disk number (lo)
        ((currentOffset & 0xFFFFFFFF) >> 8) & 0xFF, // offset of start of central directory with respect to the starting disk number (hi)
        0x00, 0x00 // .zip file comment length
    ]);
    const zipFile = new Uint8Array(zipArray.reduce((acc, entry) => acc + entry.length, 0) + centralDirectoryArray.reduce((acc, entry) => acc + entry.length, 0) + endOfCentralDirectory.length);
    zipArray.forEach((entry) => {
        zipFile.set(entry, currentOffset);
        currentOffset += entry.length;
    });
    centralDirectoryArray.forEach((entry) => {
        zipFile.set(entry, currentOffset);
        currentOffset += entry.length;
    });
    zipFile.set(endOfCentralDirectory, currentOffset);
    const zipBlob = new Blob([zipFile], { type: 'application/zip' });
    const link = document.createElement('a');
    link.href = window.URL.createObjectURL(zipBlob);
    link.download = zipFileName;
    link.click();
    return zipBlob;
}

const files = [
    {
        name: 'file1.txt',
        content: 'This is the content of file 1.',
    },
    {
        name: 'file2.txt',
        content: 'This is the content of file 2.',
    }
];

// Zip the files
const zipFileName = 'Archive.zip';
zip(files, zipFileName);

But the browser throws this error

Uncaught RangeError: offset is out of bounds
at Uint8Array.set (<anonymous>)
at file.js:228:21
at Array.forEach (<anonymous>)
at File.zip (file.js:227:31)
at index.js:17:19

The line with the error is the call to zipFile.set() in

    centralDirectory.forEach((entry) => {
        zipFile.set(entry, currentOffset);
        currentOffset += entry.length;
    });

"I haven't attempted anything thus far, but I find myself in need of assistance!

Could someone please guide me on how to solve this issue? Alternatively, is it possible that there's a bug within the zip function? If so, could you please point out where it might be located?

1

There are 1 best solutions below

3
Barmar On BEST ANSWER

The problem is that you're starting the loops that fill in zipFile using the value of currentOffset at the end of the zipData.foreach() loop. You need to reset it to 0 before filling in zipFile. So add

    currentOffset = 0;

before

    zipArray.forEach((entry) => {