How to convert base64 byte data into downloadable pdf file? - javascript

I have an encoded base64 data from an API response and stored in a variable encodedBase64.
let encodedBase64 = 'some base64 encoded long data';
function base64ToArrayBuffer(base64) {
var binaryString = window.atob(base64);
console.log('binaryString ', binaryString);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes;
}
function saveByteArray(reportName, byte) {
var blob = new Blob([byte], {type: "application/pdf"});
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
var fileName = reportName;
link.download = fileName;
link.click();
};
var sampleArr = base64ToArrayBuffer(encodedBase64);
saveByteArray("Sample Report", sampleArr);
after executing this code i am able to download pdf file names SampleReport.pdf but when i open this it is showing Failed to load PDF document. error
what is the wrong in my code ?

difficult to get it done using front end side
but it can be done easily using Nodejs using the following code.
fs.writeFile('pdfFileName.pdf', base64DataString, {encoding: 'base64'}, error => {
if (error) {
throw error;
} else {
console.log('buffer saved!');
}
});

Related

Encryption of files with CryptoJs and decryption in Python (AES CBC)

I'm trying to encrypt files (trying with png) in Javascript (ReactJs) and I want to be able to decrypt the files and restore their original state in Python.
I've tried multiple approaches but the below JS code was the only one I got working properly. With the JS code, I'm able to get back the png in its original form. However, I seem to be missing something with padding as I keep getting:
Traceback (most recent call last):
File "***/encrypt_decrypt_using_aes.py", line 110, in <module>
decrypted = decrypt(encData, key, iv)
File "***/encrypt_decrypt_using_aes.py", line 103, in decrypt
return unpad(cipher.decrypt(enc),16)
File "/usr/local/lib/python3.9/site-packages/Crypto/Util/Padding.py", line 90, in unpad
raise ValueError("Padding is incorrect.")
ValueError: Padding is incorrect.
I tried playing with ZeroPadding, NoPadding, PKCS7 but nothing worked.
Any ideas on what I am missing?
JS code:
function decrypt(input) {
var file = input;
var reader = new FileReader();
reader.onload = () => {
var key = "1234567887654321";
var decrypted = CryptoJS.AES.decrypt(reader.result, key, {mode: CryptoJS.mode.CBC}); // Decryption: I: Base64 encoded string (OpenSSL-format) -> O: WordArray
var typedArray = convertWordArrayToUint8Array(decrypted); // Convert: WordArray -> typed array
var fileDec = new Blob([typedArray]); // Create blob from typed array
var a = document.createElement("a");
var url = window.URL.createObjectURL(fileDec);
var filename = file.name.substr(0, file.name.length - 4);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
};
reader.readAsText(file);
}
function encrypt(input) {
var file = input
console.log("In Encrypt")
console.log(file)
var reader = new FileReader();
var iv = CryptoJS.enc.Utf8.parse('53509bee-8207-11');
reader.onload = () => {
var key = "1234567887654321";
var wordArray = CryptoJS.lib.WordArray.create(reader.result); // Convert: ArrayBuffer -> WordArray
var encrypted = CryptoJS.AES.encrypt(wordArray, key, {iv: iv, mode: CryptoJS.mode.CBC}).toString(); // Encryption: I: WordArray -> O: -> Base64 encoded string (OpenSSL-format)
var fileEnc = new Blob([encrypted]); // Create blob from string
// var a = document.createElement("a");
// var url = window.URL.createObjectURL(fileEnc);
var filename = input.name + ".enc";
console.log(filename)
var file = new File([fileEnc], filename);
// a.href = url;
// a.download = filename;
// a.click();
// window.URL.revokeObjectURL(url);
setFiles2State(files2State => ({
fileList2: [...files2State.fileList2, file],
}));
console.log("OUT LIST")
console.log(files2State)
};
reader.readAsArrayBuffer(file);
}
Python code (Not working):
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
def encrypt(data,key,iv):
data= pad(data.encode(),16)
cipher = AES.new(key.encode('utf-8'),AES.MODE_CBC,iv)
return base64.b64encode(cipher.encrypt(data))
def decrypt(enc,key,iv=None):
enc = base64.b64decode(enc)
# enc = enc.encode('utf-8')
if iv == None:
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC)
else:
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv)
return unpad(cipher.decrypt(enc),16)
with open("demo_file.png.enc", "r+") as fileIn:
encData = fileIn.read()
key = "1234567887654321"
iv = "53509bee-8207-11".encode('utf-8')
decrypted = decrypt(encData, key, iv)
with open("demo_file.png", "wb") as fileOut:
fileOut.write(decrypted)

