Added cryptsheet to cryptpad suite!

This commit is contained in:
Caleb James DeLisle 2015-01-29 17:55:18 +01:00
parent 4a63ba7df3
commit 50c10f818e
18 changed files with 525 additions and 54 deletions

View File

@ -1,3 +1,3 @@
{
"directory" : "www/bower"
"directory" : "www/bower_components"
}

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
www/bower/*
www/bower_components/*
node_modules
/config.js

View File

@ -18,7 +18,8 @@
"tests"
],
"dependencies": {
"jquery": "~2.1.1",
"jquery.sheet": "master",
"jquery": "~2.1.3",
"tweetnacl": "~0.12.2",
"ckeditor": "~4.4.5",
"requirejs": "~2.1.15",

View File

@ -12,6 +12,12 @@ config.websocketPort = config.websocketPort || config.httpPort;
var app = Express();
app.use(Express.static(__dirname + '/www'));
// Bower is broken and does not allow components nested within components...
// And jquery.sheet expects it!
// *Workaround*
app.use("/bower_components/jquery.sheet/bower_components",
Express.static(__dirname + '/www/bower_components'));
var httpsOpts;
if (config.privKeyAndCertFiles) {
var privKeyAndCerts = '';

View File

@ -589,6 +589,18 @@ var unschedule = function (realtime, schedule) {
clearTimeout(schedule);
};
var onMessage = function (realtime, message, callback) {
if (!realtime.messageHandlers.length) {
callback("no onMessage() handler registered");
}
for (var i = 0; i < realtime.messageHandlers.length; i++) {
realtime.messageHandlers[i](message, function () {
callback.apply(null, arguments);
callback = function () { };
});
}
};
var sync = function (realtime) {
if (Common.PARANOIA) { check(realtime); }
if (realtime.syncSchedule) {
@ -622,7 +634,7 @@ var sync = function (realtime) {
var strMsg = Message.toString(msg);
realtime.onMessage(strMsg, function (err) {
onMessage(realtime, strMsg, function (err) {
if (err) {
debug(realtime, "Posting to server failed [" + err + "]");
}
@ -657,7 +669,7 @@ var getMessages = function (realtime) {
realtime.authToken,
realtime.channelId,
Message.REGISTER);
realtime.onMessage(Message.toString(msg), function (err) {
onMessage(realtime, Message.toString(msg), function (err) {
if (err) { throw err; }
});
};
@ -670,7 +682,7 @@ var sendPing = function (realtime) {
realtime.channelId,
Message.PING,
realtime.lastPingTime);
realtime.onMessage(Message.toString(msg), function (err) {
onMessage(realtime, Message.toString(msg), function (err) {
if (err) { throw err; }
});
};
@ -706,9 +718,7 @@ var create = ChainPad.create = function (userName, authToken, channelId, initial
patchHandlers: [],
opHandlers: [],
onMessage: function (message, callback) {
callback("no onMessage() handler registered");
},
messageHandlers: [],
schedules: [],
@ -1127,7 +1137,8 @@ module.exports.create = function (userName, authToken, channelId, initialState,
doOperation(realtime, Operation.create(offset, 0, str));
}),
onMessage: enterChainPad(realtime, function (handler) {
realtime.onMessage = handler;
Common.assert(typeof(handler) === 'function');
realtime.messageHandlers.push(handler);
}),
message: enterChainPad(realtime, function (message) {
handleMessage(realtime, message);

172
www/common/toolbar.js Normal file
View File

@ -0,0 +1,172 @@
var Toolbar = function ($, container, Messages, myUserName, realtime) {
/** Id of the element for getting debug info. */
var DEBUG_LINK_CLS = 'rtwysiwyg-debug-link';
/** Id of the div containing the user list. */
var USER_LIST_CLS = 'rtwysiwyg-user-list';
/** Id of the div containing the lag info. */
var LAG_ELEM_CLS = 'rtwysiwyg-lag';
/** The toolbar class which contains the user list, debug link and lag. */
var TOOLBAR_CLS = 'rtwysiwyg-toolbar';
/** Key in the localStore which indicates realtime activity should be disallowed. */
var LOCALSTORAGE_DISALLOW = 'rtwysiwyg-disallow';
var SPINNER_DISAPPEAR_TIME = 3000;
var SPINNER = [ '-', '\\', '|', '/' ];
var uid = function () {
return 'rtwysiwyg-uid-' + String(Math.random()).substring(2);
};
var createRealtimeToolbar = function (container) {
var id = uid();
$(container).prepend(
'<div class="' + TOOLBAR_CLS + '" id="' + id + '">' +
'<div class="rtwysiwyg-toolbar-leftside"></div>' +
'<div class="rtwysiwyg-toolbar-rightside"></div>' +
'</div>'
);
var toolbar = $('#'+id);
toolbar.append([
'<style>',
'.' + TOOLBAR_CLS + ' {',
' color: #666;',
' font-weight: bold;',
// ' background-color: #f0f0ee;',
// ' border-bottom: 1px solid #DDD;',
// ' border-top: 3px solid #CCC;',
// ' border-right: 2px solid #CCC;',
// ' border-left: 2px solid #CCC;',
' height: 26px;',
' margin-bottom: -3px;',
' display: inline-block;',
' width: 100%;',
'}',
'.' + TOOLBAR_CLS + ' div {',
' padding: 0 10px;',
' height: 1.5em;',
// ' background: #f0f0ee;',
' line-height: 25px;',
' height: 22px;',
'}',
'.rtwysiwyg-toolbar-leftside {',
' float: left;',
'}',
'.rtwysiwyg-toolbar-rightside {',
' float: right;',
'}',
'.rtwysiwyg-lag {',
' float: right;',
'}',
'.rtwysiwyg-spinner {',
' float: left;',
'}',
'.gwt-TabBar {',
' display:none;',
'}',
'.' + DEBUG_LINK_CLS + ':link { color:transparent; }',
'.' + DEBUG_LINK_CLS + ':link:hover { color:blue; }',
'.gwt-TabPanelBottom { border-top: 0 none; }',
'</style>'
].join('\n'));
return toolbar;
};
var createSpinner = function (container) {
var id = uid();
$(container).append('<div class="rtwysiwyg-spinner" id="'+id+'"></div>');
return $('#'+id)[0];
};
var kickSpinner = function (spinnerElement, reversed) {
var txt = spinnerElement.textContent || '-';
var inc = (reversed) ? -1 : 1;
spinnerElement.textContent = SPINNER[(SPINNER.indexOf(txt) + inc) % SPINNER.length];
spinnerElement.timeout && clearTimeout(spinnerElement.timeout);
spinnerElement.timeout = setTimeout(function () {
spinnerElement.textContent = '';
}, SPINNER_DISAPPEAR_TIME);
};
var createUserList = function (container) {
var id = uid();
$(container).prepend('<div class="' + USER_LIST_CLS + '" id="'+id+'"></div>');
return $('#'+id)[0];
};
var updateUserList = function (myUserName, listElement, userList) {
var meIdx = userList.indexOf(myUserName);
if (meIdx === -1) {
listElement.textContent = Messages.synchronizing;
return;
}
if (userList.length === 1) {
listElement.textContent = Messages.editingAlone;
} else if (userList.length === 2) {
listElement.textContent = Messages.editingWithOneOtherPerson;
} else {
listElement.textContent = Messages.editingWith + ' ' + (userList.length - 1) + ' '
Messages.otherPeople;
}
};
var createLagElement = function (container) {
var id = uid();
$(container).append('<div class="' + LAG_ELEM_CLS + '" id="'+id+'"></div>');
return $('#'+id)[0];
};
var checkLag = function (realtime, lagElement) {
var lag = realtime.getLag();
var lagSec = lag.lag/1000;
var lagMsg = Messages.lag + ' ';
if (lag.waiting && lagSec > 1) {
lagMsg += "?? " + Math.floor(lagSec);
} else {
lagMsg += lagSec;
}
lagElement.textContent = lagMsg;
};
var toolbar = createRealtimeToolbar(container);
var userListElement = createUserList(toolbar.find('.rtwysiwyg-toolbar-leftside'));
var spinner = createSpinner(toolbar.find('.rtwysiwyg-toolbar-rightside'));
var lagElement = createLagElement(toolbar.find('.rtwysiwyg-toolbar-rightside'));
var connected = false;
realtime.onUserListChange(function (userList) {
if (userList.indexOf(myUserName) !== -1) { connected = true; }
if (!connected) { return; }
updateUserList(myUserName, userListElement, userList);
});
var ks = function () {
if (connected) { kickSpinner(spinner, false); }
};
realtime.onPatch(ks);
realtime.onMessage(ks);
setInterval(function () {
if (!connected) { return; }
checkLag(realtime, lagElement);
}, 3000);
return {
reconnecting: function () {
connected = false;
userListElement.textContent = Messages.reconnecting;
lagElement.textContent = '';
},
connected: function () {
connected = true;
}
};
};

