Merge branch 'staging' into team

pull/1/head
yflory 5 years ago
commit 15387af020

@ -8,6 +8,7 @@ const Crypto = require('crypto');
const Once = require("./lib/once"); const Once = require("./lib/once");
const Meta = require("./lib/metadata"); const Meta = require("./lib/metadata");
const WriteQueue = require("./lib/write-queue"); const WriteQueue = require("./lib/write-queue");
const BatchRead = require("./lib/batch-read");
let Log; let Log;
const now = function () { return (new Date()).getTime(); }; const now = function () { return (new Date()).getTime(); };
@ -231,7 +232,7 @@ module.exports.create = function (cfg) {
as an added bonus: as an added bonus:
if the channel exists but its index does not then it caches the index if the channel exists but its index does not then it caches the index
*/ */
const indexQueues = {}; const batchIndexReads = BatchRead("HK_GET_INDEX");
const getIndex = (ctx, channelName, cb) => { const getIndex = (ctx, channelName, cb) => {
const chan = ctx.channels[channelName]; const chan = ctx.channels[channelName];
// if there is a channel in memory and it has an index cached, return it // if there is a channel in memory and it has an index cached, return it
@ -242,40 +243,14 @@ module.exports.create = function (cfg) {
}); });
} }
// if a call to computeIndex is already in progress for this channel batchIndexReads(channelName, cb, function (done) {
// then add the callback for the latest invocation to the queue computeIndex(channelName, (err, ret) => {
// and wait for it to complete // this is most likely an unrecoverable filesystem error
if (Array.isArray(indexQueues[channelName])) { if (err) { return void done(err); }
indexQueues[channelName].push(cb); // cache the computed result if possible
return; if (chan) { chan.index = ret; }
} // return
done(void 0, ret);
// otherwise, make a queue for any 'getIndex' calls made before the following 'computeIndex' call completes
var queue = indexQueues[channelName] = (indexQueues[channelName] || [cb]);
computeIndex(channelName, (err, ret) => {
if (!Array.isArray(queue)) {
// something is very wrong if there's no callback array
return void Log.error("E_INDEX_NO_CALLBACK", channelName);
}
// clean up the queue that you're about to handle, but keep a local copy
delete indexQueues[channelName];
// this is most likely an unrecoverable filesystem error
if (err) {
// call back every pending function with the error
return void queue.forEach(function (_cb) {
_cb(err);
});
}
// cache the computed result if possible
if (chan) { chan.index = ret; }
// call back every pending function with the result
queue.forEach(function (_cb) {
_cb(void 0, ret);
}); });
}); });
}; };

@ -0,0 +1,61 @@
/*
## Purpose
To avoid running expensive IO or computation concurrently.
If the result of IO or computation is requested while an identical request
is already in progress, wait until the first one completes and provide its
result to every routine that requested it.
## Usage
Provide:
1. a named key for the computation or resource,
2. a callback to handle the result
3. an implementation which calls back with the result
```
var batch = Batch();
var read = function (path, cb) {
batch(path, cb, function (done) {
console.log("reading %s", path);
fs.readFile(path, 'utf8', done);
});
};
read('./pewpew.txt', function (err, data) {
if (err) { return void console.error(err); }
console.log(data);
});
read('./pewpew.txt', function (err, data) {
if (err) { return void console.error(err); }
console.log(data);
});
```
*/
module.exports = function (/* task */) {
var map = {};
return function (id, cb, impl) {
if (typeof(cb) !== 'function' || typeof(impl) !== 'function') {
throw new Error("expected callback and implementation");
}
if (map[id]) { return void map[id].push(cb); }
map[id] = [cb];
impl(function () {
var args = Array.prototype.slice.call(arguments);
//if (map[id] && map[id].length > 1) { console.log("BATCH-READ DID ITS JOB for [%s][%s]", task, id); }
map[id].forEach(function (h) {
h.apply(null, args);
});
delete map[id];
});
};
};

@ -3,19 +3,9 @@ var WebSocket = require("ws"); // jshint ignore:line
var nThen = require("nthen"); var nThen = require("nthen");
var Util = require("../../www/common/common-util"); var Util = require("../../www/common/common-util");
var Rpc = require("../../www/common/rpc");
var Nacl = require("tweetnacl"); var Nacl = require("tweetnacl");
var makeKeys = function () {
var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength));
return {
secret: Nacl.util.encodeBase64(keys.secretKey),
public: Nacl.util.encodeBase64(keys.publicKey),
};
};
var Client = module.exports; var Client = module.exports;
var createNetwork = Client.createNetwork = function (url, cb) { var createNetwork = Client.createNetwork = function (url, cb) {
@ -24,13 +14,14 @@ var createNetwork = Client.createNetwork = function (url, cb) {
var info = {}; var info = {};
Netflux.connect(url, function (url) { Netflux.connect(url, function (url) {
// this websocket seems to never close properly if the error is
// ECONNREFUSED
info.websocket = new WebSocket(url) info.websocket = new WebSocket(url)
.on('error', function (err) { .on('error', function (err) {
console.log(err); CB(err);
}) })
.on('close', function (err) { .on('close', function (/* err */) {
console.log("close"); delete info.websocket;
console.log(err);
}); });
return info.websocket; return info.websocket;
}).then(function (network) { }).then(function (network) {
@ -77,7 +68,10 @@ Client.create = function (config, cb) {
if (config.network) { return; } if (config.network) { return; }
// connect to the network... // connect to the network...
createNetwork('ws://localhost:3000/cryptpad_websocket', w(function (err, info) { createNetwork('ws://localhost:3000/cryptpad_websocket', w(function (err, info) {
//console.log(_network); if (err) {
w.abort();
return void CB(err);
}
config.network = info.network; config.network = info.network;
config.websocket = info.websocket; config.websocket = info.websocket;
})); }));
@ -97,21 +91,6 @@ Client.create = function (config, cb) {
w.abort(); w.abort();
CB(err); CB(err);
}); });
}).nThen(function (w) {
// connect to the anonRpc
Rpc.createAnonymous(config.network, w(function (err, rpc) {
if (err) {
return void CB('ANON_RPC_CONNECT_ERR');
}
client.anonRpc = rpc;
}));
var keys = makeKeys();
Rpc.create(config.network, keys.secret, keys.public, w(function (err, rpc) {
if (err) {
return void CB('RPC_CONNECT_ERR');
}
client.rpc = rpc;
}));
}).nThen(function () { }).nThen(function () {
CB(void 0, client); CB(void 0, client);
}); });

