diff --git a/.jshintignore b/.jshintignore index 51636ed77..93e467aef 100644 --- a/.jshintignore +++ b/.jshintignore @@ -10,3 +10,4 @@ NetFluxWebsocketSrv.js NetFluxWebsocketServer.js WebRTCSrv.js www/common/media-tag.js +www/scratch diff --git a/rpc.js b/rpc.js index 808815d84..5670f1c7a 100644 --- a/rpc.js +++ b/rpc.js @@ -376,6 +376,23 @@ var resetUserPins = function (store, Sessions, publicKey, channelList, cb) { }); }; +var getLimit = function (cb) { + +}; + +var createBlobStaging = function (cb) { + +}; + +var createBlobStore = function (cb) { +}; + +var upload = function (store, Sessions, publicKey, cb) { + +}; + + + /*::const ConfigType = require('./config.example.js');*/ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { // load pin-store... @@ -428,7 +445,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return void respond('INVALID_MESSAGE_OR_PUBLIC_KEY'); } - if (checkSignature(serialized, signature, publicKey) !== true) { return void respond("INVALID_SIGNATURE_OR_PUBLIC_KEY"); } @@ -459,7 +475,8 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return resetUserPins(store, Sessions, safeKey, msg[1], function (e, hash) { return void Respond(e, hash); }); - case 'PIN': + case 'PIN': // TODO don't pin if over the limit + // if over, send error E_OVER_LIMIT return pinChannel(store, Sessions, safeKey, msg[1], function (e, hash) { Respond(e, hash); }); @@ -471,13 +488,17 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) return void getHash(store, Sessions, safeKey, function (e, hash) { Respond(e, hash); }); - case 'GET_TOTAL_SIZE': + case 'GET_TOTAL_SIZE': // TODO cache this, since it will get called quite a bit return getTotalSize(store, ctx.store, Sessions, safeKey, function (e, size) { if (e) { return void Respond(e); } Respond(e, size); }); case 'GET_FILE_SIZE': return void getFileSize(ctx.store, msg[1], Respond); + case 'GET_LIMIT': // TODO implement this and cache it per-user + return void getLimit(function (e, limit) { + Respond('NOT_IMPLEMENTED'); + }); case 'GET_MULTIPLE_FILE_SIZE': return void getMultipleFileSize(ctx.store, msg[1], function (e, dict) { if (e) { return void Respond(e); } diff --git a/www/common/common-hash.js b/www/common/common-hash.js index 620acd319..b282a1b94 100644 --- a/www/common/common-hash.js +++ b/www/common/common-hash.js @@ -266,5 +266,9 @@ Version 2 return hex; }; + var getBlobPath = Hash.getBlobPathFromHex = function (id) { + return '/blob/' + id.slice(0,2) + '/' + id; + }; + return Hash; }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0fb7d7ad8..3462affdb 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -73,6 +73,7 @@ define([ var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; var parseHash = common.parseHash = Hash.parseHash; var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; + common.getBlobPathFromHex = Hash.getBlobPathFromHex; common.getEditHashFromKeys = Hash.getEditHashFromKeys; common.getViewHashFromKeys = Hash.getViewHashFromKeys; diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js new file mode 100644 index 000000000..924cbe334 --- /dev/null +++ b/www/file/file-crypto.js @@ -0,0 +1,182 @@ +define([ + '/bower_components/tweetnacl/nacl-fast.min.js', +], function () { + var Nacl = window.nacl; + var PARANOIA = true; + + var plainChunkLength = 128 * 1024; + var cypherChunkLength = 131088; + + var slice = function (A) { + return Array.prototype.slice.call(A); + }; + + var createNonce = function () { + return new Uint8Array(new Array(24).fill(0)); + }; + + var increment = function (N) { + var l = N.length; + while (l-- > 1) { + if (PARANOIA) { + if (typeof(N[l]) !== 'number') { + throw new Error('E_UNSAFE_TYPE'); + } + if (N[l] > 255) { + throw new Error('E_OUT_OF_BOUNDS'); + } + } + /* jshint probably suspects this is unsafe because we lack types + but as long as this is only used on nonces, it should be safe */ + if (N[l] !== 255) { return void N[l]++; } // jshint ignore:line + N[l] = 0; + + // you don't need to worry about this running out. + // you'd need a REAAAALLY big file + if (l === 0) { return true; } + } + }; + + var joinChunks = function (chunks) { + return new Uint8Array(chunks.reduce(function (A, B) { + return slice(A).concat(slice(B)); + }, [])); + }; + + var padChunk = function (A) { + var padding; + if (A.length === plainChunkLength) { return A; } + if (A.length < plainChunkLength) { + padding = new Array(plainChunkLength - A.length).fill(32); + return A.concat(padding); + } + if (A.length > plainChunkLength) { + // how many times larger is it? + var chunks = Math.ceil(A.length / plainChunkLength); + padding = new Array((plainChunkLength * chunks) - A.length).fill(32); + return A.concat(padding); + } + }; + + var decrypt = function (u8, key, cb) { + var nonce = createNonce(); + var i = 0; + + var takeChunk = function () { + var start = i * cypherChunkLength; + var end = start + cypherChunkLength; + i++; + var box = new Uint8Array(u8.subarray(start, end)); + + // decrypt the chunk + var plaintext = Nacl.secretbox.open(box, nonce, key); + // TODO handle nonce-too-large-error + increment(nonce); + return plaintext; + }; + + var buffer = ''; + + var res = { + metadata: undefined, + }; + + // decrypt metadata + var chunk; + for (; !res.metadata && i * cypherChunkLength < u8.length;) { + chunk = takeChunk(); + buffer += Nacl.util.encodeUTF8(chunk); + try { + res.metadata = JSON.parse(buffer); + //console.log(res.metadata); + } catch (e) { + console.log('buffering another chunk for metadata'); + } + } + + if (!res.metadata) { + return void setTimeout(function () { + cb('NO_METADATA'); + }); + } + + var fail = function () { + cb("DECRYPTION_ERROR"); + }; + + var chunks = []; + // decrypt file contents + for (;i * cypherChunkLength < u8.length;) { + chunk = takeChunk(); + if (!chunk) { + return window.setTimeout(fail); + } + chunks.push(chunk); + } + + // send chunks + res.content = joinChunks(chunks); + + cb(void 0, res); + }; + + // metadata + /* { filename: 'raccoon.jpg', type: 'image/jpeg' } */ + + + /* TODO + in your callback, return an object which you can iterate... + + + */ + + var encrypt = function (u8, metadata, key, cb) { + var nonce = createNonce(); + + // encode metadata + var metaBuffer = Array.prototype.slice + .call(Nacl.util.decodeUTF8(JSON.stringify(metadata))); + + var plaintext = new Uint8Array(padChunk(metaBuffer)); + + var chunks = []; + var j = 0; + + var start; + var end; + + var part; + var box; + + // prepend some metadata + for (;j * plainChunkLength < plaintext.length; j++) { + start = j * plainChunkLength; + end = start + plainChunkLength; + + part = plaintext.subarray(start, end); + box = Nacl.secretbox(part, nonce, key); + chunks.push(box); + increment(nonce); + } + + // append the encrypted file chunks + var i = 0; + for (;i * plainChunkLength < u8.length; i++) { + start = i * plainChunkLength; + end = start + plainChunkLength; + + part = new Uint8Array(u8.subarray(start, end)); + box = Nacl.secretbox(part, nonce, key); + chunks.push(box); + increment(nonce); + } + + + // TODO do something with the chunks... + }; + + return { + decrypt: decrypt, + encrypt: encrypt, + }; +}); diff --git a/www/file/inner.html b/www/file/inner.html index 7f315cef2..09f627842 100644 --- a/www/file/inner.html +++ b/www/file/inner.html @@ -14,14 +14,44 @@ padding: 0px; display: inline-block; } - media-tag * { - max-width: 100%; + #file { + display: block; + height: 300px; + width: 300px; + border: 2px solid black; + margin: 50px; } + + .inputfile { + width: 0.1px; + height: 0.1px; + opacity: 0; + overflow: hidden; + position: absolute; + z-index: -1; + } + .inputfile + label { + border: 2px solid black; + display: block; + height: 500px; + width: 500px; + background-color: rgba(50, 50, 50, .10); + margin: 50px; + } + + .inputfile:focus + label, + .inputfile + label:hover { + background-color: rgba(50, 50, 50, 0.30); + } +
-