View File

@ -3,25 +3,32 @@
<head>
<!--<title>Sample - CKEditor</title>-->
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script data-main="main" src="bower/requirejs/require.js"></script>
<script>
if (window.location.href.indexOf('#') === -1) {
document.head.innerHTML +=
'<link rel="stylesheet" type="text/css" href="index.css" />';
}
</script>
<link rel="stylesheet" type="text/css" href="index.css" />
<style>
#whatis {
padding-left: 15%;
padding-right: 15%;
font-size: medium;
}
#create-pad {
background-color: #009afd;
.create {
background-color: rgb(77, 146, 68);
background-image: linear-gradient(rgb(39, 100, 0) 0%, rgb(77, 146, 68) 100%);
border-bottom-color: rgb(77, 146, 68);
color: rgb(243, 243, 243);
font-weight:bold;
font-size:large;
width:150px;
height:35px;
margin-right: 5px;
margin-left: 5px;
}
.buttons {
margin-top: 10px;
}
.button {
padding: 2px 6px 2px 6px;
border-top: 1px solid #CCCCCC;
border-right: 1px solid #333333;
border-bottom: 1px solid #333333;
border-left: 1px solid #CCCCCC;
}
</style>
</head>
@ -48,16 +55,14 @@
get caught and laughed at and humiliated in front of the whole world (again). If you&#39;re making
the NSA mad enough for them to use an active attack against you, Great Success Highfive, now take
the battery out of your computer before it spawns Agent Smith.</p>
<center>
<h5>Try it out!</h5>
<div class="buttons">
<a class="button create" href="pad">CREATE PAD</a>
<a class="button create" href="sheet">CREATE SPREADSHEET</a>
</div>
</center>
</div>
<form id="go" action="#" method="post">
<center>
<h5>Try it out!</h5>
<br/>
<input id="create-pad" type="submit" name="x" value="Create Pad!" />
</center>
<textarea style="display:none" cols="80" id="editor1" name="editor1" rows="10">
</textarea>
</form>
</body>
</html>

