diff --git a/frontend/src/download.js b/frontend/src/download.js index f5c4d74c..4fb68810 100644 --- a/frontend/src/download.js +++ b/frontend/src/download.js @@ -1,142 +1,186 @@ -const UIWrapper = require('./ui').UIWrapper; +const FileReceiver = require('./fileReceiver'); let download = () => { - let xhr = new XMLHttpRequest(); - xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); - xhr.responseType = 'blob'; - let listelem = setupUI(); - xhr.addEventListener('progress', updateProgress.bind(null, listelem)); + const fileReceiver = new FileReceiver(); - 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 - listelem.emit( - 'name', - xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] - ); - - if (this.status == 200) { - let self = this; - let blob = new Blob([this.response]); - let arrayBuffer; - let fileReader = new FileReader(); - fileReader.onload = function() { - arrayBuffer = this.result; - let 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(key => { - return window.crypto.subtle.decrypt( - { - name: 'AES-CBC', - iv: salt - }, - key, - array - ); - }) - .then(decrypted => { - let dataView = new DataView(decrypted); - let blob = new Blob([dataView]); - let downloadUrl = URL.createObjectURL(blob); - let a = document.createElement('a'); - a.href = downloadUrl; - a.download = xhr - .getResponseHeader('Content-Disposition') - .match(/filename="(.+)"/)[1]; - document.body.appendChild(a); - a.click(); - }) - .catch(err => { - alert( - 'This link is either invalid or has expired, or the uploader has deleted the file.' - ); - 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(); -}; - -window.download = download; - -let setupUI = () => { let li = document.createElement('li'); let name = document.createElement('p'); li.appendChild(name); - let progress = document.createElement('p'); li.appendChild(progress); document.getElementById('downloaded_files').appendChild(li); - return new UIWrapper(li, name, null, progress); -}; - -let ivToStr = iv => { - let hexStr = ''; - for (let i in iv) { - if (iv[i] < 16) { - hexStr += '0' + iv[i].toString(16); - } else { - hexStr += iv[i].toString(16); - } - } - window.hexStr = hexStr; - return hexStr; -}; - -let strToIv = str => { - let iv = new Uint8Array(16); - for (let i = 0; i < str.length; i += 2) { - iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); - } - - return iv; -}; - -let updateProgress = (UIelem, e) => { - if (e.lengthComputable) { - let percentComplete = Math.floor(e.loaded / e.total * 100); - UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); + fileReceiver.on('progress', percentComplete => { + progress.innerText = `Progress: ${percentComplete}%`; if (percentComplete === 100) { let finished = document.createElement('p'); finished.innerText = 'Your download has finished.'; - UIelem.li.appendChild(finished); + li.appendChild(finished); let close = document.createElement('button'); close.innerText = 'Ok'; close.addEventListener('click', () => { - document.getElementById('downloaded_files').removeChild(UIelem.li); + document.getElementById('downloaded_files').removeChild(li); }); - - UIelem.li.appendChild(close); + li.appendChild(close); } - } -}; + }); + + fileReceiver.download().then(([decrypted, fname]) => { + name.innerText = fname; + let dataView = new DataView(decrypted); + let blob = new Blob([dataView]); + let downloadUrl = URL.createObjectURL(blob); + + let a = document.createElement('a'); + a.href = downloadUrl; + a.download = fname; + document.body.appendChild(a); + a.click(); + }) +} +// const FileReceiver = require('./fileReceiver'); + +// let download = () => { +// let xhr = new XMLHttpRequest(); +// xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); +// xhr.responseType = 'blob'; + +// let listelem = setupUI(); +// xhr.addEventListener('progress', updateProgress.bind(null, listelem)); + +// 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 +// listelem.emit( +// 'name', +// xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] +// ); + +// if (this.status == 200) { +// let self = this; +// let blob = new Blob([this.response]); +// let arrayBuffer; +// let fileReader = new FileReader(); +// fileReader.onload = function() { +// arrayBuffer = this.result; +// let 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(key => { +// return window.crypto.subtle.decrypt( +// { +// name: 'AES-CBC', +// iv: salt +// }, +// key, +// array +// ); +// }) +// .then(decrypted => { +// let dataView = new DataView(decrypted); +// let blob = new Blob([dataView]); +// let downloadUrl = URL.createObjectURL(blob); +// let a = document.createElement('a'); +// a.href = downloadUrl; +// a.download = xhr +// .getResponseHeader('Content-Disposition') +// .match(/filename="(.+)"/)[1]; +// document.body.appendChild(a); +// a.click(); +// }) +// .catch(err => { +// alert( +// 'This link is either invalid or has expired, or the uploader has deleted the file.' +// ); +// 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(); +// }; + +window.download = download; + +// let setupUI = () => { +// let li = document.createElement('li'); +// let name = document.createElement('p'); +// li.appendChild(name); + +// let progress = document.createElement('p'); +// li.appendChild(progress); + +// document.getElementById('downloaded_files').appendChild(li); + +// return new UIWrapper(li, name, null, progress); +// }; + +// let ivToStr = iv => { +// let hexStr = ''; +// for (let i in iv) { +// if (iv[i] < 16) { +// hexStr += '0' + iv[i].toString(16); +// } else { +// hexStr += iv[i].toString(16); +// } +// } +// window.hexStr = hexStr; +// return hexStr; +// }; + +// let strToIv = str => { +// let iv = new Uint8Array(16); +// for (let i = 0; i < str.length; i += 2) { +// iv[i / 2] = parseInt(str.charAt(i) + str.charAt(i + 1), 16); +// } + +// return iv; +// }; + +// let updateProgress = (UIelem, e) => { +// if (e.lengthComputable) { +// let percentComplete = Math.floor(e.loaded / e.total * 100); +// UIelem.emit('progress', 'Progress: ' + percentComplete + '%'); + +// if (percentComplete === 100) { +// let finished = document.createElement('p'); +// finished.innerText = 'Your download has finished.'; +// UIelem.li.appendChild(finished); + +// let close = document.createElement('button'); +// close.innerText = 'Ok'; +// close.addEventListener('click', () => { +// document.getElementById('downloaded_files').removeChild(UIelem.li); +// }); + +// UIelem.li.appendChild(close); +// } +// } +// }; diff --git a/frontend/src/fileReceiver.js b/frontend/src/fileReceiver.js new file mode 100644 index 00000000..2a653359 --- /dev/null +++ b/frontend/src/fileReceiver.js @@ -0,0 +1,75 @@ +const EventEmitter = require('events'); +const { strToIv } = require('./utils'); + +class FileReceiver extends EventEmitter { + constructor() { + super(); + this.salt = strToIv(location.pathname.slice(10, -1)); + } + + + download() { + return Promise.all([ + new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + + xhr.onprogress = e => { + if (e.lengthComputable) { + let percentComplete = Math.floor(e.loaded / e.total * 100); + this.emit('progress', percentComplete); + } + }; + + xhr.onload = function(e) { + let blob = new Blob([this.response]); + let fileReader = new FileReader(); + fileReader.onload = function() { + resolve({ + data: this.result, + fname: xhr.getResponseHeader('Content-Disposition').match(/filename="(.+)"/)[1] + }); + } + + fileReader.readAsArrayBuffer(blob); + } + + xhr.open('get', '/assets' + location.pathname.slice(0, -1), true); + xhr.responseType = 'blob'; + xhr.send(); + }), + window.crypto.subtle + .importKey( + 'jwk', + { + kty: 'oct', + k: location.hash.slice(1), + alg: 'A128CBC', + ext: true + }, + { + name: 'AES-CBC' + }, + true, + ['encrypt', 'decrypt'] + ) + ]) + .then(([fdata, key]) => { + let salt = this.salt; + return Promise.all([ + window.crypto.subtle.decrypt( + { + name: 'AES-CBC', + iv: salt + }, + key, + fdata.data + ), + new Promise((resolve, reject) => { + resolve(fdata.fname); + }) + ]); + }) + } +} + +module.exports = FileReceiver; \ No newline at end of file diff --git a/frontend/src/fileSender.js b/frontend/src/fileSender.js index 356c6cf7..412ca2d9 100644 --- a/frontend/src/fileSender.js +++ b/frontend/src/fileSender.js @@ -75,7 +75,6 @@ class FileSender extends EventEmitter { fd.append('data', blob, file.name); let xhr = new XMLHttpRequest(); - xhr.open('post', '/upload/' + fileId, true); xhr.upload.addEventListener('progress', e => { if (e.lengthComputable) { @@ -93,7 +92,8 @@ class FileSender extends EventEmitter { }); } }; - + + xhr.open('post', '/upload/' + fileId, true); xhr.send(fd); }); }); diff --git a/frontend/src/main.js b/frontend/src/main.js index 5dec39be..191c77bb 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -1,3 +1,2 @@ require('./upload'); -require('./download'); -require('./ui'); +require('./download'); \ No newline at end of file diff --git a/frontend/src/ui.js b/frontend/src/ui.js deleted file mode 100644 index 0b0f0fb8..00000000 --- a/frontend/src/ui.js +++ /dev/null @@ -1,26 +0,0 @@ -const EventEmitter = require('events'); - -class UIWrapper extends EventEmitter { - constructor(li, name, link, progress) { - super(); - this.li = li; - this.name = name; - this.link = link; - this.progress = progress; - - this.on('name', filename => { - this.name.innerText = filename; - }); - - this.on('link', link => { - this.link.innerText = link; - this.link.setAttribute('href', link); - }); - - this.on('progress', progress => { - this.progress.innerText = progress; - }); - } -} - -exports.UIWrapper = UIWrapper; diff --git a/frontend/src/upload.js b/frontend/src/upload.js index c2d13eb1..65ed38fc 100644 --- a/frontend/src/upload.js +++ b/frontend/src/upload.js @@ -21,7 +21,7 @@ let onChange = event => { progress.innerText = `Progress: ${percentComplete}%`; }); fileSender.upload().then(info => { - const url = `${window.location.origin}/${info.fileId}/#${info.secretKey}`; + const url = `${window.location.origin}/download/${info.fileId}/#${info.secretKey}`; localStorage.setItem(info.fileId, info.deleteToken); link.innerText = url; link.setAttribute('href', url);