Home > Back-end >  Validate file signatures before accessing them
Validate file signatures before accessing them

Time:12-07

How can I validate file signatures before accessing them in a constructor? When I run the following code, the code after try/catch in the constructor get executed even if there is an exception from checkBlobSignature(). I guess I should use async/await or callback functions, but any tip to make it easier for me?

class ImageEditor {
    constructor(blobs) {
        try {
            for (const blob of blobs) {
                checkBlobSignature(blob);
            }
        } catch(e) {
            throw e.message;
        }
        
        /* 
         * Rest of my constructor code that I want to be executed
         * if the for loop is finished, i.e. there are no exceptions.
         */
    }
    
    checkBlobSignature(blob) {
        let fileReader = new FileReader();
        fileReader.addEventListener("loadend", this.check.bind(this));
        fileReader.readAsArrayBuffer(blob.slice(0, 8));
    }
    
    check(e) {
        let uint8Array = new Uint8Array(e.target.result);
        let signature = "";

        for(const uint8 of uint8Array) {
            signature  = uint8.toString(16);
        }
        signature = signature.toUpperCase();
        
        switch (true) {
            case /^89504E470D0A1A0A/.test(signature): // PNG Hex signature
                break;
            case /^474946383761/.test(signature): // GIF Hex signature
            case /^474946383961/.test(signature): // GIF Hex signature
                break;
            case /^FFD8FF/.test(signature): // JPEG Hex signature
                break;
            case /^6674797069736F6D/.test(signature): // MP4 HEX signature
                break;
            default:
                throw "Invalid signature.";
        }
    }
}

CodePudding user response:

Constructors can't be asynchronous, but you can define an asynchronous static method to construct an instance of the class.

Secondly, you can use Blob.prototype.arrayBuffer() instead of a FileReader to get a Uint8Array from an ArrayBuffer promise. It is also possible to do so with a FileReader, but requires the explicit promise construction antipattern, so I don't recommend it.

Lastly, since checkBlobSignature doesn't use this, you can also make that a static method.

class ImageEditor {
  static async from(blobs) {
    await Promise.all(Array.from(blobs, ImageEditor.checkBlobSignature));
    return new ImageEditor(blobs);
  }

  static async checkBlobSignature(blob) {
    const signatures = [
      [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A], // PNG
      [0x47, 0x49, 0x46, 0x38, 0x37, 0x61], // GIF
      [0x47, 0x49, 0x46, 0x38, 0x39, 0x61], // GIF
      [0xFF, 0xD8, 0xFF], // JPEG
      [0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D], // MP4
    ];
    const maxSignatureLength = Math.max(
      ...signatures.map(signature => signature.length),
    );
    const arrayBuffer = await blob.slice(0, maxSignatureLength).arrayBuffer();
    const uint8Array = new Uint8Array(arrayBuffer);

    if (!signatures.some(
        signature => signature.every((byte, index) => byte === uint8Array[index]),
      )) {
      throw new Error('Invalid signature');
    }
  }

  ImageEditor(blobs) {
    /* Rest of my constructor code that I want to be executed
     * if the for loop is finished, i.e. there are no exceptions.
     */
  }
}
  • Related