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

This commit is contained in:
yflory 2017-05-04 16:08:21 +02:00
commit 4335b050af
6 changed files with 339 additions and 78 deletions

View file

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

201
rpc.js
View file

@ -2,7 +2,11 @@
/* Use Nacl for checking signatures of messages */
var Nacl = require("tweetnacl");
/* globals Buffer*/
/* globals process */
var Fs = require("fs");
var Path = require("path");
var RPC = module.exports;
@ -12,6 +16,31 @@ var isValidChannel = function (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 () {
return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
.toString(16);
@ -23,7 +52,7 @@ var makeCookie = function (token) {
return [
time,
process.pid, // jshint ignore:line
process.pid,
token
];
};
@ -88,7 +117,7 @@ var isValidCookie = function (Sessions, publicKey, cookie) {
}
// different process. try harder
if (process.pid !== parsed.pid) { // jshint ignore:line
if (process.pid !== parsed.pid) {
return false;
}
@ -351,8 +380,7 @@ var unpinChannel = function (store, Sessions, publicKey, channels, cb) {
function (e) {
if (e) { return void cb(e); }
toStore.forEach(function (channel) {
// TODO actually delete
session.channels[channel] = false;
delete session.channels[channel]; // = false;
});
getHash(store, Sessions, publicKey, cb);
@ -389,31 +417,142 @@ var safeMkdir = function (path, cb) {
});
};
var upload = function (store, Sessions, publicKey, cb) {
/*
1. check if there is an upload in progress
* if yes, return error
2.
*/
console.log('UPLOAD_NOT_IMPLEMENTED');
cb('NOT_IMPLEMENTED');
var makeFilePath = function (root, id) {
if (typeof(id) !== 'string' || id.length <= 2) { return null; }
return Path.join(root, id.slice(0, 2), id);
};
var cancelUpload = function (store, Sessions, publicKey, cb) {
console.log('CANCEL_UPLOAD_NOT_IMPLEMENTED');
cb('NOT_IMPLEMENTED');
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);
});
});
};
tryRandomLocation(function (e, newPath, id) {
Fs.rename(oldPath, newPath, function (e) {
if (e) {
console.error(e);
return cb(e);
}
cb(void 0, id);
});
});
};
var upload_status = function (stagingPath, Sessions, publicKey, cb) {
var filePath = makeFilePath(stagingPath, publicKey);
if (!filePath) { return void cb('E_INVALID_PATH'); }
isFile(filePath, function (e, yes) {
cb(e, yes);
});
};
/*::const ConfigType = require('./config.example.js');*/
RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)=>void*/) {
// load pin-store...
console.log('loading rpc module...');
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 rpc = function (
@ -475,7 +614,7 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
var Respond = function (e, msg) {
var token = Sessions[publicKey].tokens.slice(-1)[0];
var cookie = makeCookie(token).join('|');
respond(e, [cookie].concat(msg||[]));
respond(e, [cookie].concat(typeof(msg) !== 'undefined' ?msg: []));
};
if (typeof(msg) !== 'object' || !msg.length) {
@ -519,11 +658,19 @@ RPC.create = function (config /*:typeof(ConfigType)*/, cb /*:(?Error, ?Function)
});
case 'UPLOAD':
return void upload(null, null, null, function (e) {
Respond(e);
return void upload(blobStagingPath, Sessions, safeKey, msg[1], function (e, len) {
Respond(e, len);
});
case 'CANCEL_UPLOAD':
return void cancelUpload(null, null, null, function (e) {
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 'UPLOAD_CANCEL':
return void upload_cancel(blobStagingPath, Sessions, safeKey, function (e) {
Respond(e);
});
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({
filePath: pinPath,
}, function (s) {

View file

@ -721,7 +721,7 @@ define([
};
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) {

View file

@ -102,9 +102,16 @@ types of messages:
timeouts: {}, // timeouts
pending: {}, // callbacks
cookie: null,
connected: true,
};
var send = function (type, msg, cb) {
if (!ctx.connected && type !== 'COOKIE') {
return void window.setTimeout(function () {
cb('DISCONNECTED');
});
}
// construct a signed message...
var data = [type, msg];
@ -127,6 +134,17 @@ types of messages:
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) {
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it

View file

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

View file

@ -14,6 +14,8 @@ define([
var saveAs = window.saveAs;
var Nacl = window.nacl;
var APP = {};
$(function () {
var ifrw = $('#pad-iframe')[0].contentWindow;
@ -31,12 +33,96 @@ define([
xhr.send(null);
};
var upload = function (blob, id, key) {
Cryptpad.alert("UPLOAD IS NOT IMPLEMENTED YET");
};
var myFile;
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 andThen = function () {
@ -54,8 +140,6 @@ define([
uploadMode = true;
}
//window.location.hash = '/2/K6xWU-LT9BJHCQcDCT-DcQ/VLIgpQOgmSaW3AQcUCCoJnYvCbMSO0MKBqaICSly9fo=';
var parsed = Cryptpad.parsePadUrl(window.location.href);
var defaultName = Cryptpad.getDefaultName(parsed);
@ -66,7 +150,7 @@ define([
return data ? data.title : undefined;
};
var updateTitle = function (newTitle) {
var updateTitle = APP.updateTitle = function (newTitle) {
Cryptpad.renamePad(newTitle, function (err, data) {
if (err) {
console.log("Couldn't set pad title");
@ -136,7 +220,7 @@ define([
FileCrypto.decrypt(u8, key, function (e, data) {
console.log(data);
var title = document.title = data.metadata.filename;
var title = document.title = data.metadata.name;
myFile = data.content;
myDataType = data.metadata.type;
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({
display: 'block',
});
@ -154,10 +242,13 @@ define([
var $file = $form.find("#file").on('change', function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.onload = function (e) {
upload(e.target.result);
reader.onloadend = function (e) {
upload(this.result, {
name: file.name,
type: file.type,
});
};
reader.readAsText(file);
reader.readAsArrayBuffer(file);
});
// we're in upload mode