Is there a workaround to create a custom download link for safari 14?

The code above creates a temporal element <a> and download a file.
function downloadFile(file) {
var a = document.createElement("a");
a.href = file.FileData;
a.download = file.FileName;
a.click();
}
It works in Safari 13 but it stopped working in Safari 14 (tested in 14.0.1).
It seems the browser try to open a new tab with the file, a pdf in my case, but it gets prevented.
Not allowed to navigate top frame to data URL
'data:application/pdf;base64,JVBERi0xLjU...'
Did you find any workaround for this issue?
SOLUTION
Generating a Bob file instead solved my issue.
public async downloadFile() : Promise<void> {
const aux: string = 'data:' + file.ContentType + ';base64,';
let arraybuffer = this._base64ToArrayBuffer(file.FileData.substr(aux.length));
// create the blob object with content-type "application/XXX"
let blob = new Blob( [arraybuffer], { type: file.ContentType });
let url = URL.createObjectURL(blob);
let a = document.createElement("a");
a.href = url;
a.download = file.FileName;
a.click();
}
}
}
/**
* It transform a string in base64 to an ArrayBuffer.
* #param base64 string
*/
public _base64ToArrayBuffer(base64: string) : ArrayBuffer {
// decode base64 string, remove space for IE compatibility
var binary_string = window.atob(base64.replace(/\s/g, ''));
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}

window.open(url) only open new window with raw text instead of downloading

When I use the following method :
downloadFile(){
const blob = this.b64toBlob(this.formGroup.value.attachment);
const url = window.URL.createObjectURL(blob);
window.open(url);
}
I'm expected it to open a new tab and download the file.
But it's only opening a new tab with raw text content inside.
This is the result I get.
But if I copy/paste the content of the page as an URL, it's working great and my download is starting as expected.
What am I suppose to do to start the download directly.
PS: this is the b64toBlob() method :
b64toBlob(b64Data: string, contentType = '', sliceSize = 512): Blob {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, {type: contentType});
}
Instead of using URL, create a href out of it and you can use the respective click function. Please change the below function of yours something like below and hopefully, you can download the file.
downloadFile(){
var a: any = document.createElement("a");
a.setAttribute('style', 'display:none;');
const blob = this.b64toBlob(this.formGroup.value.attachment);
const url = window.URL.createObjectURL(blob);
a.href = url;
a.click();
}
Sometimes this click won't work properly in IE. In that case you can use msSaveBlob function. The whole function can be changed like,
downloadFile() {
var a: any = document.createElement("a");
a.setAttribute('style', 'display:none;');
document.body.appendChild(a);
const blob = this.b64toBlob(this.formGroup.value.attachment);
const url = window.URL.createObjectURL(blob);
a.href = url;
var isIE = /*#cc_on!#*/false || !!(<any>document).documentMode;
if (isIE) {
var retVal = navigator.msSaveBlob(blob, "test"+ '.txt');
}
else {
a.download = "test" + '.txt';
}
a.click();
}
So after few more hours of research...
I managed to get this to work as expected.
Posting the answer if someone end up with the same issue :
downloadFile() {
const blob = this.b64toBlob(this.formGroup.value.attachment);
blob.text().then(result => { // waiting for blob content to be available...
const a: any = document.createElement('a');
a.download = 'test.pdf'; // or whatever your file name is
a.href = result;
a.click();
});
}
The line a.download = 'test.pdf' seem mandatory on chrome but working fine without it for Firefox.
Hope this will help others.

Extract uploaded file using adm zip

