uses fetch.body

This commit is contained in:
Emily 2018-06-29 09:36:08 -07:00
parent 5f79a9fb6d
commit 38ef52d3ba
3 changed files with 192 additions and 24 deletions

View file

@ -197,6 +197,69 @@ export function uploadWs(encrypted, info, metadata, verifierB64, onprogress) {
}; };
} }
////////////////////////
async function downloadS(id, keychain, onprogress, signal) {
const auth = await keychain.authHeader();
try {
const response = await fetch(`/api/download/${id}`, {
signal: signal ,
method: 'GET',
headers: {'Authorization': auth}
});
if (response.status !== 200) {
throw new Error(response.status);
}
const authHeader = response.headers.get('WWW-Authenticate');
if (authHeader) {
keychain.nonce = parseNonce(authHeader);
}
const fileSize = response.headers.get('Content-Length');
onprogress([0, fileSize]);
console.log(response.body);
if (response.body) {
return response.body;
}
return response.blob();
} catch (err) {
if (err.name === 'AbortError') {
throw new Error('0');
} else {
throw err;
}
}
}
async function tryDownloadStream(id, keychain, onprogress, signal, tries = 1) {
try {
const result = await downloadS(id, keychain, onprogress, signal);
return result;
} catch (e) {
if (e.message === '401' && --tries > 0) {
return tryDownloadStream(id, keychain, onprogress, signal, tries);
}
throw e;
}
}
export function downloadStream(id, keychain, onprogress) {
const controller = new AbortController();
function cancel() {
controller.abort();
}
return {
cancel,
result: tryDownloadStream(id, keychain, onprogress, controller.signal, 2)
};
}
//////////////////
function download(id, keychain, onprogress, canceller) { function download(id, keychain, onprogress, canceller) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
canceller.oncancel = function() { canceller.oncancel = function() {

View file

@ -1,5 +1,5 @@
require('buffer'); require('buffer');
import { ReadableStream, TransformStream } from 'web-streams-polyfill'; import { TransformStream } from 'web-streams-polyfill';
const NONCE_LENGTH = 12; const NONCE_LENGTH = 12;
const TAG_LENGTH = 16; const TAG_LENGTH = 16;
@ -258,14 +258,67 @@ class BlobSlicer {
} }
} }
class BlobSliceStream extends ReadableStream { class StreamSlicer {
constructor(blob, size, mode) { constructor(rs, mode) {
super(new BlobSlicer(blob, size, mode)); this.mode = mode;
this.rs = rs;
this.chunkSize = mode === MODE_ENCRYPT ? rs - 17 : 21;
this.partialChunk = new Uint8Array(this.chunkSize); //where partial chunks are saved
this.offset = 0;
}
send(buf, controller) {
//console.log("sent a record")
controller.enqueue(buf);
if (this.chunkSize === 21) {
this.chunkSize = this.rs;
this.partialChunk = new Uint8Array(this.chunkSize);
}
}
//reslice input uint8arrays into record sized chunks
transform(chunk, controller) {
//console.log('Received chunk') // with %d bytes.', chunk.byteLength)
let i = 0;
if (this.offset > 0) { //send off the partial chunk
const len = Math.min(chunk.byteLength, (this.chunkSize - this.offset));
this.partialChunk.set((chunk.slice(0, len)), this.offset);
this.offset += len;
i += len;
if (this.offset === this.chunkSize) {
this.send(this.partialChunk, controller);
this.offset = 0;
}
}
while (i < chunk.byteLength) { //send off whole records and stick last bit in partialChunk
if ((chunk.byteLength - i) > this.chunkSize) {
const record = chunk.slice(i, i + this.chunkSize);
i += this.chunkSize;
this.send(record, controller);
} else {
const end = chunk.slice(i, end);
this.partialChunk.set(end);
this.offset = end.length;
i += end.length;
}
}
}
flush(controller) {
if (this.offset > 0) {
console.log("sent a partial record")
controller.enqueue(this.partialChunk.slice(0, this.offset));
}
} }
} }
/* /*
input: a blob containing data to be transformed input: a blob or a readable stream containing data to be transformed
key: Uint8Array containing key of size KEY_LENGTH key: Uint8Array containing key of size KEY_LENGTH
mode: string, either 'encrypt' or 'decrypt' mode: string, either 'encrypt' or 'decrypt'
rs: int containing record size, optional rs: int containing record size, optional
@ -280,11 +333,17 @@ export default class ECE {
salt = generateSalt(KEY_LENGTH); salt = generateSalt(KEY_LENGTH);
} }
this.streamInfo = { let inputStream;
recordSize: rs, if (input instanceof Blob) {
fileSize: 21 + input.size + 16 * Math.floor(input.size / (rs - 17)) this.streamInfo = {
}; recordSize: rs,
const inputStream = new BlobSliceStream(input, rs, mode); fileSize: 21 + input.size + 16 * Math.floor(input.size / (rs - 17))
};
inputStream = new ReadableStream(new BlobSlicer(input, rs, mode));
} else {
const sliceStream = new TransformStream(new StreamSlicer(rs, mode));
inputStream = input.pipeThrough(sliceStream);
}
const ts = new TransformStream(new ECETransformer(mode, key, rs, salt)); const ts = new TransformStream(new ECETransformer(mode, key, rs, salt));
this.stream = inputStream.pipeThrough(ts); this.stream = inputStream.pipeThrough(ts);

View file

@ -1,7 +1,7 @@
import Nanobus from 'nanobus'; import Nanobus from 'nanobus';
import Keychain from './keychain'; import Keychain from './keychain';
import { bytes } from './utils'; import { bytes } from './utils';
import { metadata, downloadFile } from './api'; import { metadata, downloadFile, downloadStream} from './api';
export default class FileReceiver extends Nanobus { export default class FileReceiver extends Nanobus {
constructor(fileInfo) { constructor(fileInfo) {
@ -51,24 +51,64 @@ export default class FileReceiver extends Nanobus {
this.state = 'ready'; this.state = 'ready';
} }
async streamToArrayBuffer(stream, streamSize) { /*
const reader = stream.getReader(); async streamToArrayBuffer(stream, streamSize) {
const result = new Uint8Array(streamSize); try {
let offset = 0; var finish;
const promise = new Promise((resolve) => {
finish = resolve;
});
const result = new Uint8Array(streamSize);
let offset = 0;
let state = await reader.read();
while (!state.done) { const writer = new WritableStream(
result.set(state.value, offset); {
offset += state.value.length; write(chunk) {
state = await reader.read(); result.set(state.value, offset);
offset += state.value.length;
},
close() {
//resolve a promise or something
finish.resolve();
}
}
);
stream.pipeTo(writer);
await promise;
return result.slice(0, offset).buffer;
} catch (e) {
console.log(e)
} }
}
*/
return result.slice(0, offset).buffer; async streamToArrayBuffer(stream, streamSize) {
try {
const result = new Uint8Array(streamSize);
let offset = 0;
console.log("reading...")
const reader = stream.getReader();
let state = await reader.read();
console.log("read done")
while (!state.done) {
result.set(state.value, offset);
offset += state.value.length;
state = await reader.read();
}
return result.slice(0, offset).buffer;
} catch (e) {
console.log(e)
}
} }
async download(noSave = false) { async download(noSave = false) {
this.state = 'downloading'; this.state = 'downloading';
this.downloadRequest = await downloadFile( this.downloadRequest = await downloadStream(
this.fileInfo.id, this.fileInfo.id,
this.keychain, this.keychain,
p => { p => {
@ -78,18 +118,22 @@ export default class FileReceiver extends Nanobus {
); );
try { try {
const ciphertext = await this.downloadRequest.result; const ciphertext = await this.downloadRequest.result;
this.downloadRequest = null; this.downloadRequest = null;
this.msg = 'decryptingFile'; this.msg = 'decryptingFile';
this.state = 'decrypting'; this.state = 'decrypting';
this.emit('decrypting'); this.emit('decrypting');
const dec = await this.keychain.decryptStream(ciphertext); const dec = this.keychain.decryptStream(ciphertext);
const plaintext = await this.streamToArrayBuffer(
let plaintext = await this.streamToArrayBuffer(
dec.stream, dec.stream,
this.fileInfo.size this.fileInfo.size
); );
if (plaintext === undefined) { plaintext = (new Uint8Array(1)).buffer; }
if (!noSave) { if (!noSave) {
await saveFile({ await saveFile({
plaintext, plaintext,
@ -97,8 +141,10 @@ export default class FileReceiver extends Nanobus {
type: this.fileInfo.type type: this.fileInfo.type
}); });
} }
this.msg = 'downloadFinish'; this.msg = 'downloadFinish';
this.state = 'complete'; this.state = 'complete';
} catch (e) { } catch (e) {
this.downloadRequest = null; this.downloadRequest = null;
throw e; throw e;