@ -195,6 +195,10 @@ Meta.createLineHandler = function (ref, errorHandler) {
return function (err, line) { return function (err, line) {
if (err) { if (err) {
// it's not abnormal that metadata exists without a corresponding log
// so ENOENT is fine
if (ref.index === 0 && err.code === 'ENOENT') { return; }
// any other errors are abnormal
return void errorHandler('METADATA_HANDLER_LINE_ERR', { return void errorHandler('METADATA_HANDLER_LINE_ERR', {
error: err, error: err,
index: ref.index, index: ref.index,

20
package-lock.json generated

@ -98,10 +98,24 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
}, },
"chainpad-crypto": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/chainpad-crypto/-/chainpad-crypto-0.2.2.tgz",
"integrity": "sha512-7MJ7qPz/C4sJPsDhPMjdSRmliOCPoRO0XM1vUomcgXA6HINlW+if9AAt/H4q154nYhZ/b57njgC6cWgd/RDidg==",
"requires": {
"tweetnacl": "git://github.com/dchest/tweetnacl-js.git#v0.12.2"
},
"dependencies": {
"tweetnacl": {
"version": "git://github.com/dchest/tweetnacl-js.git#8a21381d696acdc4e99c9f706f1ad23285795f79",
"from": "git://github.com/dchest/tweetnacl-js.git#v0.12.2"
}
}
},
"chainpad-server": { "chainpad-server": {
"version": "3.0.3", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.3.tgz", "resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.5.tgz",
"integrity": "sha512-NRfV7FFBEYy4ZVX7h0P5znu55X8v5K4iGWeMGihkfWZLKu70GmCPUTwpBCP79dUvnCToKEa4/e8aoSPcvZC8pA==", "integrity": "sha512-USKOMSHsNjnme81Qy3nQ+ji9eCkBPokYH4T82LVHAI0aayTSCXcTPUDLVGDBCRqe8NsXU4io1WPXn1KiZwB8fA==",
"requires": { "requires": {
"nthen": "^0.1.8", "nthen": "^0.1.8",
"pull-stream": "^3.6.9", "pull-stream": "^3.6.9",

@ -8,7 +8,8 @@
"url": "git://github.com/xwiki-labs/cryptpad.git" "url": "git://github.com/xwiki-labs/cryptpad.git"
}, },
"dependencies": { "dependencies": {
"chainpad-server": "~3.0.2", "chainpad-crypto": "^0.2.2",
"chainpad-server": "^3.0.5",
"express": "~4.16.0", "express": "~4.16.0",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1", "get-folder-size": "^2.0.1",
@ -39,7 +40,7 @@
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/", "lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"flow": "./node_modules/.bin/flow", "flow": "./node_modules/.bin/flow",
"test": "node scripts/TestSelenium.js", "test": "node scripts/TestSelenium.js",
"test-rpc": "cd scripts && node test-rpc", "test-rpc": "cd scripts/tests && node test-rpc",
"template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../about.html ../contact.html ../what-is-cryptpad.html ../features.html ../../www/login/index.html ../../www/register/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;" "template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../about.html ../contact.html ../what-is-cryptpad.html ../features.html ../../www/login/index.html ../../www/register/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;"
} }
} }

239
rpc.js

@ -19,6 +19,7 @@ const getFolderSize = require("get-folder-size");
const Pins = require("./lib/pins"); const Pins = require("./lib/pins");
const Meta = require("./lib/metadata"); const Meta = require("./lib/metadata");
const WriteQueue = require("./lib/write-queue"); const WriteQueue = require("./lib/write-queue");
const BatchRead = require("./lib/batch-read");
var RPC = module.exports; var RPC = module.exports;
@ -231,6 +232,7 @@ var checkSignature = function (signedMsg, signature, publicKey) {
return Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer); return Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer);
}; };
const batchUserPins = BatchRead("LOAD_USER_PINS");
var loadUserPins = function (Env, publicKey, cb) { var loadUserPins = function (Env, publicKey, cb) {
var session = getSession(Env.Sessions, publicKey); var session = getSession(Env.Sessions, publicKey);
@ -238,21 +240,23 @@ var loadUserPins = function (Env, publicKey, cb) {
return cb(session.channels); return cb(session.channels);
} }
var ref = {}; batchUserPins(publicKey, cb, function (done) {
var lineHandler = Pins.createLineHandler(ref, function (label, data) { var ref = {};
Log.error(label, { var lineHandler = Pins.createLineHandler(ref, function (label, data) {
log: publicKey, Log.error(label, {
data: data, log: publicKey,
data: data,
});
}); });
});
// if channels aren't in memory. load them from disk // if channels aren't in memory. load them from disk
Env.pinStore.getMessages(publicKey, lineHandler, function () { Env.pinStore.getMessages(publicKey, lineHandler, function () {
// no more messages // no more messages
// only put this into the cache if it completes // only put this into the cache if it completes
session.channels = ref.pins; session.channels = ref.pins;
cb(ref.pins); done(ref.pins); // FIXME no error handling?
});
}); });
}; };
@ -268,12 +272,12 @@ var getChannelList = function (Env, publicKey, cb) {
}); });
}; };
var makeFilePath = function (root, id) { var makeFilePath = function (root, id) { // FIXME FILES
if (typeof(id) !== 'string' || id.length <= 2) { return null; } if (typeof(id) !== 'string' || id.length <= 2) { return null; }
return Path.join(root, id.slice(0, 2), id); return Path.join(root, id.slice(0, 2), id);
}; };
var getUploadSize = function (Env, channel, cb) { var getUploadSize = function (Env, channel, cb) { // FIXME FILES
var paths = Env.paths; var paths = Env.paths;
var path = makeFilePath(paths.blob, channel); var path = makeFilePath(paths.blob, channel);
if (!path) { if (!path) {
@ -290,45 +294,49 @@ var getUploadSize = function (Env, channel, cb) {
}); });
}; };
const batchFileSize = BatchRead("GET_FILE_SIZE");
var getFileSize = function (Env, channel, cb) { var getFileSize = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); } if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
batchFileSize(channel, cb, function (done) {
if (channel.length === 32) {
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
return done('GET_CHANNEL_SIZE_UNSUPPORTED');
}
if (channel.length === 32) { return void Env.msgStore.getChannelSize(channel, function (e, size /*:number*/) {
if (typeof(Env.msgStore.getChannelSize) !== 'function') { if (e) {
return cb('GET_CHANNEL_SIZE_UNSUPPORTED'); if (e.code === 'ENOENT') { return void done(void 0, 0); }
return void done(e.code);
}
done(void 0, size);
});
} }
return void Env.msgStore.getChannelSize(channel, function (e, size /*:number*/) { // 'channel' refers to a file, so you need another API
if (e) { getUploadSize(Env, channel, function (e, size) {
if (e.code === 'ENOENT') { return void cb(void 0, 0); } if (typeof(size) === 'undefined') { return void done(e); }
return void cb(e.code); done(void 0, size);
}
cb(void 0, size);
}); });
}
// 'channel' refers to a file, so you need another API
getUploadSize(Env, channel, function (e, size) {
if (typeof(size) === 'undefined') { return void cb(e); }
cb(void 0, size);
}); });
}; };
const batchMetadata = BatchRead("GET_METADATA");
var getMetadata = function (Env, channel, cb) { var getMetadata = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); } if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length !== 32) { return cb("INVALID_CHAN"); } if (channel.length !== 32) { return cb("INVALID_CHAN"); }
var ref = {}; batchMetadata(channel, cb, function (done) {
var lineHandler = Meta.createLineHandler(ref, Log.error); var ref = {};
var lineHandler = Meta.createLineHandler(ref, Log.error);
return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) { return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) {
if (err) { if (err) {
// stream errors? // stream errors?
return void cb(err); return void done(err);
} }
cb(void 0, ref.meta); done(void 0, ref.meta);
});
}); });
}; };
@ -470,19 +478,22 @@ var getDeletedPads = function (Env, channels, cb) {
}); });
}; };
const batchTotalSize = BatchRead("GET_TOTAL_SIZE");
var getTotalSize = function (Env, publicKey, cb) { var getTotalSize = function (Env, publicKey, cb) {
var bytes = 0; batchTotalSize(publicKey, cb, function (done) {
return void getChannelList(Env, publicKey, function (channels) { var bytes = 0;
if (!channels) { return cb('INVALID_PIN_LIST'); } // unexpected return void getChannelList(Env, publicKey, function (channels) {
if (!channels) { return done('INVALID_PIN_LIST'); } // unexpected
var count = channels.length;
if (!count) { cb(void 0, 0); } var count = channels.length;
if (!count) { return void done(void 0, 0); }
channels.forEach(function (channel) {
getFileSize(Env, channel, function (e, size) { channels.forEach(function (channel) { // FIXME this might as well be nThen
count--; getFileSize(Env, channel, function (e, size) {
if (!e) { bytes += size; } count--;
if (count === 0) { return cb(void 0, bytes); } if (!e) { bytes += size; }
if (count === 0) { return done(void 0, bytes); }
});
}); });
}); });
}); });
@ -538,7 +549,7 @@ var applyCustomLimits = function (Env, config) {
// The limits object contains storage limits for all the publicKey that have paid // 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 // To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) { var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) { // FIXME BATCH?
if (config.adminEmail === false) { if (config.adminEmail === false) {
applyCustomLimits(Env, config); applyCustomLimits(Env, config);
@ -831,7 +842,7 @@ var resetUserPins = function (Env, publicKey, channelList, cb) {
}); });
}; };
var makeFileStream = function (root, id, cb) { var makeFileStream = function (root, id, cb) { // FIXME FILES
var stub = id.slice(0, 2); var stub = id.slice(0, 2);
var full = makeFilePath(root, id); var full = makeFilePath(root, id);
if (!full) { if (!full) {
@ -862,7 +873,7 @@ var makeFileStream = function (root, id, cb) {
}); });
}; };
var isFile = function (filePath, cb) { var isFile = function (filePath, cb) { // FIXME FILES
/*:: if (typeof(filePath) !== 'string') { throw new Error('should never happen'); } */ /*:: if (typeof(filePath) !== 'string') { throw new Error('should never happen'); } */
Fs.stat(filePath, function (e, stats) { Fs.stat(filePath, function (e, stats) {
if (e) { if (e) {
@ -892,8 +903,7 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
}); });
}; };
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) { var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) { // FIXME FILES // FIXME METADATA
// FIXME METADATA
var safeKey = escapeKeyCharacters(unsafeKey); var safeKey = escapeKeyCharacters(unsafeKey);
var safeKeyPrefix = safeKey.slice(0,3); var safeKeyPrefix = safeKey.slice(0,3);
var blobPrefix = blobId.slice(0,2); var blobPrefix = blobId.slice(0,2);
@ -1009,7 +1019,7 @@ var removePins = function (Env, safeKey, cb) {
}); });
}; };
var upload = function (Env, publicKey, content, cb) { var upload = function (Env, publicKey, content, cb) { // FIXME FILES
var paths = Env.paths; var paths = Env.paths;
var dec; var dec;
try { dec = Buffer.from(content, 'base64'); } try { dec = Buffer.from(content, 'base64'); }
@ -1045,7 +1055,7 @@ var upload = function (Env, publicKey, content, cb) {
} }
}; };
var upload_cancel = function (Env, publicKey, fileSize, cb) { var upload_cancel = function (Env, publicKey, fileSize, cb) { // FIXME FILES
var paths = Env.paths; var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey); var session = getSession(Env.Sessions, publicKey);
@ -1069,7 +1079,7 @@ var upload_cancel = function (Env, publicKey, fileSize, cb) {
}); });
}; };
var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging var upload_complete = function (Env, publicKey, id, cb) { // FIXME FILES
var paths = Env.paths; var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey); var session = getSession(Env.Sessions, publicKey);
@ -1085,7 +1095,7 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
var oldPath = makeFilePath(paths.staging, publicKey); var oldPath = makeFilePath(paths.staging, publicKey);
if (!oldPath) { if (!oldPath) {
WARN('safeMkdir', "oldPath is null"); // FIXME logging WARN('safeMkdir', "oldPath is null");
return void cb('RENAME_ERR'); return void cb('RENAME_ERR');
} }
@ -1093,13 +1103,13 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
var prefix = id.slice(0, 2); var prefix = id.slice(0, 2);
var newPath = makeFilePath(paths.blob, id); var newPath = makeFilePath(paths.blob, id);
if (typeof(newPath) !== 'string') { if (typeof(newPath) !== 'string') {
WARN('safeMkdir', "newPath is null"); // FIXME logging WARN('safeMkdir', "newPath is null");
return void cb('RENAME_ERR'); return void cb('RENAME_ERR');
} }
Fse.mkdirp(Path.join(paths.blob, prefix), function (e) { Fse.mkdirp(Path.join(paths.blob, prefix), function (e) {
if (e || !newPath) { if (e || !newPath) {
WARN('safeMkdir', e); // FIXME logging WARN('safeMkdir', e);
return void cb('RENAME_ERR'); return void cb('RENAME_ERR');
} }
isFile(newPath, function (e, yes) { isFile(newPath, function (e, yes) {
@ -1122,7 +1132,6 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
return void cb(e || 'PATH_ERR'); return void cb(e || 'PATH_ERR');
} }
// lol wut handle ur errors
Fse.move(oldPath, newPath, function (e) { Fse.move(oldPath, newPath, function (e) {
if (e) { if (e) {
WARN('rename', e); WARN('rename', e);
@ -1135,7 +1144,7 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
tryLocation(handleMove); tryLocation(handleMove);
}; };
/* /* FIXME FILES
var owned_upload_complete = function (Env, safeKey, cb) { var owned_upload_complete = function (Env, safeKey, cb) {
var session = getSession(Env.Sessions, safeKey); var session = getSession(Env.Sessions, safeKey);
@ -1230,7 +1239,7 @@ var owned_upload_complete = function (Env, safeKey, cb) {
}; };
*/ */
var owned_upload_complete = function (Env, safeKey, id, cb) { // FIXME logging var owned_upload_complete = function (Env, safeKey, id, cb) { // FIXME FILES
var session = getSession(Env.Sessions, safeKey); var session = getSession(Env.Sessions, safeKey);
// the file has already been uploaded to the staging area // the file has already been uploaded to the staging area
@ -1341,7 +1350,7 @@ var owned_upload_complete = function (Env, safeKey, id, cb) { // FIXME logging
}); });
}; };
var upload_status = function (Env, publicKey, filesize, cb) { var upload_status = function (Env, publicKey, filesize, cb) { // FIXME FILES
var paths = Env.paths; var paths = Env.paths;
// validate that the provided size is actually a positive number // validate that the provided size is actually a positive number
@ -1387,7 +1396,7 @@ var upload_status = function (Env, publicKey, filesize, cb) {
author of the block, since we assume that the block will have been author of the block, since we assume that the block will have been
encrypted with xsalsa20-poly1305 which is authenticated. encrypted with xsalsa20-poly1305 which is authenticated.
*/ */
var validateLoginBlock = function (Env, publicKey, signature, block, cb) { var validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS
// convert the public key to a Uint8Array and validate it // convert the public key to a Uint8Array and validate it
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); } if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
@ -1428,7 +1437,7 @@ var validateLoginBlock = function (Env, publicKey, signature, block, cb) {
return void cb(null, u8_block); return void cb(null, u8_block);
}; };
var createLoginBlockPath = function (Env, publicKey) { var createLoginBlockPath = function (Env, publicKey) { // FIXME BLOCKS
// prepare publicKey to be used as a file name // prepare publicKey to be used as a file name
var safeKey = escapeKeyCharacters(publicKey); var safeKey = escapeKeyCharacters(publicKey);
@ -1442,7 +1451,7 @@ var createLoginBlockPath = function (Env, publicKey) {
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey); return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
}; };
var writeLoginBlock = function (Env, msg, cb) { var writeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS
//console.log(msg); //console.log(msg);
var publicKey = msg[0]; var publicKey = msg[0];
var signature = msg[1]; var signature = msg[1];
@ -1473,7 +1482,7 @@ var writeLoginBlock = function (Env, msg, cb) {
cb(e); cb(e);
} }
})); }));
}).nThen(function () { // FIXME logging }).nThen(function () {
// actually write the block // actually write the block
// flow is dumb and I need to guard against this which will never happen // flow is dumb and I need to guard against this which will never happen
@ -1497,7 +1506,7 @@ var writeLoginBlock = function (Env, msg, cb) {
information, we can just sign some constant and use that as proof. information, we can just sign some constant and use that as proof.
*/ */
var removeLoginBlock = function (Env, msg, cb) { var removeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS
var publicKey = msg[0]; var publicKey = msg[0];
var signature = msg[1]; var signature = msg[1];
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
@ -1605,53 +1614,60 @@ var writePrivateMessage = function (Env, args, nfwssCtx, cb) {
}); });
}; };
const batchDiskUsage = BatchRead("GET_DISK_USAGE");
var getDiskUsage = function (Env, cb) { var getDiskUsage = function (Env, cb) {
var data = {}; batchDiskUsage('', cb, function (done) {
nThen(function (waitFor) { var data = {};
getFolderSize('./', waitFor(function(err, info) { nThen(function (waitFor) {
data.total = info; getFolderSize('./', waitFor(function(err, info) {
})); data.total = info;
getFolderSize(Env.paths.pin, waitFor(function(err, info) { }));
data.pin = info; getFolderSize(Env.paths.pin, waitFor(function(err, info) {
})); data.pin = info;
getFolderSize(Env.paths.blob, waitFor(function(err, info) { }));
data.blob = info; getFolderSize(Env.paths.blob, waitFor(function(err, info) {
})); data.blob = info;
getFolderSize(Env.paths.staging, waitFor(function(err, info) { }));
data.blobstage = info; getFolderSize(Env.paths.staging, waitFor(function(err, info) {
})); data.blobstage = info;
getFolderSize(Env.paths.block, waitFor(function(err, info) { }));
data.block = info; getFolderSize(Env.paths.block, waitFor(function(err, info) {
})); data.block = info;
getFolderSize(Env.paths.data, waitFor(function(err, info) { }));
data.datastore = info; getFolderSize(Env.paths.data, waitFor(function(err, info) {
})); data.datastore = info;
}).nThen(function () { }));
cb (void 0, data); }).nThen(function () {
done(void 0, data);
});
}); });
}; };
const batchRegisteredUsers = BatchRead("GET_REGISTERED_USERS");
var getRegisteredUsers = function (Env, cb) { var getRegisteredUsers = function (Env, cb) {
var dir = Env.paths.pin; batchRegisteredUsers('', cb, function (done) {
var folders; var dir = Env.paths.pin;
var users = 0; var folders;
nThen(function (waitFor) { var users = 0;
Fs.readdir(dir, waitFor(function (err, list) { nThen(function (waitFor) {
if (err) {
waitFor.abort();
return void cb(err);
}
folders = list;
}));
}).nThen(function (waitFor) {
folders.forEach(function (f) {
var dir = Env.paths.pin + '/' + f;
Fs.readdir(dir, waitFor(function (err, list) { Fs.readdir(dir, waitFor(function (err, list) {
if (err) { return; } if (err) {
users += list.length; waitFor.abort();
return void done(err);
}
folders = list;
})); }));
}).nThen(function (waitFor) {
folders.forEach(function (f) {
var dir = Env.paths.pin + '/' + f;
Fs.readdir(dir, waitFor(function (err, list) {
if (err) { return; }
users += list.length;
}));
});
}).nThen(function () {
done(void 0, users);
}); });
}).nThen(function () {
cb(void 0, users);
}); });
}; };
var getActiveSessions = function (Env, ctx, cb) { var getActiveSessions = function (Env, ctx, cb) {
@ -1821,6 +1837,7 @@ RPC.create = function (
}; };
var handleUnauthenticatedMessage = function (msg, respond, nfwssCtx) { var handleUnauthenticatedMessage = function (msg, respond, nfwssCtx) {
Log.silly('LOG_RPC', msg[0]);
switch (msg[0]) { switch (msg[0]) {
case 'GET_HISTORY_OFFSET': { case 'GET_HISTORY_OFFSET': {
if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') { if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') {
@ -1844,7 +1861,7 @@ RPC.create = function (
respond(e, [null, size, null]); respond(e, [null, size, null]);
}); });
case 'GET_METADATA': case 'GET_METADATA':
return void getMetadata(Env, msg[1], function (e, data) { // FIXME METADATA return void getMetadata(Env, msg[1], function (e, data) {
WARN(e, msg[1]); WARN(e, msg[1]);
respond(e, [null, data, null]); respond(e, [null, data, null]);
}); });

@ -1,43 +0,0 @@
/* globals process */
var Client = require("../lib/client/");
var Mailbox = require("../www/bower_components/chainpad-crypto").Mailbox;
var Nacl = require("tweetnacl");
var makeKeys = function () {
var pair = Nacl.box.keyPair();
return {
curvePrivate: Nacl.util.encodeBase64(pair.secretKey),
curvePublic: Nacl.util.encodeBase64(pair.publicKey),
};
};
Client.create(function (err, client) {
if (err) {
console.error(err);
process.exit(1);
}
var channel = "d34ebe83931382fcad9fe2e2d0e2cb5f"; // channel
var recipient = "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmw="; // curvePublic
var keys = makeKeys();
var cryptor = Mailbox.createEncryptor(keys);
var message = cryptor.encrypt(JSON.stringify({
type: "CHEESE",
author: keys.curvePublic,
content: {
text: "CAMEMBERT",
}
}), recipient);
client.anonRpc.send('WRITE_PRIVATE_MESSAGE', [channel, message], function (err, response) {
if (err) {
return void console.error(err);
}
response = response;
// shutdown doesn't work, so we need to do this instead
client.shutdown();
});
});

@ -0,0 +1 @@
require("./test-rpc");

@ -0,0 +1,295 @@
/* globals process */
var Client = require("../../lib/client/");
var Mailbox = require("../../www/bower_components/chainpad-crypto").Mailbox;
var Nacl = require("tweetnacl");
var nThen = require("nthen");
var Rpc = require("../../www/common/rpc");
var Hash = require("../../www/common/common-hash");
var CpNetflux = require("../../www/bower_components/chainpad-netflux");
var createMailbox = function (config, cb) {
var webchannel;
CpNetflux.start({
network: config.network,
channel: config.channel,
crypto: config.crypto,
owners: [ config.edPublic ],
noChainPad: true,
onConnect: function (wc /*, sendMessage */) {
webchannel = wc;
},
onMessage: function (/* msg, user, vKey, isCp, hash, author */) {
},
onReady: function () {
cb(void 0, webchannel);
},
});
};
process.on('unhandledRejection', function (err) {
console.error(err);
});
var makeCurveKeys = function () {
var pair = Nacl.box.keyPair();
return {
curvePrivate: Nacl.util.encodeBase64(pair.secretKey),
curvePublic: Nacl.util.encodeBase64(pair.publicKey),
};
};
var makeEdKeys = function () {
var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength));
return {
edPrivate: Nacl.util.encodeBase64(keys.secretKey),
edPublic: Nacl.util.encodeBase64(keys.publicKey),
};
};
var EMPTY_ARRAY_HASH = 'slspTLTetp6gCkw88xE5BIAbYBXllWvQGahXCx/h1gQOlE7zze4W0KRlA8puZZol8hz5zt3BPzUqPJgTjBXWrw==';
var createUser = function (config, cb) {
// config should contain keys for a team rpc (ed)
// teamEdKeys
var user;
nThen(function (w) {
Client.create(w(function (err, client) {
if (err) {
w.abort();
return void cb(err);
}
user = client;
}));
}).nThen(function (w) {
// make all the parameters you'll need
var network = user.network = user.config.network;
user.edKeys = makeEdKeys();
user.curveKeys = makeCurveKeys();
user.mailbox = Mailbox.createEncryptor(user.curveKeys);
user.mailboxChannel = Hash.createChannelId();
// create an anon rpc for alice
Rpc.createAnonymous(network, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
return void console.error('ANON_RPC_CONNECT_ERR');
}
user.anonRpc = rpc;
}));
Rpc.create(network, user.edKeys.edPrivate, user.edKeys.edPublic, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
console.error(err);
return console.log('RPC_CONNECT_ERR');
}
user.rpc = rpc;
}));
Rpc.create(network, config.teamEdKeys.edPrivate, config.teamEdKeys.edPublic, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
return console.log('RPC_CONNECT_ERR');
}
user.team_rpc = rpc;
}));
}).nThen(function (w) {
// some basic sanity checks...
user.rpc.send('GET_HASH', user.edKeys.edPublic, w(function (err, hash) {
if (err) {
w.abort();
return void cb(err);
}
if (!hash || hash[0] !== EMPTY_ARRAY_HASH) {
console.error("EXPECTED EMPTY ARRAY HASH");
process.exit(1);
}
}));
}).nThen(function (w) {
// create and subscribe to your mailbox
createMailbox({
network: user.network,
channel: user.mailboxChannel,
crypto: user.mailbox,
edPublic: user.edKeys.edPublic,
}, w(function (err, wc) {
if (err) {
w.abort();
console.error("Mailbox creation error");
process.exit(1);
}
wc.leave();
}));
}).nThen(function (w) {
// confirm that you own your mailbox
user.anonRpc.send("GET_METADATA", user.mailboxChannel, w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
try {
if (data[0].owners[0] !== user.edKeys.edPublic) {
throw new Error("INCORRECT MAILBOX OWNERSHIP METADATA");
}
} catch (err2) {
w.abort();
return void cb(err2);
}
}));
}).nThen(function (w) {
// pin your mailbox
user.rpc.send('PIN', [user.mailboxChannel], w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
try {
if (data[0] === EMPTY_ARRAY_HASH) { throw new Error("PIN_DIDNT_WORK"); }
user.latestPinHash = data[0];
} catch (err2) {
w.abort();
return void cb(err2);
}
}));
}).nThen(function (w) {
user.team_rpc.send('GET_HASH', config.teamEdKeys.edPublic, w(function (err, hash) {
if (err) {
w.abort();
return void cb(err);
}
if (!hash || hash[0] !== EMPTY_ARRAY_HASH) {
console.error("EXPECTED EMPTY ARRAY HASH");
process.exit(1);
}
}));
}).nThen(function () {
// TODO check your quota usage
}).nThen(function (w) {
user.rpc.send('UNPIN', [user.mailboxChannel], w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
try {
if (data[0] !== EMPTY_ARRAY_HASH) { throw new Error("UNPIN_DIDNT_WORK"); }
user.latestPinHash = data[0];
} catch (err2) {
w.abort();
return void cb(err2);
}
}));
}).nThen(function (w) {
// clean up the pin list to avoid lots of accounts on the server
user.rpc.send("REMOVE_PINS", undefined, w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
if (!data || data[0] !== 'OK') {
w.abort();
return void cb("REMOVE_PINS_DIDNT_WORK");
}
}));
}).nThen(function () {
user.cleanup = function (cb) {
// TODO remove your mailbox
cb = cb;
};
cb(void 0, user);
});
};
var alice, bob;
nThen(function (w) {
var sharedConfig = {
teamEdKeys: makeEdKeys(),
};
createUser(sharedConfig, w(function (err, _alice) {
if (err) {
w.abort();
return void console.log(err);
}
alice = _alice;
}));
createUser(sharedConfig, w(function (err, _bob) {
if (err) {
w.abort();
return void console.log(err);
}
bob = _bob;
}));
}).nThen(function (w) {
var message = alice.mailbox.encrypt(JSON.stringify({
type: "CHEESE",
author: alice.curveKeys.curvePublic,
content: {
text: "CAMEMBERT",
}
}), bob.curveKeys.curvePublic);
alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [bob.mailboxChannel, message], w(function (err, response) {
if (err) {
return void console.error(err);
}
// XXX validate that the write was actually successful by checking its size
response = response;
// shutdown doesn't work, so we need to do this instead
}));
}).nThen(function () {
nThen(function () {
}).nThen(function () {
// make a drive
// pin it
}).nThen(function () { // MAILBOXES
// write to your mailbox
// pin your mailbox
}).nThen(function () {
// create an owned pad
// pin the pad
// write to it
}).nThen(function () {
// get pinned usage
// remember the usage
}).nThen(function () {
// upload a file
// remember its size
}).nThen(function () {
// get pinned usage
// check that it is consistent with the size of your uploaded file
}).nThen(function () {
// delete your uploaded file
// unpin your owned file
}).nThen(function () { // EDITABLE METADATA
//
}).nThen(function () {
});
}).nThen(function () {
alice.shutdown();
bob.shutdown();
});

