diff --git a/app/api.js b/app/api.js index 876756ba..d8cd9b9b 100644 --- a/app/api.js +++ b/app/api.js @@ -100,8 +100,30 @@ function asyncInitWebSocket(server) { }); } +function listenForResponse(ws, canceller) { + return new Promise((resolve, reject) => { + ws.addEventListener('message', function(msg) { + try { + const response = JSON.parse(msg.data); + if (response.error) { + throw new Error(response.error); + } else { + resolve({ + url: response.url, + id: response.id, + ownerToken: response.owner + }); + } + } catch (e) { + canceller.cancelled = true; + canceller.error = e; + reject(e); + } + }); + }); +} + async function upload( - ws, stream, streamInfo, metadata, @@ -110,55 +132,51 @@ async function upload( onprogress, canceller ) { - const metadataHeader = arrayToB64(new Uint8Array(metadata)); - const fileMeta = { - fileMetadata: metadataHeader, - authorization: `send-v1 ${verifierB64}` - }; + const host = window.location.hostname; + const port = window.location.port; + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const error = { cancelled: false }; + const ws = await asyncInitWebSocket(`${protocol}//${host}:${port}/api/ws`); - function listenForResponse() { - return new Promise((resolve, reject) => { - ws.addEventListener('message', function(msg) { - const response = JSON.parse(msg.data); - if (response.error) { - reject(response.error); - } else { - resolve({ - url: response.url, - id: response.id, - ownerToken: response.owner - }); - } - }); - }); - } + try { + const metadataHeader = arrayToB64(new Uint8Array(metadata)); + const fileMeta = { + fileMetadata: metadataHeader, + authorization: `send-v1 ${verifierB64}` + }; - const resPromise = listenForResponse(); - ws.send(JSON.stringify(fileMeta)); + const responsePromise = listenForResponse(ws, error); - const reader = stream.getReader(); - let state = await reader.read(); - let size = 0; - while (!state.done) { - const buf = state.value; - if (canceller.cancelled) { - ws.close(4000, 'upload cancelled'); - throw new Error(0); + ws.send(JSON.stringify(fileMeta)); + + const reader = stream.getReader(); + let state = await reader.read(); + let size = 0; + while (!state.done) { + const buf = state.value; + if (canceller.cancelled) { + throw new Error(0); + } + if (error.cancelled) { + throw new Error(error.error); + } + ws.send(buf); + + onprogress([Math.min(streamInfo.fileSize, size), streamInfo.fileSize]); + size += streamInfo.recordSize; + state = await reader.read(); } - ws.send(buf); - onprogress([Math.min(streamInfo.fileSize, size), streamInfo.fileSize]); - size += streamInfo.recordSize; - state = await reader.read(); + const response = await responsePromise; //promise only fufills if response is good + ws.close(); + return response; + } catch (e) { + ws.close(4000); + throw e; } - - const response = await resPromise; - - ws.close(); - return response; } -export async function uploadWs( +export function uploadWs( encrypted, info, metadata, @@ -166,10 +184,6 @@ export async function uploadWs( keychain, onprogress ) { - const host = window.location.hostname; - const port = window.location.port; - const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; - const ws = await asyncInitWebSocket(`${protocol}//${host}:${port}/api/ws`); const canceller = { cancelled: false }; return { @@ -177,7 +191,6 @@ export async function uploadWs( canceller.cancelled = true; }, result: upload( - ws, encrypted, info, metadata, diff --git a/app/ece.js b/app/ece.js index c962106a..eb86c6c0 100644 --- a/app/ece.js +++ b/app/ece.js @@ -282,11 +282,11 @@ export default class ECE { this.streamInfo = { recordSize: rs, - fileSize: input.size + 16 * Math.floor(input.size / (rs - 17)) + fileSize: 21 + input.size + 16 * Math.floor(input.size / (rs - 17)) }; - input = new BlobSliceStream(input, rs, mode); + const inputStream = new BlobSliceStream(input, rs, mode); const ts = new TransformStream(new ECETransformer(mode, key, rs, salt)); - this.stream = input.pipeThrough(ts); + this.stream = inputStream.pipeThrough(ts); } } diff --git a/app/fileReceiver.js b/app/fileReceiver.js index d35123c4..385b749b 100644 --- a/app/fileReceiver.js +++ b/app/fileReceiver.js @@ -51,25 +51,18 @@ export default class FileReceiver extends Nanobus { this.state = 'ready'; } - async streamToArrayBuffer(stream) { + async streamToArrayBuffer(stream, streamSize) { const reader = stream.getReader(); - const chunks = []; - let length = 0; + const result = new Int8Array(streamSize); + let offset = 0; let state = await reader.read(); while (!state.done) { - chunks.push(state.value); - length += state.value.length; + result.set(state.value, offset); + offset += state.value.length; state = await reader.read(); } - const result = new Int8Array(length); - let offset = 0; - for (let i = 0; i < chunks.length; i++) { - result.set(chunks[i], offset); - offset += chunks[i].length; - } - return result.buffer; } @@ -93,8 +86,10 @@ export default class FileReceiver extends Nanobus { const dec = await this.keychain.decryptStream(ciphertext); const plainstream = dec.stream; - - const plaintext = await this.streamToArrayBuffer(plainstream); + const plaintext = await this.streamToArrayBuffer( + plainstream, + dec.streamInfo.fileSize + ); if (!noSave) { await saveFile({ diff --git a/app/fileSender.js b/app/fileSender.js index 71b416c9..4f0a64a4 100644 --- a/app/fileSender.js +++ b/app/fileSender.js @@ -65,11 +65,11 @@ export default class FileSender extends Nanobus { this.msg = 'encryptingFile'; this.emit('encrypting'); - const enc = await this.keychain.encryptStream(this.file); + const enc = this.keychain.encryptStream(this.file); const metadata = await this.keychain.encryptMetadata(this.file); const authKeyB64 = await this.keychain.authKeyB64(); - this.uploadRequest = await uploadWs( + this.uploadRequest = uploadWs( enc.stream, enc.streamInfo, metadata, diff --git a/app/keychain.js b/app/keychain.js index 89e217e6..75778990 100644 --- a/app/keychain.js +++ b/app/keychain.js @@ -179,12 +179,12 @@ export default class Keychain { return ciphertext; } - async encryptStream(plaintext) { + encryptStream(plaintext) { const enc = new ECE(plaintext, this.rawSecret, 'encrypt'); return enc; } - async decryptStream(encstream) { + decryptStream(encstream) { const dec = new ECE(encstream, this.rawSecret, 'decrypt'); return dec; } diff --git a/package-lock.json b/package-lock.json index 3516b329..6247b8a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4373,16 +4373,6 @@ "integrity": "sha512-KEyUw8AwRET2iFjFsI1EJQrJ/fHeGiJtgpYgEWG3yDv4l/To/m3a2GaYfeGyB3lsWdvbesjF5XCMx+SVBgAAYw==", "requires": { "ws": "5.2.0" - }, - "dependencies": { - "ws": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.0.tgz", - "integrity": "sha512-c18dMeW+PEQdDFzkhDsnBAlS4Z8KGStBQQUcQ5mf7Nf689jyGk0594L+i9RaQuf4gog6SvWLJorz2NfSaqxZ7w==", - "requires": { - "async-limiter": "1.0.0" - } - } } }, "extend": { diff --git a/package.json b/package.json index 66a89781..77e8e22f 100644 --- a/package.json +++ b/package.json @@ -132,8 +132,7 @@ "mozlog": "^2.2.0", "raven": "^2.4.2", "redis": "^2.8.0", - "websocket-stream": "^5.1.2", - "ws": "^5.2.0" + "websocket-stream": "^5.1.2" }, "availableLanguages": [ "en-US", diff --git a/server/routes/ws.js b/server/routes/ws.js index c29757e1..50ef9055 100644 --- a/server/routes/ws.js +++ b/server/routes/ws.js @@ -11,15 +11,13 @@ module.exports = async function(ws, req) { let fileStream; ws.on('close', e => { - if (e !== 1000) { - if (fileStream !== undefined) { - fileStream.destroy(); - } + if (e !== 1000 && fileStream !== undefined) { + fileStream.destroy(); } }); let first = true; - ws.on('message', function(message) { + ws.on('message', async function(message) { try { if (first) { const newId = crypto.randomBytes(5).toString('hex'); @@ -35,6 +33,7 @@ module.exports = async function(ws, req) { error: 400 }) ); + ws.close(); } const meta = { @@ -69,6 +68,7 @@ module.exports = async function(ws, req) { error: 500 }) ); + ws.close(); } }); }; diff --git a/test/frontend/tests/api-tests.js b/test/frontend/tests/api-tests.js index 89b63c1a..1aa5a19f 100644 --- a/test/frontend/tests/api-tests.js +++ b/test/frontend/tests/api-tests.js @@ -13,11 +13,11 @@ describe('API', function() { describe('websocket upload', function() { it('returns file info on success', async function() { const keychain = new Keychain(); - const enc = await keychain.encryptStream(plaintext); + const enc = keychain.encryptStream(plaintext); const meta = await keychain.encryptMetadata(metadata); const verifierB64 = await keychain.authKeyB64(); const p = function() {}; - const up = await api.uploadWs( + const up = api.uploadWs( enc.stream, enc.streamInfo, meta, @@ -34,11 +34,11 @@ describe('API', function() { it('can be cancelled', async function() { const keychain = new Keychain(); - const enc = await keychain.encryptStream(plaintext); + const enc = keychain.encryptStream(plaintext); const meta = await keychain.encryptMetadata(metadata); const verifierB64 = await keychain.authKeyB64(); const p = function() {}; - const up = await api.uploadWs( + const up = api.uploadWs( enc.stream, enc.streamInfo, meta,