Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

pull/1/head
yflory 8 years ago
commit 4335b050af

@ -37,5 +37,8 @@ define(function() {
config.enableHistory = true; config.enableHistory = true;
//config.enablePinLimit = true;
//config.pinLimit = 1000;
return config; return config;
}); });

197
rpc.js

@ -2,7 +2,11 @@
/* Use Nacl for checking signatures of messages */ /* Use Nacl for checking signatures of messages */
var Nacl = require("tweetnacl"); var Nacl = require("tweetnacl");
/* globals Buffer*/
/* globals process */
var Fs = require("fs"); var Fs = require("fs");
var Path = require("path");
var RPC = module.exports; var RPC = module.exports;
@ -12,6 +16,31 @@ var isValidChannel = function (chan) {
return /^[a-fA-F0-9]/.test(chan); return /^[a-fA-F0-9]/.test(chan);
}; };
var uint8ArrayToHex = function (a) {
// call slice so Uint8Arrays work as expected
return Array.prototype.slice.call(a).map(function (e, i) {
var n = Number(e & 0xff).toString(16);
if (n === 'NaN') {
throw new Error('invalid input resulted in NaN');
}
switch (n.length) {
case 0: return '00'; // just being careful, shouldn't happen
case 1: return '0' + n;
case 2: return n;
default: throw new Error('unexpected value');
}
}).join('');
};
var createChannelId = function () {
var id = uint8ArrayToHex(Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
var makeToken = function () { var makeToken = function () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
.toString(16); .toString(16);
@ -23,7 +52,7 @@ var makeCookie = function (token) {
return [ return [
time, time,
process.pid, // jshint ignore:line process.pid,
token token
]; ];
}; };
@ -88,7 +117,7 @@ var isValidCookie = function (Sessions, publicKey, cookie) {
} }
// different process. try harder // different process. try harder
if (process.pid !== parsed.pid) { // jshint ignore:line if (process.pid !== parsed.pid) {
return false; return false;
} }
@ -351,8 +380,7 @@ var unpinChannel = function (store, Sessions, publicKey, channels, cb) {
function (e) { function (e) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
toStore.forEach(function (channel) { toStore.forEach(function (channel) {
// TODO actually delete delete session.channels[channel]; // = false;
session.channels[channel] = false;
}); });
getHash(store, Sessions, publicKey, cb); getHash(store, Sessions, publicKey, cb);
@ -389,31 +417,142 @@ var safeMkdir = function (path, cb) {
}); });
}; };
var upload = function (store, Sessions, publicKey, cb) { var makeFilePath = function (root, id) {
/* if (typeof(id) !== 'string' || id.length <= 2) { return null; }
1. check if there is an upload in progress return Path.join(root, id.slice(0, 2), id);
* if yes, return error };
2.
var makeFileStream = function (root, id, cb) {
var stub = id.slice(0, 2);
var full = makeFilePath(root, id);
safeMkdir(Path.join(root, stub), function (e) {
if (e) { return void cb(e); }
try {
var stream = Fs.createWriteStream(full, {
flags: 'a',
encoding: 'binary',
});
stream.on('open', function () {
cb(void 0, stream);
});
} catch (err) {
cb('BAD_STREAM');
}
});
};
var upload = function (stagingPath, Sessions, publicKey, content, cb) {
var dec = new Buffer(Nacl.util.decodeBase64(content)); // jshint ignore:line
var session = Sessions[publicKey];
if (!session.blobstage) {
makeFileStream(stagingPath, publicKey, function (e, stream) {
if (e) { return void cb(e); }
var blobstage = session.blobstage = stream;
blobstage.write(dec);
cb(void 0, dec.length);
});
} else {
session.blobstage.write(dec);
cb(void 0, dec.length);
}
};
var upload_cancel = function (stagingPath, Sessions, publicKey, cb) {
var path = makeFilePath(stagingPath, publicKey);
if (!path) {
console.log(stagingPath, publicKey);
console.log(path);
return void cb('NO_FILE');
}
Fs.unlink(path, function (e) {
if (e) { return void cb('E_UNLINK'); }
cb(void 0);
});
};
var isFile = function (filePath, cb) {
Fs.stat(filePath, function (e, stats) {
if (e) {
if (e.code === 'ENOENT') { return void cb(void 0, false); }
return void cb(e.message);
}
return void cb(void 0, stats.isFile());
});
};
var upload_complete = function (stagingPath, storePath, Sessions, publicKey, cb) {
var session = Sessions[publicKey];
*/ if (session.blobstage && session.blobstage.close) {
session.blobstage.close();
delete session.blobstage;
}
var oldPath = makeFilePath(stagingPath, publicKey);
var tryRandomLocation = function (cb) {
var id = createChannelId();
var prefix = id.slice(0, 2);
var newPath = makeFilePath(storePath, id);
safeMkdir(Path.join(storePath, prefix), function (e) {
if (e) {
console.error(e);
return void cb('RENAME_ERR');
}
isFile(newPath, function (e, yes) {
if (e) {
console.error(e);
return void cb(e);
}
if (yes) {
return void tryRandomLocation(cb);
}
cb(void 0, newPath, id);
});
});
};
console.log('UPLOAD_NOT_IMPLEMENTED'); tryRandomLocation(function (e, newPath, id) {
cb('NOT_IMPLEMENTED'); Fs.rename(oldPath, newPath, function (e) {
if (e) {
console.error(e);
return cb(e);
}
cb(void 0, id);
});
});
}; };
var cancelUpload = function (store, Sessions, publicKey, cb) { var upload_status = function (stagingPath, Sessions, publicKey, cb) {
console.log('CANCEL_UPLOAD_NOT_IMPLEMENTED'); var filePath = makeFilePath(stagingPath, publicKey);
cb('NOT_IMPLEMENTED'); if (!filePath) { return void cb('E_INVALID_PATH'); }
isFile(filePath, function (e, yes) {
cb(e, yes);
});
}; };
/*::const ConfigType = require('./config.example.js');*/ /*::const ConfigType = require('./config.example.js');*/
RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) { RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) {
// load pin-store... // load pin-store...
console.log('loading rpc module...'); console.log('loading rpc module...');
var Sessions = {}; var Sessions = {};
var keyOrDefaultString = function (key, def) {
return typeof(config[key]) === 'string'? config[key]: def;
};
var pinPath = keyOrDefaultString('pinPath', './pins');
var blobPath = keyOrDefaultString('blobPath', './blob');
var blobStagingPath = keyOrDefaultString('blobStagingPath', './blobstage');
var store; var store;
var rpc = function ( var rpc = function (
@ -475,7 +614,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
var Respond = function (e, msg) { var Respond = function (e, msg) {
var token = Sessions[publicKey].tokens.slice(-1)[0]; var token = Sessions[publicKey].tokens.slice(-1)[0];
var cookie = makeCookie(token).join('|'); var cookie = makeCookie(token).join('|');
respond(e, [cookie].concat(msg||[])); respond(e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: []));
}; };
if (typeof(msg) !== 'object' || !msg.length) { if (typeof(msg) !== 'object' || !msg.length) {
@ -519,11 +658,19 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
}); });
case 'UPLOAD': case 'UPLOAD':
return void upload(null, null, null, function (e) { return void upload(blobStagingPath, Sessions, safeKey, msg[1], function (e, len) {
Respond(e); Respond(e, len);
});
case 'UPLOAD_STATUS':
return void upload_status(blobStagingPath, Sessions, safeKey, function (e, stat) {
Respond(e, stat);
});
case 'UPLOAD_COMPLETE':
return void upload_complete(blobStagingPath, blobPath, Sessions, safeKey, function (e, hash) {
Respond(e, hash);
}); });
case 'CANCEL_UPLOAD': case 'UPLOAD_CANCEL':
return void cancelUpload(null, null, null, function (e) { return void upload_cancel(blobStagingPath, Sessions, safeKey, function (e) {
Respond(e); Respond(e);
}); });
default: default:
@ -531,14 +678,6 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
} }
}; };
var keyOrDefaultString = function (key, def) {
return typeof(config[key]) === 'string'? config[key]: def;
};
var pinPath = keyOrDefaultString('pinPath', './pins');
var blobPath = keyOrDefaultString('blobPath', './blob');
var blobStagingPath = keyOrDefaultString('blobStagingPath', './blobstage');
Store.create({ Store.create({
filePath: pinPath, filePath: pinPath,
}, function (s) { }, function (s) {

@ -721,7 +721,7 @@ define([
}; };
var getPinLimit = common.getPinLimit = function (cb) { var getPinLimit = common.getPinLimit = function (cb) {
cb(void 0, 1000); cb(void 0, typeof(AppConfig.pinLimit) === 'number'? AppConfig.pinLimit: 1000);
}; };
var isOverPinLimit = common.isOverPinLimit = function (cb) { var isOverPinLimit = common.isOverPinLimit = function (cb) {

@ -102,9 +102,16 @@ types of messages:
timeouts: {}, // timeouts timeouts: {}, // timeouts
pending: {}, // callbacks pending: {}, // callbacks
cookie: null, cookie: null,
connected: true,
}; };
var send = function (type, msg, cb) { var send = function (type, msg, cb) {
if (!ctx.connected && type !== 'COOKIE') {
return void window.setTimeout(function () {
cb('DISCONNECTED');
});
}
// construct a signed message... // construct a signed message...
var data = [type, msg]; var data = [type, msg];
@ -127,6 +134,17 @@ types of messages:
onMsg(ctx, msg); onMsg(ctx, msg);
}); });
network.on('disconnect', function (reason) {
ctx.connected = false;
});
network.on('reconnect', function (uid) {
send('COOKIE', "", function (e, msg) {
if (e) { return void cb(e); }
ctx.connected = true;
});
});
send('COOKIE', "", function (e, msg) { send('COOKIE', "", function (e, msg) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it // callback to provide 'send' method to whatever needs it

@ -122,15 +122,7 @@ define([
// metadata // metadata
/* { filename: 'raccoon.jpg', type: 'image/jpeg' } */ /* { filename: 'raccoon.jpg', type: 'image/jpeg' } */
var encrypt = function (u8, metadata, key) {
/* TODO
in your callback, return an object which you can iterate...
*/
var encrypt = function (u8, metadata, key, cb) {
var nonce = createNonce(); var nonce = createNonce();
// encode metadata // encode metadata
@ -139,44 +131,62 @@ define([
var plaintext = new Uint8Array(padChunk(metaBuffer)); var plaintext = new Uint8Array(padChunk(metaBuffer));
var chunks = [];
var j = 0; var j = 0;
var i = 0;
var start; /*
var end; 0: metadata
1: u8
2: done
*/
var part; var state = 0;
var box;
// prepend some metadata var next = function (cb) {
for (;j * plainChunkLength < plaintext.length; j++) { var start;
start = j * plainChunkLength; var end;
end = start + plainChunkLength; var part;
var box;
part = plaintext.subarray(start, end); if (state === 0) { // metadata...
box = Nacl.secretbox(part, nonce, key); start = j * plainChunkLength;
chunks.push(box); end = start + plainChunkLength;
increment(nonce);
}
// append the encrypted file chunks part = plaintext.subarray(start, end);
var i = 0; box = Nacl.secretbox(part, nonce, key);
for (;i * plainChunkLength < u8.length; i++) { increment(nonce);
j++;
// metadata is done
if (j * plainChunkLength >= plaintext.length) {
return void cb(state++, box);
}
return void cb(state, box);
}
// encrypt the rest of the file...
start = i * plainChunkLength; start = i * plainChunkLength;
end = start + plainChunkLength; end = start + plainChunkLength;
part = new Uint8Array(u8.subarray(start, end)); part = u8.subarray(start, end);
box = Nacl.secretbox(part, nonce, key); box = Nacl.secretbox(part, nonce, key);
chunks.push(box);
increment(nonce); increment(nonce);
} i++;
// regular data is done
if (i * plainChunkLength >= u8.length) { state = 2; }
return void cb(state, box);
};
// TODO do something with the chunks... return next;
}; };
return { return {
decrypt: decrypt, decrypt: decrypt,
encrypt: encrypt, encrypt: encrypt,
joinChunks: joinChunks,
}; };
}); });

@ -14,6 +14,8 @@ define([
var saveAs = window.saveAs; var saveAs = window.saveAs;
var Nacl = window.nacl; var Nacl = window.nacl;
var APP = {};
$(function () { $(function () {
var ifrw = $('#pad-iframe')[0].contentWindow; var ifrw = $('#pad-iframe')[0].contentWindow;
@ -31,12 +33,96 @@ define([
xhr.send(null); xhr.send(null);
}; };
var upload = function (blob, id, key) {
Cryptpad.alert("UPLOAD IS NOT IMPLEMENTED YET");
};
var myFile; var myFile;
var myDataType; var myDataType;
var upload = function (blob, metadata) {
console.log(metadata);
var u8 = new Uint8Array(blob);
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var chunks = [];
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
chunks.push(box);
Cryptpad.rpc.send('UPLOAD', enc, function (e, msg) {
cb(e, msg);
});
};
var again = function (state, box) {
switch (state) {
case 0:
sendChunk(box, function (e, msg) {
if (e) { return console.error(e); }
next(again);
});
break;
case 1:
sendChunk(box, function (e, msg) {
if (e) { return console.error(e); }
next(again);
});
break;
case 2:
sendChunk(box, function (e, msg) {
if (e) { return console.error(e); }
Cryptpad.rpc.send('UPLOAD_COMPLETE', '', function (e, res) {
if (e) { return void console.error(e); }
var id = res[0];
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
window.location.hash = [
'',
2,
Cryptpad.hexToBase64(id).replace(/\//g, '-'),
Nacl.util.encodeBase64(key).replace(/\//g, '-'),
''
].join('/');
APP.$form.hide();
var newU8 = FileCrypto.joinChunks(chunks);
FileCrypto.decrypt(newU8, key, function (e, res) {
var title = document.title = res.metadata.filename;
myFile = res.content;
myDataType = res.metadata.type;
var defaultName = Cryptpad.getDefaultName(Cryptpad.parsePadUrl(window.location.href));
APP.updateTitle(title || defaultName);
});
});
});
break;
default:
throw new Error("E_INVAL_STATE");
}
};
Cryptpad.rpc.send('UPLOAD_STATUS', '', function (e, pending) {
if (e) {
console.error(e);
return void Cryptpad.alert("something went wrong");
}
if (pending[0]) {
return void Cryptpad.confirm('upload pending, abort?', function (yes) {
if (!yes) { return; }
Cryptpad.rpc.send('UPLOAD_CANCEL', '', function (e, res) {
if (e) { return void console.error(e); }
console.log(res);
});
});
}
next(again);
});
};
var uploadMode = false; var uploadMode = false;
var andThen = function () { var andThen = function () {
@ -54,8 +140,6 @@ define([
uploadMode = true; uploadMode = true;
} }
//window.location.hash = '/2/K6xWU-LT9BJHCQcDCT-DcQ/VLIgpQOgmSaW3AQcUCCoJnYvCbMSO0MKBqaICSly9fo=';
var parsed = Cryptpad.parsePadUrl(window.location.href); var parsed = Cryptpad.parsePadUrl(window.location.href);
var defaultName = Cryptpad.getDefaultName(parsed); var defaultName = Cryptpad.getDefaultName(parsed);
@ -66,7 +150,7 @@ define([
return data ? data.title : undefined; return data ? data.title : undefined;
}; };
var updateTitle = function (newTitle) { var updateTitle = APP.updateTitle = function (newTitle) {
Cryptpad.renamePad(newTitle, function (err, data) { Cryptpad.renamePad(newTitle, function (err, data) {
if (err) { if (err) {
console.log("Couldn't set pad title"); console.log("Couldn't set pad title");
@ -136,7 +220,7 @@ define([
FileCrypto.decrypt(u8, key, function (e, data) { FileCrypto.decrypt(u8, key, function (e, data) {
console.log(data); console.log(data);
var title = document.title = data.metadata.filename; var title = document.title = data.metadata.name;
myFile = data.content; myFile = data.content;
myDataType = data.metadata.type; myDataType = data.metadata.type;
updateTitle(title || defaultName); updateTitle(title || defaultName);
@ -146,7 +230,11 @@ define([
}); });
} }
var $form = $iframe.find('#upload-form'); if (!Cryptpad.isLoggedIn()) {
return Cryptpad.alert("You must be logged in to upload files");
}
var $form = APP.$form = $iframe.find('#upload-form');
$form.css({ $form.css({
display: 'block', display: 'block',
}); });
@ -154,10 +242,13 @@ define([
var $file = $form.find("#file").on('change', function (e) { var $file = $form.find("#file").on('change', function (e) {
var file = e.target.files[0]; var file = e.target.files[0];
var reader = new FileReader(); var reader = new FileReader();
reader.onload = function (e) { reader.onloadend = function (e) {
upload(e.target.result); upload(this.result, {
name: file.name,
type: file.type,
});
}; };
reader.readAsText(file); reader.readAsArrayBuffer(file);
}); });
// we're in upload mode // we're in upload mode

Loading…
Cancel
Save