View File

@ -16,12 +16,14 @@
*/
require.config({
'shim': {
'bower/modalBox/modalBox-min': ['bower/jquery/dist/jquery.min'],
'/bower_components/modalBox/modalBox-min.js': [
'/bower_components/jquery/dist/jquery.min.js'
],
}
});
define([
'messages',
'bower/modalBox/modalBox-min'
'/common/messages.js',
'/bower_components/modalBox/modalBox-min.js'
], function (Messages) {
var STYLE = [

View File

@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([
'bower/jquery/dist/jquery.min',
'otaml'
'/bower_components/jquery/dist/jquery.min.js',
'/common/otaml.js'
], function () {
var $ = jQuery;

11
www/pad/index.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
</head>
<body>
<textarea style="display:none" cols="80" id="editor1" name="editor1" rows="10"></textarea>
</body>
</html>

View File

@ -1,10 +1,10 @@
define([
'api/config?cb=' + Math.random().toString(16).substring(2),
'realtime-wysiwyg',
'messages',
'bower/jquery/dist/jquery.min',
'bower/ckeditor/ckeditor',
'bower/tweetnacl/nacl-fast.min',
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/pad/realtime-wysiwyg.js',
'/common/messages.js',
'/bower_components/jquery/dist/jquery.min.js',
'/bower_components/ckeditor/ckeditor.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (Config, RTWysiwyg, Messages) {
var Ckeditor = window.CKEDITOR;
var Nacl = window.nacl;
@ -31,10 +31,7 @@ define([
window.location.reload();
});
if (window.location.href.indexOf('#') === -1) {
$('#create-pad').click(function (ev) {
ev.preventDefault();
window.location.href = window.location.href + '#' + genKey();
});
window.location.href = window.location.href + '#' + genKey();
return;
}
var key = parseKey(window.location.hash.substring(1));

View File

@ -15,15 +15,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([
'html-patcher',
'errorbox',
'messages',
'bower/reconnectingWebsocket/reconnecting-websocket',
'rangy',
'chainpad',
'otaml',
'bower/jquery/dist/jquery.min',
'bower/tweetnacl/nacl-fast.min'
'/pad/html-patcher.js',
'/pad/errorbox.js',
'/common/messages.js',
'/bower_components/reconnectingWebsocket/reconnecting-websocket.js',
'/pad/rangy.js',
'/common/chainpad.js',
'/common/otaml.js',
'/bower_components/jquery/dist/jquery.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (HTMLPatcher, ErrorBox, Messages, ReconnectingWebSocket) {
window.ErrorBox = ErrorBox;

29
www/sheet/index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
<style>
iframe {
position:fixed;
top:0px;
left:0px;
bottom:0px;
right:0px;
width:100%;
height:100%;
border:none;
margin:0;
padding:0;
overflow:hidden;
}
textarea {
display:none;
}
</style>
</head>
<body>
<iframe src="inner.html"></iframe>
<textarea id="sheet-json"></textarea>
</body>
</html>

36
www/sheet/inner.html Normal file
View File

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="-1">
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<title>jQuery.sheet - The Ajax Spreadsheet DTS Lazy Loading</title>
<script src="/bower_components/jquery-legacy/dist/jquery.js"></script>
<script src="/bower_components/jquery.sheet/parser/formula/formula.js"></script>
<script src="/bower_components/jquery.sheet/jquery.sheet.js"></script>
<script>
var numColumns = 10;
var numRows = 20;
var spreadsheetJson = [ { "title": "", "rows": [ ] } ];
for (var i = 0; i < numRows; i++) {
var columns = [];
spreadsheetJson[0].rows.push({ columns: columns });
for (var j = 0; j < numColumns; j++) { columns[j] = { "value": "" }; }
}
$.sheet.preLoad('/bower_components/jquery.sheet/');
$(function () {
window.sheetLdr = new Sheet.JSONLoader( spreadsheetJson );
window.sh = $('#sheet-json').height($(window).height()).width($(window).width()).sheet({
loader: window.sheetLdr,
minSize: { rows:numRows, cols:numColumns }
});
});
</script>
</head>
<body>
<div id="sheet-json" title="CryptSheet"></div>
</body>
</html>

201
www/sheet/main.js Normal file
View File

@ -0,0 +1,201 @@
define([
'/api/config?cb=' + Math.random().toString(16).substring(2),
'/common/messages.js',
'/common/toolbar.js',
'/common/chainpad.js',
'/bower_components/jquery/dist/jquery.min.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/common/otaml.js'
], function (Config, Messages) {
var Nacl = window.nacl;
var $ = jQuery;
var ChainPad = window.ChainPad;
var Otaml = window.Otaml;
var Toolbar = window.Toolbar;
var module = { exports: {} };
var parseKey = function (str) {
var array = Nacl.util.decodeBase64(str);
var hash = Nacl.hash(array);
return { lookupKey: hash.subarray(32), cryptKey: hash.subarray(0,32) };
};
var genKey = function () {
return Nacl.util.encodeBase64(Nacl.randomBytes(18));
};
var userName = function () {
return 'Other-' + Nacl.util.encodeBase64(Nacl.randomBytes(8));
};
var sheetToJson = function (ifrWindow) {
var xx = ifrWindow.sh[0].jS
var m = [];
for (var i = 0; i < xx.spreadsheets.length; i++) {
m[i]=[];
var sheet = xx.spreadsheets[i];
for (var j = 1; j < sheet.length; j++) {
m[i][j]=[];
var row = sheet[j];
for (var k = 1; k < row.length; k++) {
var col = row[k];
m[i][j][k] = { value: col.value, formula: col.formula };
}
}
}
return m;
};
var jsonToSheet = function (ifrWindow, json) {
var xx = ifrWindow.sh[0].jS;
for (var i = 0; i < xx.spreadsheets.length; i++) {
var sheet = xx.spreadsheets[i];
for (var j = 1; j < sheet.length; j++) {
var row = sheet[j];
for (var k = 1; k < row.length; k++) {
var col = row[k];
var jcol = json[i][j][k];
if (jcol.value === col.value && jcol.formula === col.formula) { continue; }
col.value = jcol.value;
col.formula = jcol.formula;
col.displayValue();
}
}
}
};
var encryptStr = function (str, key) {
var array = Nacl.util.decodeUTF8(str);
var nonce = Nacl.randomBytes(24);
var packed = Nacl.secretbox(array, nonce, key);
if (!packed) { throw new Error(); }
return Nacl.util.encodeBase64(nonce) + "|" + Nacl.util.encodeBase64(packed);
};
var decryptStr = function (str, key) {
var arr = str.split('|');
if (arr.length !== 2) { throw new Error(); }
var nonce = Nacl.util.decodeBase64(arr[0]);
var packed = Nacl.util.decodeBase64(arr[1]);
var unpacked = Nacl.secretbox.open(packed, nonce, key);
if (!unpacked) { throw new Error(); }
return Nacl.util.encodeUTF8(unpacked);
};
// this is crap because of bencoding messages... it should go away....
var splitMessage = function (msg, sending) {
var idx = 0;
var nl;
for (var i = ((sending) ? 0 : 1); i < 3; i++) {
nl = msg.indexOf(':',idx);
idx = nl + Number(msg.substring(idx,nl)) + 1;
}
return [ msg.substring(0,idx), msg.substring(msg.indexOf(':',idx) + 1) ];
};
var encrypt = function (msg, key) {
var spl = splitMessage(msg, true);
var json = JSON.parse(spl[1]);
// non-patches are not encrypted.
if (json[0] !== 2) { return msg; }
json[1] = encryptStr(JSON.stringify(json[1]), key);
var res = JSON.stringify(json);
return spl[0] + res.length + ':' + res;
};
var decrypt = function (msg, key) {
var spl = splitMessage(msg, false);
var json = JSON.parse(spl[1]);
// non-patches are not encrypted.
if (json[0] !== 2) { return msg; }
if (typeof(json[1]) !== 'string') { throw new Error(); }
json[1] = JSON.parse(decryptStr(json[1], key));
var res = JSON.stringify(json);
return spl[0] + res.length + ':' + res;
};
var applyChange = function(ctx, oldval, newval) {
if (oldval === newval) return;
var commonStart = 0;
while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
commonStart++;
}
var commonEnd = 0;
while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) &&
commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
commonEnd++;
}
if (oldval.length !== commonStart + commonEnd) {
ctx.remove(commonStart, oldval.length - commonStart - commonEnd);
}
if (newval.length !== commonStart + commonEnd) {
ctx.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd));
}
};
$(function () {
if (window.location.href.indexOf('#') === -1) {
window.location.href = window.location.href + '#' + genKey();
}
$(window).on('hashchange', function() {
window.location.reload();
});
var $sheetJson = $('#sheet-json');
var ifrw = $('iframe')[0].contentWindow;
var sheetEvent = function (realtime) {
var sheetJson = JSON.stringify(sheetToJson(ifrw));
applyChange(realtime, realtime.getUserDoc(), sheetJson);
$sheetJson.text(sheetJson);
};
var eventPending = false;
var realtimeEvent = function (realtime) {
if (eventPending) { return; }
eventPending = true;
setTimeout(function () {
eventPending = false;
try{
var data = window.data = realtime.getUserDoc();
$sheetJson.text(data);
var json = JSON.parse(data);
jsonToSheet(ifrw, json);
}catch(e) { console.log(e.stack); }
}, 0);
};
var key = parseKey(window.location.hash.substring(1));
var channel = Nacl.util.encodeBase64(key.lookupKey).substring(0,10);
var myUserName = userName();
var socket = new WebSocket(Config.websocketURL);
socket.onopen = function () {
var realtime = ChainPad.create(
myUserName, 'x', channel, '', { transformFunction: Otaml.transform });
socket.onmessage = function (evt) {
var message = decrypt(evt.data, key.cryptKey);
realtime.message(message);
};
realtime.onMessage(function (message) {
message = encrypt(message, key.cryptKey);
try {
socket.send(message);
} catch (e) {
console.log(e.stack);
}
});
ifrw.sh.on('sheetCellEdited', function () { sheetEvent(realtime); });
sheetEvent(realtime);
realtime.onPatch(function () { realtimeEvent(realtime); });
ifrw.$('.jSTitle').html('');
Toolbar(ifrw.$, ifrw.$('.jSTitle'), Messages, myUserName, realtime);
realtime.start();
};
});
});