function download() { var xhr = new XMLHttpRequest(); xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); xhr.responseType = 'blob'; var li = document.createElement('li'); var progress = document.createElement('p'); li.appendChild(progress); document.getElementById('downloaded_files').appendChild(li); xhr.addEventListener('progress', returnBindedLI(li, progress)); xhr.onload = function(e) { // maybe send a separate request before this one to get the filename? // maybe render the html itself with the filename, since it's generated server side // after a get request with the unique id var name = document.createElement('p'); name.innerHTML = xhr .getResponseHeader('Content-Disposition') .match(/filename="(.+)"/)[1]; li.insertBefore(name, li.firstChild); if (this.status == 200) { let self = this; var blob = new Blob([this.response]); var arrayBuffer; var fileReader = new FileReader(); fileReader.onload = function() { arrayBuffer = this.result; var array = new Uint8Array(arrayBuffer); salt = strToIv(location.pathname.slice(10, -1)); window.crypto.subtle .importKey( 'jwk', { kty: 'oct', k: location.hash.slice(1), alg: 'A128CBC', ext: true }, { name: 'AES-CBC' }, true, ['encrypt', 'decrypt'] ) .then(function(key) { window.crypto.subtle .decrypt( { name: 'AES-CBC', iv: salt }, key, array ) .then(function(decrypted) { var dataView = new DataView(decrypted); var blob = new Blob([dataView]); var downloadUrl = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = downloadUrl; a.download = xhr .getResponseHeader('Content-Disposition') .match(/filename="(.+)"/)[1]; document.body.appendChild(a); a.click(); }) .catch(function(err) { alert( 'This link is either invalid or has expired, or the uploader has deleted the file.' ); console.error(err); }); }) .catch(function(err) { console.error(err); }); }; fileReader.readAsArrayBuffer(blob); } else { alert( 'This link is either invalid or has expired, or the uploader has deleted the file.' ); } }; xhr.send(); } function ivToStr(iv) { let hexStr = ''; for (var i in iv) { if (iv[i] < 16) { hexStr += '0' + iv[i].toString(16); } else { hexStr += iv[i].toString(16); } } window.hexStr = hexStr; return hexStr; } function strToIv(str) { var iv = new Uint8Array(16); for (var i = 0; i < str.length; i += 2) { iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); } return iv; } function returnBindedLI(li, progress) { return function updateProgress(e) { if (e.lengthComputable) { var percentComplete = Math.floor(e.loaded / e.total * 100); progress.innerHTML = 'Progress: ' + percentComplete + '%'; } if (percentComplete === 100) { var finished = document.createElement('p'); finished.innerHTML = 'Your download has finished.'; li.appendChild(finished); var close = document.createElement('button'); close.innerHTML = 'Ok'; close.addEventListener('click', function() { document.getElementById('downloaded_files').removeChild(li); }); li.appendChild(close); } }; }