From 6d30d7f89fda0ceb88bf153c917d91e0a2cfa592 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 May 2017 18:15:33 +0200 Subject: [PATCH 01/15] strip junk bytes from base64 more safely --- www/common/common-util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index f5ba9a61c..e62ed7016 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -21,7 +21,7 @@ define([], function () { .replace(/ +$/, "") .split(" "); var byteString = String.fromCharCode.apply(null, hexArray); - return window.btoa(byteString).replace(/\//g, '-').slice(0,-2); + return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, ''); }; Util.base64ToHex = function (b64String) { From 5d37a50a8776231e1d5cdd30c0a8072a4f47fd60 Mon Sep 17 00:00:00 2001 From: ansuz Date: Wed, 10 May 2017 18:57:25 +0200 Subject: [PATCH 02/15] try to estimate upload size --- www/file/file-crypto.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index 5290dfb80..de80fd336 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -7,6 +7,17 @@ define([ var plainChunkLength = 128 * 1024; var cypherChunkLength = 131088; + var computeEncryptedSize = function (bytes, meta) { + var metasize = Nacl.util.decodeUTF8(meta).length + 18; + var chunks = Math.ceil(bytes / plainChunkLength); + console.log({ + metasize: metasize, + chunks: chunks, + bytes: bytes, + }); + return metasize + (chunks * 16) + bytes; + }; + var encodePrefix = function (p) { return [ 65280, // 255 << 8 @@ -159,6 +170,11 @@ define([ var prefixed = new Uint8Array(encodePrefix(box.length) .concat(slice(box))); state++; + + // TODO verify that each box is the expected size + + console.log(part.length, prefixed.length); + return void cb(void 0, prefixed); } @@ -171,6 +187,8 @@ define([ increment(nonce); i++; + console.log(part.length, box.length); + // regular data is done if (i * plainChunkLength >= u8.length) { state = 2; } @@ -184,5 +202,6 @@ define([ decrypt: decrypt, encrypt: encrypt, joinChunks: joinChunks, + computeEncryptedSize: computeEncryptedSize, }; }); From 57ee7de7d40d0f27c1a137167f67f57959baa4f4 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 11 May 2017 16:12:44 +0200 Subject: [PATCH 03/15] Update and return the storage limit --- config.example.js | 6 ++++ rpc.js | 62 ++++++++++++++++++++++++++++++++--- www/common/cryptpad-common.js | 9 ++++- www/common/pinpad.js | 23 +++++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/config.example.js b/config.example.js index 76bba6eae..1be17e510 100644 --- a/config.example.js +++ b/config.example.js @@ -116,6 +116,12 @@ module.exports = { 'contact', ], + /* Domain + * If you want to have enable payments on your CryptPad instance, it has to be able to tell + * our account server what is your domain + */ + // domain: 'https://cryptpad.fr', + /* You have the option of specifying an alternative storage adaptor. These status of these alternatives are specified in their READMEs, diff --git a/rpc.js b/rpc.js index ec6e516d0..dd84b6b29 100644 --- a/rpc.js +++ b/rpc.js @@ -7,10 +7,14 @@ var Nacl = require("tweetnacl"); var Fs = require("fs"); var Path = require("path"); +var Https = require("https"); var RPC = module.exports; var Store = require("./storage/file"); +var config = require('./config'); + +var DEFAULT_LIMIT = 100; var isValidChannel = function (chan) { return /^[a-fA-F0-9]/.test(chan) || @@ -454,8 +458,42 @@ var isPrivilegedUser = function (publicKey, cb) { }); }; -var getLimit = function (cb) { - cb = cb; // TODO +var limits = {}; +var updateLimits = function (publicKey, cb) { + if (typeof cb !== "function") { cb = function () {}; } + var domain = config.domain; + var options = { + host: 'accounts.cryptpad.fr', + path: '/api/getAuthorized?domain=' + encodeURIComponent(domain) + }; + var callback = function (response) { + var str = ''; + + response.on('data', function (chunk) { + str += chunk; + }); + + response.on('end', function () { + try { + var json = JSON.parse(str); + limits = json; + var l; + if (publicKey) { + l = typeof limits[publicKey] === "number" ? limits[publicKey] : DEFAULT_LIMIT; + } + cb(void 0, l); + } catch (e) { + cb(e); + } + }); + }; + Https.get(options, callback).on('error', function (e) { + console.error(e); + cb(e); + }); +}; +var getLimit = function (publicKey, cb) { + return void cb(null, typeof limits[publicKey] === "number" ? limits[publicKey] : DEFAULT_LIMIT); }; var safeMkdir = function (path, cb) { @@ -714,10 +752,16 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) }); 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) { + case 'UPDATE_LIMITS': + return void updateLimits(safeKey, function (e, limit) { + if (e) { return void Respond(e); } + Respond(void 0, limit); + }); + case 'GET_LIMIT': + return void getLimit(safeKey, function (e, limit) { + if (e) { return void Respond(e); } limit = limit; - Respond('NOT_IMPLEMENTED'); + Respond(void 0, limit); }); case 'GET_MULTIPLE_FILE_SIZE': return void getMultipleFileSize(ctx.store, msg[1], function (e, dict) { @@ -775,6 +819,14 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) handleMessage(session.privilege); }; + var updateLimitDaily = function () { + updateLimits(function (e) { + if (e) { console.error('Error updating the storage limits', e); } + }); + }; + updateLimitDaily(); + setInterval(updateLimitDaily, 24*3600*1000); + Store.create({ filePath: pinPath, }, function (s) { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 73b51e8b3..e63399ec1 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -744,8 +744,15 @@ define([ }); }; + common.updatePinLimit = function (cb) { + if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } + rpc.getFileListSize(cb); + }; + common.getPinLimit = function (cb) { - cb(void 0, typeof(AppConfig.pinLimit) === 'number'? AppConfig.pinLimit: 1000); + if (!pinsReady()) { return void cb('[RPC_NOT_READY]'); } + rpc.getFileListSize(cb); + //cb(void 0, typeof(AppConfig.pinLimit) === 'number'? AppConfig.pinLimit: 1000); }; common.isOverPinLimit = function (cb) { diff --git a/www/common/pinpad.js b/www/common/pinpad.js index 3470db34e..efa915ec7 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -121,6 +121,29 @@ define([ }); }; + // Update the limit value for all the users and return the limit for your publicKey + exp.updatePinLimits = function (cb) { + rpc.send('UPDATE_LIMITS', undefined, function (e, response) { + if (e) { return void cb(e); } + if (response && typeof response === "number") { + cb (void 0, response); + } else { + cb('INVALID_RESPONSE'); + } + }); + }; + // Get the storage limit associated with your publicKey + exp.getLimit = function (cb) { + rpc.send('GET_LIMIT', undefined, function (e, response) { + if (e) { return void cb(e); } + if (response && typeof response === "number") { + cb (void 0, response); + } else { + cb('INVALID_RESPONSE'); + } + }); + }; + cb(e, exp); }); }; From e123ad0333787e91d63589553e199e11e9c2a5c3 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 11 May 2017 16:31:14 +0200 Subject: [PATCH 04/15] Use a POST request to get the storage limits --- rpc.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/rpc.js b/rpc.js index dd84b6b29..2e603d7fb 100644 --- a/rpc.js +++ b/rpc.js @@ -461,12 +461,23 @@ var isPrivilegedUser = function (publicKey, cb) { var limits = {}; var updateLimits = function (publicKey, cb) { if (typeof cb !== "function") { cb = function () {}; } - var domain = config.domain; + var body = JSON.stringify({ + domain: config.domain, + subdomain: config.subdomain + }); var options = { host: 'accounts.cryptpad.fr', - path: '/api/getAuthorized?domain=' + encodeURIComponent(domain) + path: '/api/getauthorized', + method: 'POST', + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(body) + } }; - var callback = function (response) { + var req = Https.request(options, function (response) { + if (!('' + req.statusCode).match(/^2\d\d$/)) { + return void cb('SERVER ERROR ' + req.statusCode); + } var str = ''; response.on('data', function (chunk) { @@ -486,11 +497,14 @@ var updateLimits = function (publicKey, cb) { cb(e); } }); - }; - Https.get(options, callback).on('error', function (e) { + }); + + req.on('error', function (e) { console.error(e); cb(e); }); + + req.end(body); }; var getLimit = function (publicKey, cb) { return void cb(null, typeof limits[publicKey] === "number" ? limits[publicKey] : DEFAULT_LIMIT); From d2ba8f1c27d244e0595017aa6cbac506e8ea6400 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 11 May 2017 18:07:29 +0200 Subject: [PATCH 05/15] Use the new format for the storage limits --- rpc.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rpc.js b/rpc.js index 2e603d7fb..d9a619823 100644 --- a/rpc.js +++ b/rpc.js @@ -458,6 +458,8 @@ var isPrivilegedUser = function (publicKey, cb) { }); }; +// The limits object contains storage limits for all the publicKey that have paid +// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit var limits = {}; var updateLimits = function (publicKey, cb) { if (typeof cb !== "function") { cb = function () {}; } @@ -490,7 +492,8 @@ var updateLimits = function (publicKey, cb) { limits = json; var l; if (publicKey) { - l = typeof limits[publicKey] === "number" ? limits[publicKey] : DEFAULT_LIMIT; + var limit = limits[publicKey]; + l = limit && typeof limit.limit === "number" ? limit.limit : DEFAULT_LIMIT; } cb(void 0, l); } catch (e) { @@ -507,7 +510,8 @@ var updateLimits = function (publicKey, cb) { req.end(body); }; var getLimit = function (publicKey, cb) { - return void cb(null, typeof limits[publicKey] === "number" ? limits[publicKey] : DEFAULT_LIMIT); + var limit = limits[publicKey]; + return limit && typeof limit.limit === "number" ? limit.limit : DEFAULT_LIMIT; }; var safeMkdir = function (path, cb) { From a993ab661631f65d0a85ba6a47284e4367e218e5 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 11:25:07 +0200 Subject: [PATCH 06/15] use new encrypted file encoding in media-tag --- www/common/media-tag.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index d0953bcf6..d73cac0e0 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=82)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS");if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"padChunk",value:function(e){var t;if(131072===e.length)return e;if(e.length<131072)return t=new Array(131072-e.length).fill(32),e.concat(t);if(e.length>131072){var n=Math.ceil(e.length/131072);return t=new Array(131072*n-e.length).fill(32),e.concat(t)}}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){for(var r,o=e.Nacl,i=e.createNonce(),u=0,a=function(){var r=131088*u,a=r+131088;u++;var c=new Uint8Array(t.subarray(r,a)),s=o.secretbox.open(c,i,n);return e.increment(i),s},c="",s={metadata:void 0};!s.metadata&&131088*u1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){var r=e.Nacl,o=function(e){throw new Error(e||"DECRYPTION_ERROR")},i=new Uint8Array(new Array(24).fill(0)),u=0,a=t.subarray(0,2),c=e.decodePrefix(a),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+c)),p=r.secretbox.open(l,i,n);e.increment(i);try{f.metadata=JSON.parse(r.util.encodeUTF8(p))}catch(e){return o("E_METADATA_DECRYPTION")}f.metadata||o("NO_METADATA");for(var y,b=function(){var o=u*s+2+c,a=o+s;u++;var f=new Uint8Array(t.subarray(o,a)),l=r.secretbox.open(f,i,n);return e.increment(i),l},h=[];u*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n Date: Fri, 12 May 2017 12:12:51 +0200 Subject: [PATCH 07/15] correctly estimate upload size --- www/file/file-crypto.js | 27 ++++----------------------- www/file/main.js | 7 +++++++ 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/www/file/file-crypto.js b/www/file/file-crypto.js index de80fd336..cde49759c 100644 --- a/www/file/file-crypto.js +++ b/www/file/file-crypto.js @@ -8,14 +8,9 @@ define([ var cypherChunkLength = 131088; var computeEncryptedSize = function (bytes, meta) { - var metasize = Nacl.util.decodeUTF8(meta).length + 18; + var metasize = Nacl.util.decodeUTF8(JSON.stringify(meta)).length; var chunks = Math.ceil(bytes / plainChunkLength); - console.log({ - metasize: metasize, - chunks: chunks, - bytes: bytes, - }); - return metasize + (chunks * 16) + bytes; + return metasize + 18 + (chunks * 16) + bytes; }; var encodePrefix = function (p) { @@ -142,23 +137,15 @@ define([ var i = 0; - /* - 0: metadata - 1: u8 - 2: done - */ - var state = 0; - var next = function (cb) { + if (state === 2) { return void cb(); } + var start; var end; var part; var box; - // DONE - if (state === 2) { return void cb(); } - if (state === 0) { // metadata... part = new Uint8Array(plaintext); box = Nacl.secretbox(part, nonce, key); @@ -171,10 +158,6 @@ define([ .concat(slice(box))); state++; - // TODO verify that each box is the expected size - - console.log(part.length, prefixed.length); - return void cb(void 0, prefixed); } @@ -187,8 +170,6 @@ define([ increment(nonce); i++; - console.log(part.length, box.length); - // regular data is done if (i * plainChunkLength >= u8.length) { state = 2; } diff --git a/www/file/main.js b/www/file/main.js index 32bcaafe6..2f553cf20 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -36,6 +36,7 @@ define([ var key = Nacl.randomBytes(32); var next = FileCrypto.encrypt(u8, metadata, key); + var estimate = FileCrypto.computeEncryptedSize(blob.byteLength, metadata); var chunks = []; var sendChunk = function (box, cb) { @@ -47,15 +48,21 @@ define([ }); }; + var actual = 0; var again = function (err, box) { if (err) { throw new Error(err); } if (box) { + actual += box.length; return void sendChunk(box, function (e) { if (e) { return console.error(e); } next(again); }); } + if (actual !== estimate) { + console.error('Estimated size does not match actual size'); + } + // if not box then done Cryptpad.rpc.send('UPLOAD_COMPLETE', '', function (e, res) { if (e) { return void console.error(e); } From a249435003ac2e1af44beb977c03d17ca8f360ad Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 14:19:36 +0200 Subject: [PATCH 08/15] refactor limit handling a bit --- rpc.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rpc.js b/rpc.js index d9a619823..6e437e385 100644 --- a/rpc.js +++ b/rpc.js @@ -12,7 +12,6 @@ var Https = require("https"); var RPC = module.exports; var Store = require("./storage/file"); -var config = require('./config'); var DEFAULT_LIMIT = 100; @@ -461,8 +460,9 @@ var isPrivilegedUser = function (publicKey, cb) { // The limits object contains storage limits for all the publicKey that have paid // To each key is associated an object containing the 'limit' value and a 'note' explaining that limit var limits = {}; -var updateLimits = function (publicKey, cb) { +var updateLimits = function (config, publicKey, cb) { if (typeof cb !== "function") { cb = function () {}; } + var body = JSON.stringify({ domain: config.domain, subdomain: config.subdomain @@ -503,7 +503,7 @@ var updateLimits = function (publicKey, cb) { }); req.on('error', function (e) { - console.error(e); + if (!config.domain) { return cb(); } cb(e); }); @@ -771,7 +771,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) case 'GET_FILE_SIZE': return void getFileSize(ctx.store, msg[1], Respond); case 'UPDATE_LIMITS': - return void updateLimits(safeKey, function (e, limit) { + return void updateLimits(config, safeKey, function (e, limit) { if (e) { return void Respond(e); } Respond(void 0, limit); }); @@ -838,7 +838,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) }; var updateLimitDaily = function () { - updateLimits(function (e) { + updateLimits(config, undefined, function (e) { if (e) { console.error('Error updating the storage limits', e); } }); }; From 77efc2cee703b51a6f6c14ea8f79d021defb3ebf Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 14:24:52 +0200 Subject: [PATCH 09/15] bump version for upcoming release --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83653bd7b..75a4fbb11 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "1.6.0", + "version": "1.8.0", "dependencies": { "chainpad-server": "^1.0.1", "express": "~4.10.1", From fa4b17e2bc5dea87e074f35583b0fa3d2cd62a7b Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 15:39:55 +0200 Subject: [PATCH 10/15] use callback in 'getLimit' --- rpc.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rpc.js b/rpc.js index 6e437e385..e729e4802 100644 --- a/rpc.js +++ b/rpc.js @@ -509,9 +509,12 @@ var updateLimits = function (config, publicKey, cb) { req.end(body); }; + var getLimit = function (publicKey, cb) { var limit = limits[publicKey]; - return limit && typeof limit.limit === "number" ? limit.limit : DEFAULT_LIMIT; + + cb(void 0, limit && typeof(limit.limit) === "number"? + limit.limit : DEFAULT_LIMIT); }; var safeMkdir = function (path, cb) { @@ -787,7 +790,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function) Respond(void 0, dict); }); - // restricted to privileged users... case 'UPLOAD': if (!privileged) { return deny(); } From 6c68b5e6ca038ba425f71f906341ea5bbc842aac Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 15:42:01 +0200 Subject: [PATCH 11/15] use new media-tag, listen for decryption events --- www/common/media-tag.js | 2 +- www/media/main.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/www/common/media-tag.js b/www/common/media-tag.js index d73cac0e0..0d18138ce 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MediaTag=t():e.MediaTag=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=82)}([function(e,t,n){"use strict";var r={IMAGE:"image",AUDIO:"audio",VIDEO:"video",PDF:"pdf",DASH:"dash",DOWNLOAD:"download",CRYPTO:"crypto",CLEAR_KEY:"clear-key",MEDIA_OBJECT:"media-object"};e.exports=r},function(e,t,n){"use strict";var r={MATCHER:"matcher",RENDERER:"renderer",FILTER:"filter",SANITIZER:"sanitizer"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){var r=e.Nacl,o=function(e){throw new Error(e||"DECRYPTION_ERROR")},i=new Uint8Array(new Array(24).fill(0)),u=0,a=t.subarray(0,2),c=e.decodePrefix(a),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+c)),p=r.secretbox.open(l,i,n);e.increment(i);try{f.metadata=JSON.parse(r.util.encodeUTF8(p))}catch(e){return o("E_METADATA_DECRYPTION")}f.metadata||o("NO_METADATA");for(var y,b=function(){var o=u*s+2+c,a=o+s;u++;var f=new Uint8Array(t.subarray(o,a)),l=r.secretbox.open(f,i,n);return e.increment(i),l},h=[];u*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n=e.STACK_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin stack size exceed");if(e.snapshots[n].length>=e.SNAPSHOT_LIMIT)throw console.error(e.snapshots[n]),new Error("Plugin snapshots size exceed");var r=0;if(e.stacks[n].forEach(function(e){e.type===u.RENDERER&&r++}),r<1&&e.stacks[n].unshift(e.defaultPlugin),r>1)throw new Error("More of one renderer in the stack")}},{key:"return",value:function(t){e.start(t)}},{key:"run",value:function(t){var n=t.getId(),r=e.stacks[n].length,o=e.stacks[n][r-1];if(!o)throw console.log(e.stacks),new Error("Impossible to run a undefined plugin");o.process(t)}}]),e}();f.stacks={},f.STACK_LIMIT=1e3,f.snapshots={},f.SNAPSHOT_LIMIT=1e3,f.defaultPlugin=new s,e.exports=f},function(e,t,n){"use strict";var r={EVERY:"every",ANY:"any",ONCE:"once"};e.exports=r},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n1;){if(c){if("number"!=typeof e[t])throw new Error("E_UNSAFE_TYPE");if(e[t]>255)throw new Error("E_OUT_OF_BOUNDS")}if(255!==e[t])return void e[t]++;if(e[t]=0,0===t)throw new Error("E_NONCE_TOO_LARGE")}}},{key:"encodePrefix",value:function(e){return[65280,255].map(function(t,n){return(e&t)>>8*(1-n)})}},{key:"decodePrefix",value:function(e){return e[0]<<8|e[1]}},{key:"joinChunks",value:function(t){return new Uint8Array(t.reduce(function(t,n){return e.slice(t).concat(e.slice(n))},[]))}},{key:"slice",value:function(e){return Array.prototype.slice.call(e)}},{key:"getRandomKeyStr",value:function(){var t=e.Nacl,n=t.randomBytes(18);return t.util.encodeBase64(n)}},{key:"getKeyFromStr",value:function(t){return e.Nacl.util.decodeBase64(t)}},{key:"encrypt",value:function(t,n){var r=t,o=e.Nacl.randomBytes(24),i=e.Nacl.secretbox(r,o,n);if(i)return new Uint8Array(e.slice(o).concat(e.slice(i)));throw new Error}},{key:"decrypt",value:function(t,n){var r=e.Nacl,o=function(e){throw new Error(e||"DECRYPTION_ERROR")},i=new Uint8Array(new Array(24).fill(0)),u=0,a=t.subarray(0,2),c=e.decodePrefix(a),f={metadata:void 0},l=new Uint8Array(t.subarray(2,2+c)),p=r.secretbox.open(l,i,n);e.increment(i);try{f.metadata=JSON.parse(r.util.encodeUTF8(p))}catch(e){return o("E_METADATA_DECRYPTION")}f.metadata||o("NO_METADATA");for(var y,b=function(){var o=u*s+2+c,a=o+s;u++;var f=new Uint8Array(t.subarray(o,a)),l=r.secretbox.open(f,i,n);return e.increment(i),l},h=[];u*s1?t[0]:window.location.protocol}},{key:"hostname",value:function(e){var t=e.getAttribute("src").split("://");return t.length>1?t[1].split("/")[0]:window.location.hostname}},{key:"source",value:function(e){return e.getAttribute("src")}},{key:"schemes",value:function(e){return/\w+:/.exec(e.getAttribute("src"))}},{key:"parse",value:function(t){return{protocol:e.protocol(t),hostname:e.hostname(t),src:e.source(t),type:e.type(t),extension:e.extension(t),mime:e.mime(t)}}}]),e}();e.exports=i},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=function(){function e(e,t){for(var n=0;n Date: Fri, 12 May 2017 15:43:32 +0200 Subject: [PATCH 12/15] center media tags --- www/media/inner.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/www/media/inner.html b/www/media/inner.html index bc5b96ae0..46e5cea4a 100644 --- a/www/media/inner.html +++ b/www/media/inner.html @@ -16,6 +16,8 @@ } media-tag * { max-width: 100%; + margin: auto; + display: block; } From 42f3a62cac345b4ffb5a6640d03ce35fd767c202 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 16:13:09 +0200 Subject: [PATCH 13/15] handle errors with XHR --- www/common/common-util.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/www/common/common-util.js b/www/common/common-util.js index e62ed7016..bbd648f7c 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -90,11 +90,21 @@ define([], function () { }; Util.fetch = function (src, cb) { + var done = false; + var CB = function (err, res) { + if (done) { return; } + done = true; + cb(err, res); + }; + var xhr = new XMLHttpRequest(); xhr.open("GET", src, true); xhr.responseType = "arraybuffer"; xhr.onload = function () { - return void cb(void 0, new Uint8Array(xhr.response)); + return void CB(void 0, new Uint8Array(xhr.response)); + }; + xhr.onerror = function () { + CB('XHR_ERROR'); }; xhr.send(null); }; From 69e933dd1744ebbf0cb0fd66e5d5aa386c679c57 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 16:17:00 +0200 Subject: [PATCH 14/15] better error handling in file download --- www/file/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/file/main.js b/www/file/main.js index 2f553cf20..9c76e3618 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -175,8 +175,8 @@ define([ if (!uploadMode) { var src = Cryptpad.getBlobPathFromHex(hexFileName); return Cryptpad.fetch(src, function (e, u8) { + if (e) { return void Cryptpad.alert(e); } // now decrypt the u8 - if (e) { return window.alert('error'); } var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = Nacl.util.decodeBase64(cryptKey); From 4c4c21342de6669d8463685658fb94db584efbb9 Mon Sep 17 00:00:00 2001 From: ansuz Date: Fri, 12 May 2017 16:26:51 +0200 Subject: [PATCH 15/15] don't try to decrypt nothing --- www/file/main.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/file/main.js b/www/file/main.js index 9c76e3618..e6ab7990c 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -180,6 +180,10 @@ define([ var cryptKey = secret.keys && secret.keys.fileKeyStr; var key = Nacl.util.decodeBase64(cryptKey); + if (!u8 || !u8.length) { + return void Cryptpad.errorLoadingScreen(e); + } + FileCrypto.decrypt(u8, key, function (e, data) { if (e) { Cryptpad.removeLoadingScreen();