Michael Tot Korsgaard Michael Tot Korsgaard - 6 months ago 91
AngularJS Question

AngularJS - Download file from base64

I'm trying to encode a file to

base64
and then allow the user to download a decoded version of the same file (just a training excersize for me ;-) )

However when I open the file, the content shows the text "undefined" instead of the content of the file originally encoded.

What am I doing wrong here, and how do I fix it?

HTML

<form ng-submit="upload(file)">
<input type="file" ng-model="file">
<button type="submit" class="button success pull-right"><i class="glyphicon glyphicon-ok"></i> Add</button>
</form>
<ul>
<li ng-repeat="link in downloads track by $index">
<a href="{{ link.link }}" download="{{ link.link }}">{{ link.name }}<a>
</li>
</ul>





Controller

angular.module('myApp')
.controller('testController', function ($scope) {
$scope.upload = function(file){
var contentType = 'text/plain';
var b64Data = encode64(file);

var blob = b64toBlob(b64Data, contentType);
addLink(URL.createObjectURL(blob));
}

$scope.downloads = [];
function addLink(linkAddress){
$scope.downloads.push({
link: linkAddress,
name: 'test ' + ($scope.downloads.length+1)
});
}

function b64toBlob(b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;

var byteCharacters = decode64(b64Data);
var byteArrays = [];

for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);

var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}

var byteArray = new Uint8Array(byteNumbers);

byteArrays.push(byteArray);
}

var blob = new Blob(byteArrays, {type: contentType});
return blob;
}

function encode64(input) {
input = escape(input);
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

do {
chr1 = input.charCodeAt(i++);
chr2 = input.charCodeAt(i++);
chr3 = input.charCodeAt(i++);

enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;

if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}

output = output +
keyStr.charAt(enc1) +
keyStr.charAt(enc2) +
keyStr.charAt(enc3) +
keyStr.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);

return output;
}

function decode64(input) {
var output = "";
var chr1, chr2, chr3 = "";
var enc1, enc2, enc3, enc4 = "";
var i = 0;
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

// remove all characters that are not A-Z, a-z, 0-9, +, /, or =
var base64test = /[^A-Za-z0-9\+\/\=]/g;
if (base64test.exec(input)) {
console.error("There were invalid base64 characters in the input text.\n" +
"Valid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\n" +
"Expect errors in decoding.");
}
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

do {
enc1 = keyStr.indexOf(input.charAt(i++));
enc2 = keyStr.indexOf(input.charAt(i++));
enc3 = keyStr.indexOf(input.charAt(i++));
enc4 = keyStr.indexOf(input.charAt(i++));

chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;

output = output + String.fromCharCode(chr1);

if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}

chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";

} while (i < input.length);

return unescape(output);
}
});

Answer

You have to use a FileReader to actually read the contents of your file. I also created a promise (with AngularJS's $q) that resolves when the FileReader is finished with loading the file contents and encoding them.

Getting the fileInput element per document.getElementById() is not the angular way but you get the idea for your exercise.

I have adapted your code:

function encode64() {
    var fileInput = document.getElementById('fileInput');
    var file = fileInput.files[0];
    var deferred = $q.defer();

    var reader = new FileReader();
    reader.onload = function(e) {
        var input = reader.result;

        var output = "";
        var chr1, chr2, chr3 = "";
        var enc1, enc2, enc3, enc4 = "";
        var i = 0;
        var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

        do {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
                keyStr.charAt(enc1) +
                keyStr.charAt(enc2) +
                keyStr.charAt(enc3) +
                keyStr.charAt(enc4);
            chr1 = chr2 = chr3 = "";
            enc1 = enc2 = enc3 = enc4 = "";
        } while (i < input.length);
        deferred.resolve(output);
    }
    reader.readAsText(file);
    return deferred.promise;
}

$scope.upload = function() {
    var contentType = 'text/plain';
    encode64().then(function(b64Data){
        var blob = b64toBlob(b64Data, contentType);
        addLink(URL.createObjectURL(blob));
    });
}