I have a local site which uses Javascript to browse files on my machine. This is not a NodeJS question. I have been reading binary files on my local filesystem and converting them to base64. The problem I'm having is when there are non-printable characters. The output I get from javascript is different to the base64 command line tool in Linux.
An example file, which we can use for this question, was generated with head -c 8 /dev/random > random -- it's just some binary nonsense written to a file. On this example it yielded the following:
$ base64 random
Tg8j3hAv/u4=
If you want to play along at home you can run this to generate the same file:
echo -n 'Tg8j3hAv/u4=' | base64 -d > random
However, when I try and read that file in Javascript and convert it to base64 I get a different result:
Tg8j77+9EC/vv73vv70=
It looks kind of similar, but with some other characters in there.
Here's how I got it:
function readTextFile(file)
{
let fileContents;
var rawFile = new XMLHttpRequest();
rawFile.open("GET", file, false);
rawFile.onreadystatechange = function ()
{
if(rawFile.readyState === 4)
{
if(rawFile.status === 200 || rawFile.status == 0)
{
fileContents = rawFile.responseText;
}
}
}
rawFile.send(null);
return fileContents;
}
var fileContents = readTextFile("file:///Users/henrytk/tmp/stuff/random");
console.log(btoa(unescape(encodeURIComponent(fileContents))));
// I also tried
console.log(Base64.encode(fileContents));
// from http://www.webtoolkit.info/javascript_base64.html#.YVW4WaDTW_w
// but I got the same result
How is this happening? Is it something to do with how I'm reading the file? I want to be able to read that file synchronously in a way which can be run locally - no NodeJS, no fancy third-party libraries, if possible.
I believe this is the problem:
fileContents = rawFile.responseText
This will read your file as a JavaScript string, and not all binary is valid JavaScript character code points.
I will recommend using fetch to get a blob, since that is the method I know best:
async function readTextFileAsBlob(file) {
const response = await fetch( file );
const blob = await response.blob();
return blob;
}
Then, convert the blob to base64 using the browser's FileReader.
(Maybe that matches the Linux tool?)
const blobToBase64DataURL = blob => new Promise(
resolvePromise => {
const reader = new FileReader();
reader.onload = () => resolvePromise( reader.result );
reader.readAsDataURL( blob );
}
);
In your example, you would use these functions like this:
readTextFileAsBlob( "file:///Users/henrytk/tmp/stuff/random" ).then(
async blob => {
const base64URL = await blobToBase64DataURL( blob );
console.log( base64URL );
}
);
This will give you a URL like data://.... You'll need to split off the URL part, but if all goes well, the last bit should be the right base64 data. (Hopefully).