@ -1,11 +1,5 @@
define([ (function (window) {
'/common/common-util.js', var factory = function (Util, Crypto, Nacl) {
'/customize/messages.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Messages, Crypto) {
var Nacl = window.nacl;
var Hash = window.CryptPad_Hash = {}; var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex; var uint8ArrayToHex = Util.uint8ArrayToHex;
@ -510,20 +504,6 @@ Version 1
'/' + curvePublic.replace(/\//g, '-') + '/'; '/' + curvePublic.replace(/\//g, '-') + '/';
}; };
// Create untitled documents when no name is given
var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"};
return new window.Intl.DateTimeFormat(undefined, options).format(new Date());
}
return new Date().toString().split(' ').slice(0,4).join(' ');
};
Hash.getDefaultName = function (parsed) {
var type = parsed.type;
var name = (Messages.type)[type] + ' - ' + getLocaleDate();
return name;
};
Hash.isValidHref = function (href) { Hash.isValidHref = function (href) {
// Non-empty href? // Non-empty href?
if (!href) { return; } if (!href) { return; }
@ -547,4 +527,19 @@ Version 1
}; };
return Hash; return Hash;
}); };
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require("./common-util"), require("chainpad-crypto"), require("tweetnacl"));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Crypto) {
return factory(Util, Crypto, window.nacl);
});
} else {
// unsupported initialization
}
}(typeof(window) !== 'undefined'? window : {}));