I am trying to get the list of files in a uploaded file using adm-zip. As we cant get the path of the uploaded file , iam trying to convert the zip into a Buffer and pass it to the adm-zip. But zip.getEntries(); its not giving me the list of files.
checkZipFiles(){
var AdmZip = require("adm-zip");
var filesInput = document.querySelector('input[type="file"]').files[0];
var res;
var zip = new AdmZip(filesInput);
console.log(' zip is '+JSON.stringify(zip));
var zipEntries = zip.getEntries();
console.log(' zipEntries is '+zipEntries);
zipEntries.forEach(function(zipEntry) {
console.log(zipEntry.toString()); // outputs zip entries information
if (zipEntry.entryName == "my_file.txt") {
console.log(zipEntry.data.toString('utf8'));
}
});
var reader = new FileReader();
reader.readAsArrayBuffer(filesInput);
var bytes,buf ;
reader.onloadend = function(e){
var readLen=e.target.result.byteLength;
var arrayBuffer = reader.result
bytes = new Uint8Array(arrayBuffer);
console.log('insidebytes is '+bytes);
buf = new Buffer(e.target.result.byteLength);
for (var i = 0; i < buf.length; ++i) {
buf[i] = bytes[i];
}
console.log(e.target.result.byteLength+' length is '+bytes.byteLength);
var myZip = e.target.result;
console.log('bytes is '+bytes);
console.log('buf is '+buf);
zip = new AdmZip(buf);
// console.log(zip+' reader ');
var zipEntries = zip.getEntries();
console.log(' zip is '+zipEntries);
}
}
When i print the buffer it prints something like below , with the file name inside the zip.
121832 length is 121832
}+����Y���L�]��%:����_����ld� ��c{\��h7���L��e33�\"ԅտ׉��v�˕3�-��^�'�Ҁ霗�^�p�q�W��������vްR�����akny���Egr����G�%1/���Wer����\d���A�upR�L����up�jemF���� ��k9y��^Ն;h�1:ca delete.txt
�-�-F1p[
But AdmZip gives below error. Is the issue with the buffer or AdmZip not able to read the buffer?
Uncaught Invalid END header (bad signature) mainHeader.js?4281:35

Using EXIF and BinaryFile get an error

I'm trying to draw photo with correct orientation in canvas after capture photo by using input[type='file'] in mobile web browser for that I'm using:
fileReader.onloadend = function() {
var exif = EXIF.readFromBinaryFile(new BinaryFile(this.result));
switch(exif.Orientation){
case 8:
ctx.rotate(90*Math.PI/180);
break;
case 3:
ctx.rotate(180*Math.PI/180);
break;
case 6:
ctx.rotate(-90*Math.PI/180);
break;
}
};
But I get: TypeError: First argument to DataView constructor must be an ArrayBuffer?
How can I get this array buffer?
I'm using EXIF.js and BinaryFile.js
You need to convert the base64 string to an ArrayBuffer for ExifJs:
function base64ToArrayBuffer (base64) {
base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
var binaryString = atob(base64);
var len = binaryString.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
You don't need BinaryFile:
var exif = EXIF.readFromBinaryFile(base64ToArrayBuffer(this.result));
This assumes you are using FileReader with readAsDataURL to get this.result.
A better approach would be to read the file as an array buffer to begin with and not convert it to base64 and then back again using FileReader.readAsArrayBuffer(). Something along the lines of this (pseudocode):
// `file` = files[0] from input change event
function getFileArrayBuffer(file) {
return new Promise(function (resolve, reject) {
var reader = new FileReader();
reader.onload = function() {
resolve(new Uint8Array(reader.result));
}
reader.readAsArrayBuffer(file);
});
}
#chings228 you must pass base64 data to base64ToArrayBuffer, not blob data.
function base64ToArrayBuffer (base64) {
base64 = base64.replace(/^data\:([^\;]+)\;base64,/gmi, '');
var binaryString = atob(base64);
var len = binaryString.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
var b64 = "data:image/jpeg;base64,"+$parameters.image;
var exif = EXIF.readFromBinaryFile(base64ToArrayBuffer(b64));
alert(exif.Orientation);

Categories

Resources