Stuk / jszip

Create, read and edit .zip files with Javascript
https://stuk.github.io/jszip/
Other
9.83k stars 1.3k forks source link

getTypeOf() Can't infer datatype in Firefox Addon context #759

Open JadeMaveric opened 3 years ago

JadeMaveric commented 3 years ago

I'm trying to save a bunch of image blob (from canvas.toBlob) into a zip for an addon I'm working on. But I keep getting the Can’t read the data of […]. Is it in a supported JavaScript type error. Here's the code block I'm using

    Promise.all(promises)
    .then( blobs => {
        blobs.forEach((blob, i) => {
            console.log("Insert image", blob);
            zip.file(`Frame ${i}.jpg`, blob, {base64: true});
        });

        console.log("Zipped Files", zip.files);

        zip.generateAsync({type:"blob"})
        .then( blob => {
            console.log("Saving zip...")
            saveAs(blob, "gMeetSlides.zip")
        })
        .catch( err => console.log(err));
    })

In what seems like a repetition of #151 this is the error I get

Error: Can't read the data of 'Frame 0.jpg'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?
    prepareContent moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/lib/jszip.js:3488
    promise callback*[32]</exports.prepareContent moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/lib/jszip.js:3483
    fileAdd moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/lib/jszip.js:1416
    file moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/lib/jszip.js:1572
    downloadZip moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/capture.js:121
    downloadZip moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/capture.js:119
    promise callback*downloadZip moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/capture.js:118
    <anonymous> moz-extension://d3aaac9d-8a41-48a0-9e3c-d5a52f9b1f75/content_scripts/slideshot/capture.js:163

Which leads me to exports.getTypeOf. I added some console logs to it, and this is the result

        exports.getTypeOf = function(input) {
        console.log("Input", input);
        console.log("Blob", input instanceof Blob);
        console.log("Array", input instanceof ArrayBuffer);

2 calls, seems legit, there are 2 images being zipped. (in all trials)

Input ArrayBuffer { byteLength: 12666 }        jszip.js:3361:17
Blob false                                     jszip.js:3362:17
Array false                                    jszip.js:3363:17
Input ArrayBuffer { byteLength: 12666 }        jszip.js:3361:17
Blob false                                     jszip.js:3362:17
Array false                                    jszip.js:3363:17
Error: Can't read the data...

According to this stackoverflow comment it might be better to use input.constructor.name instead of instanceof. I using that in the function blocks, but while that doesn't throw an error, I don't get the download prompt either (not the console.logs in the callback function)

        if (support.uint8array && input.constructor.name === "Uint8Array") {
            return "uint8array";
        }
        if (support.arraybuffer && input.constructor.name === "ArrayBuffer") {
            return "arraybuffer";
        }

Multiple calls, each with a different type!?

Input ArrayBuffer { byteLength: 12664 }   jszip.js:3361:17
Blob false                                jszip.js:3362:17
Array false                               jszip.js:3363:17
Input ArrayBuffer { byteLength: 12664 }   jszip.js:3361:17
Blob false                                jszip.js:3362:17
Array false                               jszip.js:3363:17
Input Uint8Array(12664) [ 255, 216, … ]   jszip.js:3361:17
Blob false                                jszip.js:3362:17
Array false                               jszip.js:3363:17
Input ArrayBuffer { byteLength: 12664 }   jszip.js:3361:17
Blob false                                jszip.js:3362:17
Array false                               jszip.js:3363:17
Input ArrayBuffer { byteLength: 12664 }   jszip.js:3361:17
Blob false                                jszip.js:3362:17
Array false                               jszip.js:3363:17
Input Uint8Array(12664) [ 255, 216, … ]   jszip.js:3361:17
Blob false                                jszip.js:3362:17
Array false                               jszip.js:3362:17

I also tried some console.logs to checkout the return value, and the output is even more confusion

    exports.getTypeOf = function(input) {
        console.log("Input", input);
        console.log("Blob", input instanceof Blob);
        console.log("Array", input instanceof ArrayBuffer);
        let _type = undefined;
        if (typeof input === "string") {
            _type = "string";
        }
        if (Object.prototype.toString.call(input) === "[object Array]") {
            _type = "array";
        }
        if (support.nodebuffer && nodejsUtils.isBuffer(input)) {
            _type = "nodebuffer";
        }
        if (support.uint8array && input.constructor.name === "Uint8Array") {
            _type = "uint8array";
        }
        if (support.arraybuffer && input.constructor.name === "ArrayBuffer") {
            _type = "arraybuffer";
        }
        console.log("Constr Type", input.constructor.name);
        console.log("Return Type", type);
        return _type;
    };

Only 1 call to the function (a Blob?)

Input Blob { size: 12671, type: "image/jpeg" }     jszip.js:3361:17
Blob true                                          jszip.js:3362:17
Array false                                        jszip.js:3363:17
Constr Type undefined                              jszip.js:3380:17
JadeMaveric commented 3 years ago

The addon works as expected on chrome though. Which is ironic, since I'm developing primarily with firefox in mind

Tithen-Firion commented 3 years ago

This issue is easy to reproduce with a UserScript. Install Greasemonkey or Violentmonkey in Firefox. Then install this script:

// ==UserScript==
// @name        test
// @version     1
// @include     https://github.com/*
// @grant       none
// @require     https://cdn.jsdelivr.net/npm/jszip@3.7.1/dist/jszip.js
// ==/UserScript==

(async () => {
  const zip = new JSZip();
  const blob = await zip.generateAsync({type:'blob'});

  try {
    zip.file('test.txt', new Blob(['']));
  }
  catch(error) {
    console.error(error);
  }
  try {
    await JSZip.loadAsync(blob);
  }
  catch(error) {
    console.error(error);
  }
})();

Adding blob to a zip throws Error: Can't read the data of 'test.txt'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?. Loading zip from blob throws Error: Can't read the data of 'the loaded zip file'. Is it in a supported JavaScript type (String, Blob, ArrayBuffer, etc) ?.

Both can be fixed with either #578 or #598. But then loading zip from blob throws Error: Permission denied to access property "constructor". Caused by this line: https://github.com/Stuk/jszip/blob/053d8d5a132e0e89e32892619e7478aa3bf0e0a0/lib/reader/Uint8ArrayReader.js#L18 And I'm not sure how to fix that.

WORKAROUND

Since I don't know when it's going to be fixed and in my case those PRs don't solve my problem I'm using this workaround:

const readAsBinaryString = blob => new Promise(resolve => {
  const reader = new FileReader();
  reader.onload = function(event) {
    resolve(event.target.result);
  };
  reader.readAsBinaryString(blob);
});

(async () => {
  const zip = new JSZip();
  const blob = await zip.generateAsync({type:'blob'});
  zip.file('test.txt', await readAsBinaryString(new Blob([''])), {binary: true});
  await JSZip.loadAsync(await readAsBinaryString(blob));
})();

And now it works.

Related issue https://github.com/greasemonkey/greasemonkey/issues/3120

ethernidee commented 12 months ago

Faced the same problem in Firefox web extension content script.

const encodedText = new TextEncoder().encode('some text'); // ok
const buffer = new Uint8Array(encodedText.buffer); //  ok, but seems like some security flags for new object are missing
buffer.slice(); // Error: Permission denied to access property "constructor"
buffer.subarray(0, 2); // Error: Permission denied to access property "constructor"

Creating new ArrayBufferView from existing ArrayBuffer in web extension content script leads to Error: Permission denied to access property "constructor" on any method call for new object.

Created a bug report here: https://bugzilla.mozilla.org/show_bug.cgi?id=1868675