-2

I have this code that receives an encoded base64 file from PHP, and I need to download to the user system with JavaScript, but when the program downloads it, the file has no information, for example a PDF file is blank.

This is my code:

var name = json_obj['data']['nombre'];
var data = atob(json_obj['data']['archivo']);

var file = new Blob([data], {type: 'application/pdf'});

link = URL.createObjectURL(file);

var a = $("<a />");
a.attr("download", name);
a.attr("href", link);
$("body").append(a);
a[0].click();
$("body").remove(a);

PS: PHP its working well, the problem is in the JS

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Francisco
  • 23
  • 3
  • 2
    This looks like some script kiddie nonsense code. Obvious answer is that if it's encoded base64 then it's not the raw pdf format is it? – gview Mar 10 '23 at 20:22
  • 1
    Why was this question closed? It contains an error (the Blob constructor expects utf-8 encoded, not binary, strings) that requires further investigation. The question is clear enough to identify this and respond. – traktor Mar 10 '23 at 22:34
  • Hi Francisco and welcome to Stack Overflow. I don't believe this question has been handled appropriately and am sorry about that. Although nominated for re-opening, it probably isn't needed as there are existing questions covering how to create a blob from a base64 string: [Convert base64 string to ArrayBuffer](https://stackoverflow.com/q/21797299/5217142) and [Creating a BLOB from a Base64 string in JavaScript](https://stackoverflow.com/q/16245767/5217142). You can ignore most of the comments in the accepted answer of the first link as either being incorrect or irrelevant. – traktor Mar 11 '23 at 00:19

1 Answers1

0

The issue lies with how Blob objects serialize strings provided in the constructor's first argument, an array of items to include in the blob. String items are treated as Unicode text and serialized in Blob output using utf8 encoding. For this question in particular it means base64 strings supplied to the constructor are rendered as a string of 8 bit ASCII characters in blob output, whereas the PHP application is looking for decoded binary content, not the base64 characters used to encode it.

Potential solutions to provide a download link derived from a base64 string received from PHP on the server include:

  1. Use a data URL in the download link without creating a Blob.

    Pros: simple, Cons: Historically limits on the size of data URLs have been imposed by various browsers.

  2. Create a blob from the base64 data string and download it using an Object URL;

Another option could be to provide PHP end points on the server to send files in response to fetch requests from the front end. Such would require changes to the application architecture.

The html page below demonstrates two methods of creating a download link from a base64 string. For security reasons (presumably because it downloads files programmatically) it doesn't work as a code snippet.

Generating test data requires opening a local pdf file to use its data, but the file object read is not used in tests themselves. Code syntax has been kept extremely simple but could be modernized if support for obsolete browsers is not required. Use of jQuery was not attempted.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>PDF download</title>
</head>
<body>
<label>
  Open a pdf file for use as test data:
  <input type="file" id="fileBrowse" accept="application/pdf" onchange="testData.call( this,event)">
</label>
<hr>
<blockquote>
  <button type="button" disabled onclick="testDataURL()"> Download pdf as data URL</button>
<p>
  <button type="button" disabled onclick="testObjectURL()"> Download pdf from ObjectURL</button>
</blockquote>

<script>"use strict";
var json_data = null;
function testData(event) {
  var file = this.files[0];
  var fReader = new FileReader();
  fReader.onload = function(){
    var base64 = btoa( this.result);
    json_data = {
      data: { name: file.name, arquivo: base64 }
    };
    document.querySelectorAll("button").forEach( button => button.disabled=false);
  };
  fReader.onerror = function(err){ console.log(err);};
  fReader.readAsBinaryString(file);
}

// check validity of base64 string by using it in a data URL

function testDataURL() {
   var href = "data:application/pdf;base64," + json_data.data.arquivo;
   downloadLink(json_data.data.name, href, false);
}

// check code to download as ObjectUURL

function testObjectURL() {
   var base64 = json_data.data.arquivo;
   var bString = atob(base64); // binary string;
   var length = bString.length;
   var bytes = new Uint8Array(length);
   for( var i = 0; i < length; ++i) {
     bytes[i] = bString.charCodeAt(i);
   }
   var oURL = URL.createObjectURL( new Blob([bytes], {type: "application/pdf"}));
   downloadLink(json_data.data.name, oURL, true)
}

// download link common to both tests

function downloadLink( name, url, revoke) {
  var a = document.createElement('a');
  a.href = url;
  a.download = name;
  document.body.append(a);
  a.click();
  a.remove()
  if(revoke) {
    URL.revokeObjectURL(url);
  }
}
</script></body>
</html>
traktor
  • 17,588
  • 4
  • 32
  • 53
  • Thank you very much @traktor, this solution helps me with my issue, I am new in the development world, your comment put a light where I wasn't watching. Have a great day! – Francisco Mar 13 '23 at 12:29