@ -124,6 +124,7 @@ define([
f = f || user; f = f || user;
if (f.name) { if (f.name) {
f.displayName = f.name; f.displayName = f.name;
f.edPublic = edPublic;
} }
} }
_owners[ed] = f || { _owners[ed] = f || {

@ -1,5 +1,10 @@
(function (window) { (function (window) {
var Util = {}; var Util = {};
Util.tryParse = function (s) {
try { return JSON.parse(s); } catch (e) { return;}
};
Util.mkAsync = function (f) { Util.mkAsync = function (f) {
return function () { return function () {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);

@ -659,7 +659,6 @@ define([
data.href = parsed.getUrl({present: parsed.present}); data.href = parsed.getUrl({present: parsed.present});
if (typeof (data.title) !== "string") { return cb('Missing title'); } if (typeof (data.title) !== "string") { return cb('Missing title'); }
if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
if (common.initialPath) { if (common.initialPath) {
if (!data.path) { if (!data.path) {

@ -563,7 +563,7 @@ define([
roHref: roHref, roHref: roHref,
atime: now, atime: now,
ctime: now, ctime: now,
title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), title: title || UserObject.getDefaultName(Hash.parsePadUrl(href)),
}; };
}; };
@ -933,6 +933,8 @@ define([
var p = Hash.parsePadUrl(href); var p = Hash.parsePadUrl(href);
var h = p.hashData; var h = p.hashData;
if (title.trim() === "") { title = UserObject.getDefaultName(p); }
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
if (p.type === "debug") { return void cb(); } if (p.type === "debug") { return void cb(); }

@ -189,7 +189,6 @@ proxy.mailboxes = {
box.queue.push(msg); box.queue.push(msg);
} }
}; };
Crypto = Crypto;
if (!Crypto.Mailbox) { if (!Crypto.Mailbox) {
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes."); return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
} }

@ -695,7 +695,7 @@ define([
// Fix creation time // Fix creation time
if (!el.ctime) { el.ctime = el.atime; } if (!el.ctime) { el.ctime = el.atime; }
// Fix title // Fix title
if (!el.title) { el.title = Hash.getDefaultName(parsed); } if (!el.title) { el.title = exp.getDefaultName(parsed); }
// Fix channel // Fix channel
if (!el.channel) { if (!el.channel) {
try { try {

@ -1,144 +1,197 @@
(function () { (function () {
var factory = function (Util, Nacl) { var factory = function (Util, Nacl) {
// we will send messages with a unique id for each RPC
// that id is returned with each response, indicating which call it was in response to
var uid = Util.uid; var uid = Util.uid;
// safely parse json messages, because they might cause parse errors
var tryParse = Util.tryParse;
// we will sign various message with our edPrivate keys
// this handles that in a generic way
var signMsg = function (data, signKey) { var signMsg = function (data, signKey) {
var buffer = Nacl.util.decodeUTF8(JSON.stringify(data)); var buffer = Nacl.util.decodeUTF8(JSON.stringify(data));
return Nacl.util.encodeBase64(Nacl.sign.detached(buffer, signKey)); return Nacl.util.encodeBase64(Nacl.sign.detached(buffer, signKey));
}; };
/* // sendMsg takes a pre-formed message, does a little validation
types of messages: // adds a transaction id to the message and stores its callback
pin -> hash // and finally sends it off to the historyKeeper, which delegates its
unpin -> hash // processing to the RPC submodule
getHash -> hash
getTotalSize -> bytes
getFileSize -> bytes
*/
var sendMsg = function (ctx, data, cb) { var sendMsg = function (ctx, data, cb) {
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
var network = ctx.network; var network = ctx.network;
var hkn = network.historyKeeper; var hkn = network.historyKeeper;
var txid = uid(); if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); }
if (typeof(cb) !== 'function') { var txid = uid();
return console.error('expected callback');
}
var pending = ctx.pending[txid] = function (err, response) { var pending = ctx.pending[txid] = function (err, response) {
cb(err, response); cb(err, response);
}; };
pending.data = data; pending.data = data;
pending.called = 0; pending.called = 0;
return network.sendto(hkn, JSON.stringify([txid, data])); return network.sendto(hkn, JSON.stringify([txid, data]));
}; };
var parse = function (msg) { var matchesAnon = function (ctx, txid) {
try { if (!ctx.anon) { return false; }
return JSON.parse(msg); if (typeof(ctx.anon.pending[txid]) !== 'function') { return false; }
} catch (e) { return true;
return null;
}
}; };
var onMsg = function (ctx, msg) { var handleAnon = function (ctx /* anon_ctx */, txid, body /* parsed messages without txid */) {
var parsed = parse(msg); // if anon is handling it we know there's a pending callback
var pending = ctx.pending[txid];
if (body[0] === 'ERROR') { pending(body[1]); }
else { pending(void 0, body.slice(1)); }
delete ctx.pending[txid];
};
var onMsg = function (ctx /* network context */, msg /* string message */) {
if (typeof(msg) !== 'string') {
console.error("received non-string message [%s]", msg);
}
var parsed = tryParse(msg);
if (!parsed) { if (!parsed) {
return void console.error(new Error('could not parse message: %s', msg)); return void console.error(new Error('could not parse message: %s', msg));
} }
// RPC messages are always arrays. // RPC messages are always arrays.
if (!Array.isArray(parsed)) { return; } if (!Array.isArray(parsed)) { return; }
// ignore FULL_HISTORY messages
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
var txid = parsed[0]; var txid = parsed[0];
// txid must be a string, or this message is not meant for us // txid must be a string, or this message is not meant for us
if (typeof(txid) !== 'string') { return; } if (typeof(txid) !== 'string') { return; }
var cookie = parsed[1];
var pending = ctx.pending[txid];
if (!(parsed && parsed.slice)) { if (matchesAnon(ctx, txid)) {
// RPC responses are arrays. this message isn't meant for us. return void handleAnon(ctx.anon, txid, parsed.slice(1));
return;
} }
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
var response = parsed.slice(2); // iterate over authenticated rpc contexts and check if they are expecting
// a message with this txid
if (typeof(pending) === 'function') { if (ctx.authenticated.some(function (rpc_ctx) {
if (parsed[1] === 'ERROR') { var pending = rpc_ctx.pending[txid];
if (parsed[2] === 'NO_COOKIE') { // not meant for you
return void ctx.send('COOKIE', "", function (e) { if (typeof(pending) !== 'function') { return false; }
if (e) {
console.error(e); // if you're here, the message is for you...
return void pending(e);
} if (parsed[1] !== 'ERROR') {
// if the server sent you a new cookie, replace the old one
// resend the same command again if (/\|/.test(parsed[1]) && rpc_ctx.cookie !== parsed[1]) {
// give up if you've already tried resending rpc_ctx.cookie = parsed[1];
if (ctx.resend(txid)) { delete ctx.pending[txid]; }
});
} }
pending(void 0, parsed.slice(2));
pending(parsed[2]); // if successful, delete the callback...
delete ctx.pending[txid]; delete rpc_ctx.pending[txid];
return; // prevent further iteration
} else { return true;
// update the cookie }
if (/\|/.test(cookie)) {
if (ctx.cookie !== cookie) { // NO_COOKIE errors mean you failed to authenticate.
ctx.cookie = cookie; // request a new cookie and resend the query
if (parsed[2] === 'NO_COOKIE') {
rpc_ctx.send('COOKIE', "", function (e) {
if (e) {
console.error(e);
return void pending(e);
} }
}
// resend the same command again
// give up if you've already tried resending
if (rpc_ctx.resend(txid)) { delete rpc_ctx.pending[txid]; }
});
// prevent further iteration
return true;
} }
pending(void 0, response);
// if successful, delete the callback... // if you're here then your RPC passed authentication but had some other error
delete ctx.pending[txid]; // call back with the error message
pending(parsed[2]);
// and delete the pending callback
delete rpc_ctx.pending[txid];
// prevent further iteration
return true;
})) {
// the message was handled, so stop here
return; return;
} }
// HACK to hide messages from the anon rpc console.error("UNHANDLED RPC MESSAGE", msg);
if (parsed.length !== 4 && parsed[1] !== 'ERROR') {
console.log(parsed);
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
}
}; };
var create = function (network, edPrivateKey, edPublicKey, cb) { var networks = [];
var signKey; var contexts = [];
try { var initNetworkContext = function (network) {
signKey = Nacl.util.decodeBase64(edPrivateKey); var ctx = {
if (signKey.length !== 64) { network: network,
throw new Error('private key did not match expected length of 64'); connected: true,
} anon: undefined,
} catch (err) { authenticated: [],
return void cb(err); };
} networks.push(network);
contexts.push(ctx);
var pubBuffer; // add listeners...
try { network.on('message', function (msg, sender) {
pubBuffer = Nacl.util.decodeBase64(edPublicKey); if (sender !== network.historyKeeper) { return; }
if (pubBuffer.length !== 32) { onMsg(ctx, msg);
return void cb('expected public key to be 32 uint'); });
}
} catch (err) { network.on('disconnect', function () {
return void cb(err); ctx.connected = false;
} if (ctx.anon) { ctx.anon.connected = false; }
ctx.authenticated.forEach(function (ctx) {
ctx.connected = false;
});
});
network.on('reconnect', function () {
if (ctx.anon) { ctx.anon.connected = true; }
ctx.authenticated.forEach(function (ctx) {
ctx.connected = true;
});
});
return ctx;
};
var getNetworkContext = function (network) {
var i;
networks.some(function (current, j) {
if (network !== current) { return false; }
i = j;
return true;
});
if (contexts[i]) { return contexts[i]; }
return initNetworkContext(network);
};
var initAuthenticatedRpc = function (networkContext, keys) {
var ctx = { var ctx = {
network: network, network: networkContext.network,
timeouts: {}, // timeouts publicKey: keys.publicKeyString,
pending: {}, // callbacks timeouts: {},
pending: {},
cookie: null, cookie: null,
connected: true, connected: true,
}; };
var send = ctx.send = function (type, msg, cb) { var send = ctx.send = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected && type !== 'COOKIE') { if (!ctx.connected && type !== 'COOKIE') {
return void setTimeout(function () { return void cb("DISCONNECTED");
cb('DISCONNECTED');
});
} }
// construct a signed message... // construct a signed message...
@ -150,9 +203,9 @@ types of messages:
data.unshift(ctx.cookie); data.unshift(ctx.cookie);
} }
var sig = signMsg(data, signKey); var sig = signMsg(data, keys.signKey);
data.unshift(edPublicKey); data.unshift(keys.publicKeyString);
data.unshift(sig); data.unshift(sig);
// [sig, edPublicKey, cookie, type, msg] // [sig, edPublicKey, cookie, type, msg]
@ -169,25 +222,29 @@ types of messages:
// update the cookie and signature... // update the cookie and signature...
pending.data[2] = ctx.cookie; pending.data[2] = ctx.cookie;
pending.data[0] = signMsg(pending.data.slice(2), signKey); pending.data[0] = signMsg(pending.data.slice(2), keys.signKey);
// store the callback with a new txid
var new_txid = uid();
ctx.pending[new_txid] = pending;
// and delete the old one
delete ctx.pending[txid];
try { try {
return ctx.network.sendto(ctx.network.historyKeeper, return ctx.network.sendto(ctx.network.historyKeeper,
JSON.stringify([txid, pending.data])); JSON.stringify([new_txid, pending.data]));
} catch (e) { } catch (e) {
console.log("failed to resend"); console.log("failed to resend");
console.error(e); console.error(e);
} }
}; };
send.unauthenticated = function (type, msg, cb) { send.unauthenticated = function (type, msg, _cb) {
if (!ctx.connected) { var cb = Util.mkAsync(_cb);
return void setTimeout(function () { if (!ctx.connected) { return void cb('DISCONNECTED'); }
cb('DISCONNECTED');
});
}
// construct an unsigned message // construct an unsigned message
var data = [null, edPublicKey, null, type, msg]; var data = [null, keys.publicKeyString, null, type, msg];
if (ctx.cookie && ctx.cookie.join) { if (ctx.cookie && ctx.cookie.join) {
data[2] = ctx.cookie.join('|'); data[2] = ctx.cookie.join('|');
} else { } else {
@ -197,103 +254,100 @@ types of messages:
return sendMsg(ctx, data, cb); return sendMsg(ctx, data, cb);
}; };
network.on('message', function (msg, sender) { ctx.destroy = function () {
if (sender !== network.historyKeeper) { return; } // clear all pending timeouts
onMsg(ctx, msg); Object.keys(ctx.timeouts).forEach(function (to) {
}); clearTimeout(to);
});
network.on('disconnect', function () { // remove the ctx from the network's stack
ctx.connected = false; var idx = networkContext.authenticated.indexOf(ctx);
}); if (idx === -1) { return; }
networkContext.authenticated.splice(idx, 1);
};
network.on('reconnect', function () { networkContext.authenticated.push(ctx);
send('COOKIE', "", function (e) { return ctx;
if (e) { return void cb(e); } };
ctx.connected = true;
});
});
// network.onHistoryKeeperChange is defined in chainpad-netflux.js var getAuthenticatedContext = function (networkContext, keys) {
// The function we pass will be called when the drive reconnects and if (!networkContext) { throw new Error('expected network context'); }
// chainpad-netflux detects a new history keeper id
if (network.onHistoryKeeperChange) {
network.onHistoryKeeperChange(function () {
send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
ctx.connected = true;
});
});
}
send('COOKIE', "", function (e) { var publicKey = keys.publicKeyString;
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it var i;
cb(void 0, { send: send, }); networkContext.authenticated.some(function (ctx, j) {
if (ctx.publicKey !== publicKey) { return false; }
i = j;
return true;
}); });
};
var onAnonMsg = function (ctx, msg) { if (networkContext.authenticated[i]) { return networkContext.authenticated[i]; }
var parsed = parse(msg);
if (!parsed) { return initAuthenticatedRpc(networkContext, keys);
return void console.error(new Error('could not parse message: %s', msg)); };
}
// RPC messages are always arrays. var create = function (network, edPrivateKey, edPublicKey, _cb) {
if (!Array.isArray(parsed)) { return; } if (typeof(_cb) !== 'function') { throw new Error("expected callback"); }
var txid = parsed[0];
// txid must be a string, or this message is not meant for us var cb = Util.mkAsync(_cb);
if (typeof(txid) !== 'string') { return; }
var pending = ctx.pending[txid]; var signKey;
if (!(parsed && parsed.slice)) { try {
// RPC responses are arrays. this message isn't meant for us. signKey = Nacl.util.decodeBase64(edPrivateKey);
return; if (signKey.length !== 64) {
throw new Error('private key did not match expected length of 64');
}
} catch (err) {
return void cb(err);
} }
if (/FULL_HISTORY/.test(parsed[0])) { return; }
var response = parsed.slice(2); try {
if (Nacl.util.decodeBase64(edPublicKey).length !== 32) {
if (typeof(pending) === 'function') { return void cb('expected public key to be 32 uint');
if (parsed[1] === 'ERROR') {
pending(parsed[2]);
delete ctx.pending[txid];
return;
} }
pending(void 0, response); } catch (err) { return void cb(err); }
// if successful, delete the callback... if (!network) { return void cb('NO_NETWORK'); }
delete ctx.pending[txid];
return; // get or create a context for the provided network
} var net_ctx = getNetworkContext(network);
// HACK: filter out ugly messages we don't care about
if (typeof(msg) !== 'string') { var rpc_ctx = getAuthenticatedContext(net_ctx, {
console.error("received message [%s] for txid[%s] with no callback", msg, txid); publicKeyString: edPublicKey,
} signKey: signKey,
});
rpc_ctx.send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it
cb(void 0, {
send: rpc_ctx.send,
destroy: rpc_ctx.destroy,
});
});
}; };
var createAnonymous = function (network, cb) { var initAnonRpc = function (networkContext) {
var ctx = { var ctx = {
network: network, network: networkContext.network,
timeouts: {}, // timeouts timeouts: {},
pending: {}, // callbacks pending: {},
cookie: null,
connected: true, connected: true,
}; };
var send = ctx.send = function (type, msg, cb) { // any particular network will only ever need one anonymous rpc
if (!ctx.connected) { networkContext.anon = ctx;
return void setTimeout(function () {
cb('DISCONNECTED');
});
}
ctx.send = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected) { return void cb('DISCONNECTED'); }
// construct an unsigned message... // construct an unsigned message...
var data = [type, msg]; var data = [type, msg];
// [sig, edPublicKey, cookie, type, msg] // [type, msg]
return sendMsg(ctx, data, cb); return sendMsg(ctx, data, cb);
}; };
@ -314,21 +368,35 @@ types of messages:
} }
}; };
network.on('message', function (msg, sender) { ctx.destroy = function () {
if (sender !== network.historyKeeper) { return; } // clear all pending timeouts
onAnonMsg(ctx, msg); Object.keys(ctx.timeouts).forEach(function (to) {
}); clearTimeout(to);
});
network.on('disconnect', function () { networkContext.anon = undefined;
ctx.connected = false; };
});
network.on('reconnect', function () { return ctx;
ctx.connected = true; };
});
var getAnonContext = function (networkContext) {
return networkContext.anon || initAnonRpc(networkContext);
};
var createAnonymous = function (network, _cb) {
// enforce asynchrony
var cb = Util.mkAsync(_cb);
if (typeof(cb) !== 'function') { throw new Error("expected callback"); }
if (!network) { return void cb('NO_NETWORK'); }
// get or create a context for the provided network
var ctx = getAnonContext(getNetworkContext(network));
cb(void 0, { cb(void 0, {
send: send send: ctx.send,
destroy: ctx.destroy,
}); });
}; };

@ -50,9 +50,10 @@ define([
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/customize/application_config.js', '/customize/application_config.js',
'/common/test.js', '/common/test.js',
'/common/userObject.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime, _FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, _AppConfig, _Test) { _Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) {
CpNfOuter = _CpNfOuter; CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad; Cryptpad = _Cryptpad;
Crypto = Utils.Crypto = _Crypto; Crypto = Utils.Crypto = _Crypto;
@ -68,6 +69,7 @@ define([
Utils.Constants = _Constants; Utils.Constants = _Constants;
Utils.Feedback = _Feedback; Utils.Feedback = _Feedback;
Utils.LocalStore = _LocalStore; Utils.LocalStore = _LocalStore;
Utils.UserObject = _UserObject;
AppConfig = _AppConfig; AppConfig = _AppConfig;
Test = _Test; Test = _Test;
@ -271,7 +273,7 @@ define([
Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys); Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
var parsed = Utils.Hash.parsePadUrl(window.location.href); var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); } if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.Hash.getDefaultName(parsed); var defaultTitle = Utils.UserObject.getDefaultName(parsed);
var edPublic, curvePublic, notifications, isTemplate; var edPublic, curvePublic, notifications, isTemplate;
var forceCreationScreen = cfg.useCreationScreen && var forceCreationScreen = cfg.useCreationScreen &&
sessionStorage[Utils.Constants.displayPadCreationScreen]; sessionStorage[Utils.Constants.displayPadCreationScreen];
@ -1176,7 +1178,7 @@ define([
// Update metadata values and send new metadata inside // Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href); parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed); defaultTitle = Utils.UserObject.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret); hashes = Utils.Hash.getHashes(secret);
readOnly = false; readOnly = false;
updateMeta(); updateMeta();

@ -15,8 +15,24 @@ define([
var TEMPLATE = module.TEMPLATE = "template"; var TEMPLATE = module.TEMPLATE = "template";
var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders"; var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders";
// Create untitled documents when no name is given
var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"};
return new window.Intl.DateTimeFormat(undefined, options).format(new Date());
}
return new Date().toString().split(' ').slice(0,4).join(' ');
};
module.getDefaultName = function (parsed) {
var type = parsed.type;
var name = (Messages.type)[type] + ' - ' + getLocaleDate();
return name;
};
module.init = function (files, config) { module.init = function (files, config) {
var exp = {}; var exp = {};
exp.getDefaultName = module.getDefaultName;
var sframeChan = config.sframeChan; var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey; var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;

Loading…
Cancel
Save