I need to convert a base64 encode string into an ArrayBuffer.
The base64 strings are user input, they will be copy and pasted from an email, so they're not there when the page is loaded.
I would like to do this in javascript without making an ajax call to the server if possible.
I found those links interesting, but they didt'n help me:
ArrayBuffer to base64 encoded string
this is about the opposite conversion, from ArrayBuffer to base64, not the other way round
http://jsperf.com/json-vs-base64/2
this looks good but i can't figure out how to use the code.
Is there an easy (maybe native) way to do the conversion? thanks
Try this:
function _base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
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;
}
Using TypedArray.from:
Uint8Array.from(atob(base64_string), c => c.charCodeAt(0))
Performance to be compared with the for loop version of Goran.it answer.
For Node.js users:
const myBuffer = Buffer.from(someBase64String, 'base64');
myBuffer will be of type Buffer which is a subclass of Uint8Array. Unfortunately, Uint8Array is NOT an ArrayBuffer as the OP was asking for. But when manipulating an ArrayBuffer I almost always wrap it with Uint8Array or something similar, so it should be close to what's being asked for.
Goran.it's answer does not work because of unicode problem in javascript - https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding.
I ended up using the function given on Daniel Guerrero's blog: http://blog.danguer.com/2011/10/24/base64-binary-decoding-in-javascript/
Function is listed on github link: https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js
Use these lines
var uintArray = Base64Binary.decode(base64_string);
var byteArray = Base64Binary.decodeArrayBuffer(base64_string);
Async solution, it's better when the data is big:
// base64 to buffer
function base64ToBufferAsync(base64) {
var dataUrl = "data:application/octet-binary;base64," + base64;
fetch(dataUrl)
.then(res => res.arrayBuffer())
.then(buffer => {
console.log("base64 to buffer: " + new Uint8Array(buffer));
})
}
// buffer to base64
function bufferToBase64Async( buffer ) {
var blob = new Blob([buffer], {type:'application/octet-binary'});
console.log("buffer to blob:" + blob)
var fileReader = new FileReader();
fileReader.onload = function() {
var dataUrl = fileReader.result;
console.log("blob to dataUrl: " + dataUrl);
var base64 = dataUrl.substr(dataUrl.indexOf(',')+1)
console.log("dataUrl to base64: " + base64);
};
fileReader.readAsDataURL(blob);
}
Javascript is a fine development environment so it seems odd than it doesn't provide a solution to this small problem. The solutions offered elsewhere on this page are potentially slow. Here is my solution. It employs the inbuilt functionality that decodes base64 image and sound data urls.
var req = new XMLHttpRequest;
req.open('GET', "data:application/octet;base64," + base64Data);
req.responseType = 'arraybuffer';
req.onload = function fileLoaded(e)
{
var byteArray = new Uint8Array(e.target.response);
// var shortArray = new Int16Array(e.target.response);
// var unsignedShortArray = new Int16Array(e.target.response);
// etc.
}
req.send();
The send request fails if the base 64 string is badly formed.
The mime type (application/octet) is probably unnecessary.
Tested in chrome. Should work in other browsers.
Pure JS - no string middlestep (no atob)
I write following function which convert base64 in direct way (without conversion to string at the middlestep). IDEA
get 4 base64 characters chunk
find index of each character in base64 alphabet
convert index to 6-bit number (binary string)
join four 6 bit numbers which gives 24-bit numer (stored as binary string)
split 24-bit string to three 8-bit and covert each to number and store them in output array
corner case: if input base64 string ends with one/two = char, remove one/two numbers from output array
Below solution allows to process large input base64 strings. Similar function for convert bytes to base64 without btoa is HERE
function base64ToBytesArr(str) {
const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
let result = [];
for(let i=0; i<str.length/4; i++) {
let chunk = [...str.slice(4*i,4*i+4)]
let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join('');
let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
}
return result;
}
// --------
// TEST
// --------
let test = "Alice's Adventure in Wonderland.";
console.log('test string:', test.length, test);
let b64_btoa = btoa(test);
console.log('encoded string:', b64_btoa);
let decodedBytes = base64ToBytesArr(b64_btoa); // decode base64 to array of bytes
console.log('decoded bytes:', JSON.stringify(decodedBytes));
let decodedTest = decodedBytes.map(b => String.fromCharCode(b) ).join``;
console.log('Uint8Array', JSON.stringify(new Uint8Array(decodedBytes)));
console.log('decoded string:', decodedTest.length, decodedTest);
Caution!
If you want to decode base64 to STRING (not bytes array) and you know that result contains utf8 characters then atob will fail in general e.g. for character 💩 the atob("8J+SqQ==") will give wrong result . In this case you can use above solution and convert result bytes array to string in proper way e.g. :
function base64ToBytesArr(str) {
const abc = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"]; // base64 alphabet
let result = [];
for(let i=0; i<str.length/4; i++) {
let chunk = [...str.slice(4*i,4*i+4)]
let bin = chunk.map(x=> abc.indexOf(x).toString(2).padStart(6,0)).join('');
let bytes = bin.match(/.{1,8}/g).map(x=> +('0b'+x));
result.push(...bytes.slice(0,3 - (str[4*i+2]=="=") - (str[4*i+3]=="=")));
}
return result;
}
// --------
// TEST
// --------
let testB64 = "8J+SqQ=="; // for string: "💩";
console.log('input base64 :', testB64);
let decodedBytes = base64ToBytesArr(testB64); // decode base64 to array of bytes
console.log('decoded bytes :', JSON.stringify(decodedBytes));
let result = new TextDecoder("utf-8").decode(new Uint8Array(decodedBytes));
console.log('properly decoded string :', result);
let result_atob = atob(testB64);
console.log('decoded by atob :', result_atob);
Snippets tested 2022-08-04 on: chrome 103.0.5060.134 (arm64), safari 15.2, firefox 103.0.1 (64 bit), edge 103.0.1264.77 (arm64), and node-js v12.16.1
I would strongly suggest using an npm package implementing correctly the base64 specification.
The best one I know is rfc4648
The problem is that btoa and atob use binary strings instead of Uint8Array and trying to convert to and from it is cumbersome. Also there is a lot of bad packages in npm for that. I lose a lot of time before finding that one.
The creators of that specific package did a simple thing: they took the specification of Base64 (which is here by the way) and implemented it correctly from the beginning to the end. (Including other formats in the specification that are also useful like Base64-url, Base32, etc ...) That doesn't seem a lot but apparently that was too much to ask to the bunch of other libraries.
So yeah, I know I'm doing a bit of proselytism but if you want to avoid losing your time too just use rfc4648.
I used the accepted answer to this question to create base64Url string <-> arrayBuffer conversions in the realm of base64Url data transmitted via ASCII-cookie [atob, btoa are base64[with +/]<->js binary string], so I decided to post the code.
Many of us may want both conversions and client-server communication may use the base64Url version (though a cookie may contain +/ as well as -_ characters if I understand well, only ",;\ characters and some wicked characters from the 128 ASCII are disallowed). But a url cannot contain / character, hence the wider use of b64 url version which of course not what atob-btoa supports...
Seeing other comments, I would like to stress that my use case here is base64Url data transmission via url/cookie and trying to use this crypto data with the js crypto api (2017) hence the need for ArrayBuffer representation and b64u <-> arrBuff conversions... if array buffers represent other than base64 (part of ascii) this conversion wont work since atob, btoa is limited to ascii(128). Check out an appropriate converter like below:
The buff -> b64u version is from a tweet from Mathias Bynens, thanks for that one (too)! He also wrote a base64 encoder/decoder:
https://github.com/mathiasbynens/base64
Coming from java, it may help when trying to understand the code that java byte[] is practically js Int8Array (signed int) but we use here the unsigned version Uint8Array since js conversions work with them. They are both 256bit, so we call it byte[] in js now...
The code is from a module class, that is why static.
//utility
/**
* Array buffer to base64Url string
* - arrBuff->byte[]->biStr->b64->b64u
* #param arrayBuffer
* #returns {string}
* #private
*/
static _arrayBufferToBase64Url(arrayBuffer) {
console.log('base64Url from array buffer:', arrayBuffer);
let base64Url = window.btoa(String.fromCodePoint(...new Uint8Array(arrayBuffer)));
base64Url = base64Url.replaceAll('+', '-');
base64Url = base64Url.replaceAll('/', '_');
console.log('base64Url:', base64Url);
return base64Url;
}
/**
* Base64Url string to array buffer
* - b64u->b64->biStr->byte[]->arrBuff
* #param base64Url
* #returns {ArrayBufferLike}
* #private
*/
static _base64UrlToArrayBuffer(base64Url) {
console.log('array buffer from base64Url:', base64Url);
let base64 = base64Url.replaceAll('-', '+');
base64 = base64.replaceAll('_', '/');
const binaryString = window.atob(base64);
const length = binaryString.length;
const bytes = new Uint8Array(length);
for (let i = 0; i < length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
console.log('array buffer:', bytes.buffer);
return bytes.buffer;
}
made a ArrayBuffer from a base64:
function base64ToArrayBuffer(base64) {
var binary_string = window.atob(base64);
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;
}
I was trying to use above code and It's working fine.
The result of atob is a string that is separated with some comma
,
A simpler way is to convert this string to a json array string and after that parse it to a byteArray
below code can simply be used to convert base64 to an array of number
let byteArray = JSON.parse('['+atob(base64)+']');
let buffer = new Uint8Array(byteArray);
Solution without atob
I've seen many people complaining about using atob and btoa in the replies. There are some issues to take into account when using them.
There's a solution without using them in the MDN page about Base64. Below you can find the code to convert a base64 string into a Uint8Array copied from the docs.
Note that the function below returns a Uint8Array. To get the ArrayBuffer version you just need to do uintArray.buffer.
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
? nChr - 71
: nChr > 47 && nChr < 58
? nChr + 4
: nChr === 43
? 62
: nChr === 47
? 63
: 0;
}
function base64DecToArr(sBase64, nBlocksSize) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
const nOutLen = nBlocksSize
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
: (nInLen * 3 + 1) >> 2;
const taBytes = new Uint8Array(nOutLen);
let nMod3;
let nMod4;
let nUint24 = 0;
let nOutIdx = 0;
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
if (nMod4 === 3 || nInLen - nInIdx === 1) {
nMod3 = 0;
while (nMod3 < 3 && nOutIdx < nOutLen) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
nMod3++;
nOutIdx++;
}
nUint24 = 0;
}
}
return taBytes;
}
If you're interested in the reverse operation, ArrayBuffer to base64, you can find how to do it in the same link.
so due to some security reason i want to add some extra text to .ts file in the begining of so when parsing it causes buffering issues
to fix this i decided to removed that 'extra' text i added before processing the segment issue is i dont know how to manipulate arraybuffer so i can remove that text since i am not that knowledgable on js
I tried many things including just download hlsjs file directly then edit readystatechange
// >= HEADERS_RECEIVED
if (readyState >= 2) {
....
if (isArrayBuffer)
{
console.log(xhr.response);
var ress = xhr.response;
//console.log(ress.replace('FFmpeg',''));
var enc = new TextDecoder('ASCII');
var seg = enc.decode(ress);
//var binaryArray = new Uint8Array(this.response.slice(0)); // use UInt8Array for binary
//var blob = new Blob([seg], { type: "video/MP2T" });
var enc = new TextEncoder(); // always utf-8
var newww = enc.encode(enc.encode(seg));
var ddd = newww.buffer;
console.debug( newww );
console.debug( newww.buffer);
//dec = dec.replace('ÿØÿà �JFIF','') ;
//xhr.response = Array.from(newww) ;
data = ddd;
len = data.byteLength;
the idea was to convert arraybuffer to string remove that text then convert it back to arraybuffer
I am trying to send recorded audio from the web browser to Oracle Apex but a problem is happening when the audio is quite long. The code works very well when the audio is less than two minutes.
What I know, the data is sent by URL, therefore it is being sent in text format. Oracle has a 32k limit for the string, so if the blob exceeds that limit, it must be sent in an array divided into parts of 30k each. So I am suspecting that the array is not being sent in the correct format, but I don't know how to confirm it.
The code I am using is as follows: (I built a plugin for Apex to send the audio)
Fragment in Javascript that sends the audio:
// builds a js array from long string
clob2Array: function(clob, size, array) {
loopCount = Math.floor(clob.length / size) + 1;
for (var i = 0; i < loopCount; i++) {
array.push(clob.slice(size * i, size * (i + 1)));
}
return array;
},
// converts DataURI to base64 string
dataURI2base64: function(dataURI) {
var base64 = dataURI.substr(dataURI.indexOf(',') + 1);
return base64;
},
blobToDataURL: function(blob, callback) {
var a = new FileReader();
a.onload = function(e) {callback(e.target.result);}
a.readAsDataURL(blob);
},
// save to DB function
save2Db: function(pAjaxIdentifier, pRegionId, pAudio, callback) {
apexAudio.blobToDataURL(pAudio, function(data){
// audio DataURI to base64
var base64 = apexAudio.dataURI2base64(data);
// split base64 clob string to f01 array length 30k
var f01Array = new Array();
f01Array = apexAudio.clob2Array(base64, 30000, f01Array);
// Apex Ajax Call
apex.server.plugin(pAjaxIdentifier, {
f01: f01Array,
}, {
dataType: 'html',
// SUCESS function
success: function() {
// add apex event
$('#' + pRegionId).trigger('apexaudio-saved-db');
// callback
callback();
},
// ERROR function
error: function(xhr, pMessage) {
// add apex event
$('#' + pRegionId).trigger('apexaudio-error-db');
console.log('save2Db: apex.server.plugin ERROR:', pMessage);
// callback
callback();
}
});
});
}
The PL/SQL Code that receives adn transforms the string array into blob
DECLARE
--
l_collection_name VARCHAR2(100);
l_blob BLOB;
l_filename VARCHAR2(100);
l_mime_type VARCHAR2(100);
l_token VARCHAR2(32000);
--
BEGIN
-- get defaults
l_filename := 'audio_' || to_char(SYSDATE, 'YYYYMMDDHH24MISS') || '.webm';
l_mime_type := 'audio/webm';
-- build BLOB from f01 30k Array
dbms_lob.createtemporary(l_blob,
TRUE,
dbms_lob.session);
FOR i IN 1 .. apex_application.g_f01.count LOOP
l_token := wwv_flow.g_f01(i);
IF length(l_token) > 0 THEN
dbms_lob.append(l_blob
,to_blob(utl_encode.base64_decode(utl_raw.cast_to_raw(l_token))));
END IF;
END LOOP;
l_collection_name := 'APEX_AUDIO';
APEX_COLLECTION.CREATE_OR_TRUNCATE_COLLECTION(
p_collection_name => l_collection_name);
-- add collection member (only if BLOB not null)
IF dbms_lob.getlength(l_blob) IS NOT NULL THEN
apex_collection.add_member(p_collection_name => l_collection_name,
p_c001 => l_filename, -- filename
p_c002 => l_mime_type, -- mime_type
p_d001 => SYSDATE, -- date created
p_blob001 => l_blob); -- BLOB audio content
END IF;
END;
I repeat, the code works perfectly if the audio is short, but if it is long, the following error arises:
2020-02-20T20:09:27.169Z SEVERE <P-fvMwI2WpKybDySZRumRQ> java.sql.SQLException: ORA-06550: line 2, column 2:
PLS-00306: number or wrong type arguments when calling 'AJAX'
ORA-06550: line 2, column 2:
PL/SQL: Statement ignored
InternalServerException [statusCode=500, reasons=[]]
at oracle.dbtools.apex.ModApexContext.handleError(ModApexContext.java:288)
at oracle.dbtools.apex.OWA.execute(OWA.java:206)
at oracle.dbtools.apex.ModApex.handleRequest(ModApex.java:310)
at oracle.dbtools.apex.ModApex.doPost(ModApex.java:188)
at oracle.dbtools.apex.ModApex.service(ModApex.java:112)
at oracle.dbtools.http.entrypoint.Dispatcher.dispatch(Dispatcher.java:126)
[...]
Tecnology:
Oracle 12c
Oracle Apex 19.2
Ords 19.4
Tomcat 8
Thus you are sending your request using AJAX with content type "application/x-www-form-urlencoded" Tomcat is limiting the max allowed POST size (defaults to 2MB).
To make it work in APEX you have possibly 2 ways
1) Do some kind of double chunked upload, so first you split the file itself with e.g. file.slice() and then you build the 30k base64 array of each file chunk and upload this, chunk by chunk
2) Use a "multipart/form-data" content type with an form submit, thus here you are not running in Tomcats limitation of 2MB.
I built a file uploader plugin some time ago, just have a look at this function:
https://github.com/Dani3lSun/apex-plugin-dropzone/blob/90a82f4bb83fee9d78458af790560fb6c5b77978/server/js/apexdropzone.js#L378
The uploaded file will then be inserted into apex_application_files automatically, from there you can grab it:
https://github.com/Dani3lSun/apex-plugin-dropzone/blob/90a82f4bb83fee9d78458af790560fb6c5b77978/source/render_region.sql#L332
I would not recommend doing it with ORDS when you can do it inside of your APEX app, thus you have to deal with security, additional authentication etc...
My requirement is to upload large files (upto 50GB) through browser. So I used JQuery file upload plugin to upload those large files in smaller chunks.
Now I have to calculate MD5 checksum for each chunk to avoid data corruption issues from browser to server.
Is there any plugin in JQuery to calculate checksum for the blob chunks except Google CryptoJS utility ?
You can try js-md5.
This is a online demo to get file checksum.
You can input arraybuffer like this
var total; // total file size
var batch = 1024 * 1024; // batch size
var start = 0;
var current = md5;
var asyncUpdate = function () {
if (start < total) {
var end = Math.min(start + batch, total);
current = current.update(arrayBuffer.slice(start, end));
start = end;
setTimeout(asyncUpdate);
} else {
var hash = current.hex(); // result
}
}
asyncUpdate();
how do I convert a UTF-8 string to Latin1 encoded string using javascript?
Here is what I am trying to do:
I get a file, split that in chunks by reading as arraybuffer
then, I parse the arraybuffer as string
and passing it to cryptoJS for hash computation using following code:
cryptosha256 = CryptoJS.algo.SHA256.create();
cryptosha256.update(text);
hash = cryptosha256.finalize();
It all works well for a text file. I get problems when using the code for hashing a non-text files (image/.wmv files). I saw in another blog and there the CryptoJS author requires the bytes to be sent using Latin1 format instead of UTF-8 and that's where I am stuck.
Not sure, how can I generate the bytes (or strings) using Latin1 format from arraybuffer in javascript?
$('#btnHash').click(function () {
var fr = new FileReader(),
file = document.getElementById("fileName").files[0];
fr.onload = function (e) {
calcHash(e.target.result, file);
};
fr.readAsArrayBuffer(file);
});
function calcHash(dataArray, file) {
cryptosha256 = CryptoJS.algo.SHA256.create();
text = CryptoJS.enc.Latin1.parse(dataArray);
cryptosha256.update(text);
hash = cryptosha256.finalize();
}
CryptoJS doesn't understand what an ArrayBuffer is and if you use some text encoding like Latin1 or UTF-8, you will inevitably lose some bytes. Not every possible byte value has a valid encoding in one of those text encodings.
You will have to convert the ArrayBuffer to CryptoJS' internal WordArray which holds the bytes as an array of words (32 bit integers). We can view the ArrayBuffer as an array of unsigned 8 bit integers and put them together to build the WordArray (see arrayBufferToWordArray).
The following code shows a full example:
function arrayBufferToWordArray(ab) {
var i8a = new Uint8Array(ab);
var a = [];
for (var i = 0; i < i8a.length; i += 4) {
a.push(i8a[i] << 24 | i8a[i + 1] << 16 | i8a[i + 2] << 8 | i8a[i + 3]);
}
return CryptoJS.lib.WordArray.create(a, i8a.length);
}
function handleFileSelect(evt) {
var files = evt.target.files; // FileList object
// Loop through the FileList and render image files as thumbnails.
for (var i = 0, f; f = files[i]; i++) {
var reader = new FileReader();
// Closure to capture the file information.
reader.onloadend = (function(theFile) {
return function(e) {
var arrayBuffer = e.target.result;
var hash = CryptoJS.SHA256(arrayBufferToWordArray(arrayBuffer));
var elem = document.getElementById("hashValue");
elem.value = hash;
};
})(f);
reader.onerror = function(e) {
console.error(e);
};
// Read in the image file as a data URL.
reader.readAsArrayBuffer(f);
}
}
document.getElementById('upload').addEventListener('change', handleFileSelect, false);
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/sha256.js"></script>
<form method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="upload" id="upload">
<input type="text" name="hashValue" id="hashValue">
</form>
You can extend this code with the techniques in my other answer in order to hash files of arbitrary size without freezing the browser.